@zywave/zywave-api-toolkit-bundle
Version:
1,132 lines (1,120 loc) • 86.5 kB
JavaScript
import { d as desc, _ as _$LH, n as noChange, a as nothing, c as css, p as property, q as query, Z as ZywaveBaseElement, h as html, i as ifDefined } from './internals/_api-proxy-f3bf2df5.js';
import { A as AnalyticsTracker } from './internals/_analytics-a9e9065c.js';
/**
* @license
* Copyright 2017 Google LLC
* SPDX-License-Identifier: BSD-3-Clause
*/
// Note, in the future, we may extend this decorator to support the use case
// where the queried element may need to do work to become ready to interact
// with (e.g. load some implementation code). If so, we might elect to
// add a second argument defining a function that can be run to make the
// queried element loaded/updated/ready.
/**
* A property decorator that converts a class property into a getter that
* returns a promise that resolves to the result of a querySelector on the
* element's renderRoot done after the element's `updateComplete` promise
* resolves. When the queried property may change with element state, this
* decorator can be used instead of requiring users to await the
* `updateComplete` before accessing the property.
*
* @param selector A DOMString containing one or more selectors to match.
*
* See: https://developer.mozilla.org/en-US/docs/Web/API/Document/querySelector
*
* ```ts
* class MyElement {
* @queryAsync('#first')
* first: Promise<HTMLDivElement>;
*
* render() {
* return html`
* <div id="first"></div>
* <div id="second"></div>
* `;
* }
* }
*
* // external usage
* async doSomethingWithFirst() {
* (await aMyElement.first).doSomething();
* }
* ```
* @category Decorator
*/
function queryAsync(selector) {
return (obj, name) => {
return desc(obj, name, {
async get() {
await this.updateComplete;
return this.renderRoot?.querySelector(selector) ?? null;
}
});
};
}
/**
* @license
* Copyright 2017 Google LLC
* SPDX-License-Identifier: BSD-3-Clause
*/
const PartType = {
ATTRIBUTE: 1,
CHILD: 2,
PROPERTY: 3,
BOOLEAN_ATTRIBUTE: 4,
EVENT: 5,
ELEMENT: 6
};
/**
* Creates a user-facing directive function from a Directive class. This
* function has the same parameters as the directive's render() method.
*/
const directive = c => (...values) => ({
// This property needs to remain unminified.
['_$litDirective$']: c,
values
});
/**
* Base class for creating custom directives. Users should extend this class,
* implement `render` and/or `update`, and then pass their subclass to
* `directive`.
*/
class Directive {
constructor(_partInfo) {}
// See comment in Disconnectable interface for why this is a getter
get _$isConnected() {
return this._$parent._$isConnected;
}
/** @internal */
_$initialize(part, parent, attributeIndex) {
this.__part = part;
this._$parent = parent;
this.__attributeIndex = attributeIndex;
}
/** @internal */
_$resolve(part, props) {
return this.update(part, props);
}
update(_part, props) {
return this.render(...props);
}
}
/**
* @license
* Copyright 2020 Google LLC
* SPDX-License-Identifier: BSD-3-Clause
*/
const {
_ChildPart: ChildPart
} = _$LH;
const wrap = window.ShadyDOM?.inUse && window.ShadyDOM?.noPatch === true ? window.ShadyDOM.wrap : node => node;
const createMarker = () => document.createComment('');
/**
* Inserts a ChildPart into the given container ChildPart's DOM, either at the
* end of the container ChildPart, or before the optional `refPart`.
*
* This does not add the part to the containerPart's committed value. That must
* be done by callers.
*
* @param containerPart Part within which to add the new ChildPart
* @param refPart Part before which to add the new ChildPart; when omitted the
* part added to the end of the `containerPart`
* @param part Part to insert, or undefined to create a new part
*/
const insertPart = (containerPart, refPart, part) => {
const container = wrap(containerPart._$startNode).parentNode;
const refNode = refPart === undefined ? containerPart._$endNode : refPart._$startNode;
if (part === undefined) {
const startNode = wrap(container).insertBefore(createMarker(), refNode);
const endNode = wrap(container).insertBefore(createMarker(), refNode);
part = new ChildPart(startNode, endNode, containerPart, containerPart.options);
} else {
const endNode = wrap(part._$endNode).nextSibling;
const oldParent = part._$parent;
const parentChanged = oldParent !== containerPart;
if (parentChanged) {
part._$reparentDisconnectables?.(containerPart);
// Note that although `_$reparentDisconnectables` updates the part's
// `_$parent` reference after unlinking from its current parent, that
// method only exists if Disconnectables are present, so we need to
// unconditionally set it here
part._$parent = containerPart;
// Since the _$isConnected getter is somewhat costly, only
// read it once we know the subtree has directives that need
// to be notified
let newConnectionState;
if (part._$notifyConnectionChanged !== undefined && (newConnectionState = containerPart._$isConnected) !== oldParent._$isConnected) {
part._$notifyConnectionChanged(newConnectionState);
}
}
if (endNode !== refNode || parentChanged) {
let start = part._$startNode;
while (start !== endNode) {
const n = wrap(start).nextSibling;
wrap(container).insertBefore(start, refNode);
start = n;
}
}
}
return part;
};
/**
* Sets the value of a Part.
*
* Note that this should only be used to set/update the value of user-created
* parts (i.e. those created using `insertPart`); it should not be used
* by directives to set the value of the directive's container part. Directives
* should return a value from `update`/`render` to update their part state.
*
* For directives that require setting their part value asynchronously, they
* should extend `AsyncDirective` and call `this.setValue()`.
*
* @param part Part to set
* @param value Value to set
* @param index For `AttributePart`s, the index to set
* @param directiveParent Used internally; should not be set by user
*/
const setChildPartValue = (part, value, directiveParent = part) => {
part._$setValue(value, directiveParent);
return part;
};
// A sentinel value that can never appear as a part value except when set by
// live(). Used to force a dirty-check to fail and cause a re-render.
const RESET_VALUE = {};
/**
* Sets the committed value of a ChildPart directly without triggering the
* commit stage of the part.
*
* This is useful in cases where a directive needs to update the part such
* that the next update detects a value change or not. When value is omitted,
* the next update will be guaranteed to be detected as a change.
*
* @param part
* @param value
*/
const setCommittedValue = (part, value = RESET_VALUE) => part._$committedValue = value;
/**
* Returns the committed value of a ChildPart.
*
* The committed value is used for change detection and efficient updates of
* the part. It can differ from the value set by the template or directive in
* cases where the template value is transformed before being committed.
*
* - `TemplateResult`s are committed as a `TemplateInstance`
* - Iterables are committed as `Array<ChildPart>`
* - All other types are committed as the template value or value returned or
* set by a directive.
*
* @param part
*/
const getCommittedValue = part => part._$committedValue;
/**
* Removes a ChildPart from the DOM, including any of its content and markers.
*
* Note: The only difference between this and clearPart() is that this also
* removes the part's start node. This means that the ChildPart must own its
* start node, ie it must be a marker node specifically for this part and not an
* anchor from surrounding content.
*
* @param part The Part to remove
*/
const removePart = part => {
part._$clear();
part._$startNode.remove();
};
/**
* @license
* Copyright 2017 Google LLC
* SPDX-License-Identifier: BSD-3-Clause
*/
// Helper for generating a map of array item to its index over a subset
// of an array (used to lazily generate `newKeyToIndexMap` and
// `oldKeyToIndexMap`)
const generateMap = (list, start, end) => {
const map = new Map();
for (let i = start; i <= end; i++) {
map.set(list[i], i);
}
return map;
};
class RepeatDirective extends Directive {
constructor(partInfo) {
super(partInfo);
if (partInfo.type !== PartType.CHILD) {
throw new Error('repeat() can only be used in text expressions');
}
}
_getValuesAndKeys(items, keyFnOrTemplate, template) {
let keyFn;
if (template === undefined) {
template = keyFnOrTemplate;
} else if (keyFnOrTemplate !== undefined) {
keyFn = keyFnOrTemplate;
}
const keys = [];
const values = [];
let index = 0;
for (const item of items) {
keys[index] = keyFn ? keyFn(item, index) : index;
values[index] = template(item, index);
index++;
}
return {
values,
keys
};
}
render(items, keyFnOrTemplate, template) {
return this._getValuesAndKeys(items, keyFnOrTemplate, template).values;
}
update(containerPart, [items, keyFnOrTemplate, template]) {
// Old part & key lists are retrieved from the last update (which may
// be primed by hydration)
const oldParts = getCommittedValue(containerPart);
const {
values: newValues,
keys: newKeys
} = this._getValuesAndKeys(items, keyFnOrTemplate, template);
// We check that oldParts, the committed value, is an Array as an
// indicator that the previous value came from a repeat() call. If
// oldParts is not an Array then this is the first render and we return
// an array for lit-html's array handling to render, and remember the
// keys.
if (!Array.isArray(oldParts)) {
this._itemKeys = newKeys;
return newValues;
}
// In SSR hydration it's possible for oldParts to be an array but for us
// to not have item keys because the update() hasn't run yet. We set the
// keys to an empty array. This will cause all oldKey/newKey comparisons
// to fail and execution to fall to the last nested brach below which
// reuses the oldPart.
const oldKeys = this._itemKeys ??= [];
// New part list will be built up as we go (either reused from
// old parts or created for new keys in this update). This is
// saved in the above cache at the end of the update.
const newParts = [];
// Maps from key to index for current and previous update; these
// are generated lazily only when needed as a performance
// optimization, since they are only required for multiple
// non-contiguous changes in the list, which are less common.
let newKeyToIndexMap;
let oldKeyToIndexMap;
// Head and tail pointers to old parts and new values
let oldHead = 0;
let oldTail = oldParts.length - 1;
let newHead = 0;
let newTail = newValues.length - 1;
// Overview of O(n) reconciliation algorithm (general approach
// based on ideas found in ivi, vue, snabbdom, etc.):
//
// * We start with the list of old parts and new values (and
// arrays of their respective keys), head/tail pointers into
// each, and we build up the new list of parts by updating
// (and when needed, moving) old parts or creating new ones.
// The initial scenario might look like this (for brevity of
// the diagrams, the numbers in the array reflect keys
// associated with the old parts or new values, although keys
// and parts/values are actually stored in parallel arrays
// indexed using the same head/tail pointers):
//
// oldHead v v oldTail
// oldKeys: [0, 1, 2, 3, 4, 5, 6]
// newParts: [ , , , , , , ]
// newKeys: [0, 2, 1, 4, 3, 7, 6] <- reflects the user's new
// item order
// newHead ^ ^ newTail
//
// * Iterate old & new lists from both sides, updating,
// swapping, or removing parts at the head/tail locations
// until neither head nor tail can move.
//
// * Example below: keys at head pointers match, so update old
// part 0 in-place (no need to move it) and record part 0 in
// the `newParts` list. The last thing we do is advance the
// `oldHead` and `newHead` pointers (will be reflected in the
// next diagram).
//
// oldHead v v oldTail
// oldKeys: [0, 1, 2, 3, 4, 5, 6]
// newParts: [0, , , , , , ] <- heads matched: update 0
// newKeys: [0, 2, 1, 4, 3, 7, 6] and advance both oldHead
// & newHead
// newHead ^ ^ newTail
//
// * Example below: head pointers don't match, but tail
// pointers do, so update part 6 in place (no need to move
// it), and record part 6 in the `newParts` list. Last,
// advance the `oldTail` and `oldHead` pointers.
//
// oldHead v v oldTail
// oldKeys: [0, 1, 2, 3, 4, 5, 6]
// newParts: [0, , , , , , 6] <- tails matched: update 6
// newKeys: [0, 2, 1, 4, 3, 7, 6] and advance both oldTail
// & newTail
// newHead ^ ^ newTail
//
// * If neither head nor tail match; next check if one of the
// old head/tail items was removed. We first need to generate
// the reverse map of new keys to index (`newKeyToIndexMap`),
// which is done once lazily as a performance optimization,
// since we only hit this case if multiple non-contiguous
// changes were made. Note that for contiguous removal
// anywhere in the list, the head and tails would advance
// from either end and pass each other before we get to this
// case and removals would be handled in the final while loop
// without needing to generate the map.
//
// * Example below: The key at `oldTail` was removed (no longer
// in the `newKeyToIndexMap`), so remove that part from the
// DOM and advance just the `oldTail` pointer.
//
// oldHead v v oldTail
// oldKeys: [0, 1, 2, 3, 4, 5, 6]
// newParts: [0, , , , , , 6] <- 5 not in new map: remove
// newKeys: [0, 2, 1, 4, 3, 7, 6] 5 and advance oldTail
// newHead ^ ^ newTail
//
// * Once head and tail cannot move, any mismatches are due to
// either new or moved items; if a new key is in the previous
// "old key to old index" map, move the old part to the new
// location, otherwise create and insert a new part. Note
// that when moving an old part we null its position in the
// oldParts array if it lies between the head and tail so we
// know to skip it when the pointers get there.
//
// * Example below: neither head nor tail match, and neither
// were removed; so find the `newHead` key in the
// `oldKeyToIndexMap`, and move that old part's DOM into the
// next head position (before `oldParts[oldHead]`). Last,
// null the part in the `oldPart` array since it was
// somewhere in the remaining oldParts still to be scanned
// (between the head and tail pointers) so that we know to
// skip that old part on future iterations.
//
// oldHead v v oldTail
// oldKeys: [0, 1, -, 3, 4, 5, 6]
// newParts: [0, 2, , , , , 6] <- stuck: update & move 2
// newKeys: [0, 2, 1, 4, 3, 7, 6] into place and advance
// newHead
// newHead ^ ^ newTail
//
// * Note that for moves/insertions like the one above, a part
// inserted at the head pointer is inserted before the
// current `oldParts[oldHead]`, and a part inserted at the
// tail pointer is inserted before `newParts[newTail+1]`. The
// seeming asymmetry lies in the fact that new parts are
// moved into place outside in, so to the right of the head
// pointer are old parts, and to the right of the tail
// pointer are new parts.
//
// * We always restart back from the top of the algorithm,
// allowing matching and simple updates in place to
// continue...
//
// * Example below: the head pointers once again match, so
// simply update part 1 and record it in the `newParts`
// array. Last, advance both head pointers.
//
// oldHead v v oldTail
// oldKeys: [0, 1, -, 3, 4, 5, 6]
// newParts: [0, 2, 1, , , , 6] <- heads matched: update 1
// newKeys: [0, 2, 1, 4, 3, 7, 6] and advance both oldHead
// & newHead
// newHead ^ ^ newTail
//
// * As mentioned above, items that were moved as a result of
// being stuck (the final else clause in the code below) are
// marked with null, so we always advance old pointers over
// these so we're comparing the next actual old value on
// either end.
//
// * Example below: `oldHead` is null (already placed in
// newParts), so advance `oldHead`.
//
// oldHead v v oldTail
// oldKeys: [0, 1, -, 3, 4, 5, 6] <- old head already used:
// newParts: [0, 2, 1, , , , 6] advance oldHead
// newKeys: [0, 2, 1, 4, 3, 7, 6]
// newHead ^ ^ newTail
//
// * Note it's not critical to mark old parts as null when they
// are moved from head to tail or tail to head, since they
// will be outside the pointer range and never visited again.
//
// * Example below: Here the old tail key matches the new head
// key, so the part at the `oldTail` position and move its
// DOM to the new head position (before `oldParts[oldHead]`).
// Last, advance `oldTail` and `newHead` pointers.
//
// oldHead v v oldTail
// oldKeys: [0, 1, -, 3, 4, 5, 6]
// newParts: [0, 2, 1, 4, , , 6] <- old tail matches new
// newKeys: [0, 2, 1, 4, 3, 7, 6] head: update & move 4,
// advance oldTail & newHead
// newHead ^ ^ newTail
//
// * Example below: Old and new head keys match, so update the
// old head part in place, and advance the `oldHead` and
// `newHead` pointers.
//
// oldHead v oldTail
// oldKeys: [0, 1, -, 3, 4, 5, 6]
// newParts: [0, 2, 1, 4, 3, ,6] <- heads match: update 3
// newKeys: [0, 2, 1, 4, 3, 7, 6] and advance oldHead &
// newHead
// newHead ^ ^ newTail
//
// * Once the new or old pointers move past each other then all
// we have left is additions (if old list exhausted) or
// removals (if new list exhausted). Those are handled in the
// final while loops at the end.
//
// * Example below: `oldHead` exceeded `oldTail`, so we're done
// with the main loop. Create the remaining part and insert
// it at the new head position, and the update is complete.
//
// (oldHead > oldTail)
// oldKeys: [0, 1, -, 3, 4, 5, 6]
// newParts: [0, 2, 1, 4, 3, 7 ,6] <- create and insert 7
// newKeys: [0, 2, 1, 4, 3, 7, 6]
// newHead ^ newTail
//
// * Note that the order of the if/else clauses is not
// important to the algorithm, as long as the null checks
// come first (to ensure we're always working on valid old
// parts) and that the final else clause comes last (since
// that's where the expensive moves occur). The order of
// remaining clauses is just a simple guess at which cases
// will be most common.
//
// * Note, we could calculate the longest
// increasing subsequence (LIS) of old items in new position,
// and only move those not in the LIS set. However that costs
// O(nlogn) time and adds a bit more code, and only helps
// make rare types of mutations require fewer moves. The
// above handles removes, adds, reversal, swaps, and single
// moves of contiguous items in linear time, in the minimum
// number of moves. As the number of multiple moves where LIS
// might help approaches a random shuffle, the LIS
// optimization becomes less helpful, so it seems not worth
// the code at this point. Could reconsider if a compelling
// case arises.
while (oldHead <= oldTail && newHead <= newTail) {
if (oldParts[oldHead] === null) {
// `null` means old part at head has already been used
// below; skip
oldHead++;
} else if (oldParts[oldTail] === null) {
// `null` means old part at tail has already been used
// below; skip
oldTail--;
} else if (oldKeys[oldHead] === newKeys[newHead]) {
// Old head matches new head; update in place
newParts[newHead] = setChildPartValue(oldParts[oldHead], newValues[newHead]);
oldHead++;
newHead++;
} else if (oldKeys[oldTail] === newKeys[newTail]) {
// Old tail matches new tail; update in place
newParts[newTail] = setChildPartValue(oldParts[oldTail], newValues[newTail]);
oldTail--;
newTail--;
} else if (oldKeys[oldHead] === newKeys[newTail]) {
// Old head matches new tail; update and move to new tail
newParts[newTail] = setChildPartValue(oldParts[oldHead], newValues[newTail]);
insertPart(containerPart, newParts[newTail + 1], oldParts[oldHead]);
oldHead++;
newTail--;
} else if (oldKeys[oldTail] === newKeys[newHead]) {
// Old tail matches new head; update and move to new head
newParts[newHead] = setChildPartValue(oldParts[oldTail], newValues[newHead]);
insertPart(containerPart, oldParts[oldHead], oldParts[oldTail]);
oldTail--;
newHead++;
} else {
if (newKeyToIndexMap === undefined) {
// Lazily generate key-to-index maps, used for removals &
// moves below
newKeyToIndexMap = generateMap(newKeys, newHead, newTail);
oldKeyToIndexMap = generateMap(oldKeys, oldHead, oldTail);
}
if (!newKeyToIndexMap.has(oldKeys[oldHead])) {
// Old head is no longer in new list; remove
removePart(oldParts[oldHead]);
oldHead++;
} else if (!newKeyToIndexMap.has(oldKeys[oldTail])) {
// Old tail is no longer in new list; remove
removePart(oldParts[oldTail]);
oldTail--;
} else {
// Any mismatches at this point are due to additions or
// moves; see if we have an old part we can reuse and move
// into place
const oldIndex = oldKeyToIndexMap.get(newKeys[newHead]);
const oldPart = oldIndex !== undefined ? oldParts[oldIndex] : null;
if (oldPart === null) {
// No old part for this value; create a new one and
// insert it
const newPart = insertPart(containerPart, oldParts[oldHead]);
setChildPartValue(newPart, newValues[newHead]);
newParts[newHead] = newPart;
} else {
// Reuse old part
newParts[newHead] = setChildPartValue(oldPart, newValues[newHead]);
insertPart(containerPart, oldParts[oldHead], oldPart);
// This marks the old part as having been used, so that
// it will be skipped in the first two checks above
oldParts[oldIndex] = null;
}
newHead++;
}
}
}
// Add parts for any remaining new values
while (newHead <= newTail) {
// For all remaining additions, we insert before last new
// tail, since old pointers are no longer valid
const newPart = insertPart(containerPart, newParts[newTail + 1]);
setChildPartValue(newPart, newValues[newHead]);
newParts[newHead++] = newPart;
}
// Remove any remaining unused old parts
while (oldHead <= oldTail) {
const oldPart = oldParts[oldHead++];
if (oldPart !== null) {
removePart(oldPart);
}
}
// Save order of new parts for next round
this._itemKeys = newKeys;
// Directly set part value, bypassing it's dirty-checking
setCommittedValue(containerPart, newParts);
return noChange;
}
}
/**
* A directive that repeats a series of values (usually `TemplateResults`)
* generated from an iterable, and updates those items efficiently when the
* iterable changes based on user-provided `keys` associated with each item.
*
* Note that if a `keyFn` is provided, strict key-to-DOM mapping is maintained,
* meaning previous DOM for a given key is moved into the new position if
* needed, and DOM will never be reused with values for different keys (new DOM
* will always be created for new keys). This is generally the most efficient
* way to use `repeat` since it performs minimum unnecessary work for insertions
* and removals.
*
* The `keyFn` takes two parameters, the item and its index, and returns a unique key value.
*
* ```js
* html`
* <ol>
* ${repeat(this.items, (item) => item.id, (item, index) => {
* return html`<li>${index}: ${item.name}</li>`;
* })}
* </ol>
* `
* ```
*
* **Important**: If providing a `keyFn`, keys *must* be unique for all items in a
* given call to `repeat`. The behavior when two or more items have the same key
* is undefined.
*
* If no `keyFn` is provided, this directive will perform similar to mapping
* items to values, and DOM will be reused against potentially different items.
*/
const repeat = directive(RepeatDirective);
const resources = {
userHeader: "Account Information",
manageUserLink: "Manage account",
logoutUserLink: "Log out",
profileHeader: "Profile Information",
manageProfileLink: "Edit profile",
switchProfileLink: "Switch profile",
impersonatingVerb: "acting as",
notificationsHeader: "Notifications",
notificationsLabel: "Unread notifications",
noNotificationsLabel: "No unread notifications",
notificationsLink: "View all",
copyright: "© 1995 - {0} Zywave, Inc. All rights reserved.",
tosLink: "Terms and Conditions",
privacyLink: "Privacy Statement",
dmcaLink: "DMCA",
cookiesLink: "Cookie Usage",
contactLinkTitle: "Contact",
contactLink: "https://support.zywave.com/s/contactsupport",
loginUserText: "Log in",
featureFlagsHeader: "Early feature access"
};
/**
* @license
* Copyright 2018 Google LLC
* SPDX-License-Identifier: BSD-3-Clause
*/
class ClassMapDirective extends Directive {
constructor(partInfo) {
super(partInfo);
if (partInfo.type !== PartType.ATTRIBUTE || partInfo.name !== 'class' || partInfo.strings?.length > 2) {
throw new Error('`classMap()` can only be used in the `class` attribute ' + 'and must be the only part in the attribute.');
}
}
render(classInfo) {
// Add spaces to ensure separation from static classes
return ' ' + Object.keys(classInfo).filter(key => classInfo[key]).join(' ') + ' ';
}
update(part, [classInfo]) {
// Remember dynamic classes on the first render
if (this._previousClasses === undefined) {
this._previousClasses = new Set();
if (part.strings !== undefined) {
this._staticClasses = new Set(part.strings.join(' ').split(/\s/).filter(s => s !== ''));
}
for (const name in classInfo) {
if (classInfo[name] && !this._staticClasses?.has(name)) {
this._previousClasses.add(name);
}
}
return this.render(classInfo);
}
const classList = part.element.classList;
// Remove old classes that no longer apply
for (const name of this._previousClasses) {
if (!(name in classInfo)) {
classList.remove(name);
this._previousClasses.delete(name);
}
}
// Add or remove classes based on their classMap value
for (const name in classInfo) {
// We explicitly want a loose truthy check of `value` because it seems
// more convenient that '' and 0 are skipped.
const value = !!classInfo[name];
if (value !== this._previousClasses.has(name) && !this._staticClasses?.has(name)) {
if (value) {
classList.add(name);
this._previousClasses.add(name);
} else {
classList.remove(name);
this._previousClasses.delete(name);
}
}
}
return noChange;
}
}
/**
* A directive that applies dynamic CSS classes.
*
* This must be used in the `class` attribute and must be the only part used in
* the attribute. It takes each property in the `classInfo` argument and adds
* the property name to the element's `classList` if the property value is
* truthy; if the property value is falsy, the property name is removed from
* the element's `class`.
*
* For example `{foo: bar}` applies the class `foo` if the value of `bar` is
* truthy.
*
* @param classInfo
*/
const classMap = directive(ClassMapDirective);
/**
* @license
* Copyright 2017 Google LLC
* SPDX-License-Identifier: BSD-3-Clause
*/
const HTML_RESULT = 1;
class UnsafeHTMLDirective extends Directive {
constructor(partInfo) {
super(partInfo);
this._value = nothing;
if (partInfo.type !== PartType.CHILD) {
throw new Error(`${this.constructor.directiveName}() can only be used in child bindings`);
}
}
render(value) {
if (value === nothing || value == null) {
this._templateResult = undefined;
return this._value = value;
}
if (value === noChange) {
return value;
}
if (typeof value != 'string') {
throw new Error(`${this.constructor.directiveName}() called with a non-string value`);
}
if (value === this._value) {
return this._templateResult;
}
this._value = value;
const strings = [value];
// eslint-disable-next-line @typescript-eslint/no-explicit-any
strings.raw = strings;
// WARNING: impersonating a TemplateResult like this is extremely
// dangerous. Third-party directives should not do this.
return this._templateResult = {
// Cast to a known set of integers that satisfy ResultType so that we
// don't have to export ResultType and possibly encourage this pattern.
// This property needs to remain unminified.
['_$litType$']: this.constructor.resultType,
strings,
values: []
};
}
}
UnsafeHTMLDirective.directiveName = 'unsafeHTML';
UnsafeHTMLDirective.resultType = HTML_RESULT;
/**
* Renders the result as HTML, rather than text.
*
* The values `undefined`, `null`, and `nothing`, will all result in no content
* (empty string) being rendered.
*
* Note, this is unsafe to use with any user-provided input that hasn't been
* sanitized or escaped, as it may lead to cross-site-scripting
* vulnerabilities.
*/
const unsafeHTML = directive(UnsafeHTMLDirective);
const style$2 = css`:host{display:contents}:host *:not(:defined){display:none}.theme-logo{width:auto;height:100%}zui-shell{--zui-shell-primary-theme: var(--zywave-shell-primary-color, var(--zui-blue-500))}.legalese{display:block}.notify-parent{position:fixed;top:var(--zui-shell-topbar-global-height);left:0;width:calc(100vw - 1.25rem);visibility:hidden;margin-top:1.25rem}.profile-types-list,.profile-type-identifier{list-style:none;padding:0}*:not(:defined){display:none;width:0;height:0;visibility:hidden}.feature-flags-header{padding:.625rem .9375rem;background:var(--zui-gray-50);font-weight:700}slot[name=experimentalassistant]{display:none}@media(min-width: 45em){slot[name=experimentalassistant]{display:contents}}::slotted([slot=experimentalassistant]){min-width:0}`;
function sleep(timeoutMs, reject = false) {
return new Promise((resolve, rej) => {
setTimeout(() => reject ? rej(new Error("Sleep rejected")) : resolve(), timeoutMs);
});
}
/**
* @license
* Copyright 2018 Google LLC
* SPDX-License-Identifier: BSD-3-Clause
*/
const important = 'important';
// The leading space is important
const importantFlag = ' !' + important;
// How many characters to remove from a value, as a negative number
const flagTrim = 0 - importantFlag.length;
class StyleMapDirective extends Directive {
constructor(partInfo) {
super(partInfo);
if (partInfo.type !== PartType.ATTRIBUTE || partInfo.name !== 'style' || partInfo.strings?.length > 2) {
throw new Error('The `styleMap` directive must be used in the `style` attribute ' + 'and must be the only part in the attribute.');
}
}
render(styleInfo) {
return Object.keys(styleInfo).reduce((style, prop) => {
const value = styleInfo[prop];
if (value == null) {
return style;
}
// Convert property names from camel-case to dash-case, i.e.:
// `backgroundColor` -> `background-color`
// Vendor-prefixed names need an extra `-` appended to front:
// `webkitAppearance` -> `-webkit-appearance`
// Exception is any property name containing a dash, including
// custom properties; we assume these are already dash-cased i.e.:
// `--my-button-color` --> `--my-button-color`
prop = prop.includes('-') ? prop : prop.replace(/(?:^(webkit|moz|ms|o)|)(?=[A-Z])/g, '-$&').toLowerCase();
return style + `${prop}:${value};`;
}, '');
}
update(part, [styleInfo]) {
const {
style
} = part.element;
if (this._previousStyleProperties === undefined) {
this._previousStyleProperties = new Set(Object.keys(styleInfo));
return this.render(styleInfo);
}
// Remove old properties that no longer exist in styleInfo
for (const name of this._previousStyleProperties) {
// If the name isn't in styleInfo or it's null/undefined
if (styleInfo[name] == null) {
this._previousStyleProperties.delete(name);
if (name.includes('-')) {
style.removeProperty(name);
} else {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
style[name] = null;
}
}
}
// Add or update properties
for (const name in styleInfo) {
const value = styleInfo[name];
if (value != null) {
this._previousStyleProperties.add(name);
const isImportant = typeof value === 'string' && value.endsWith(importantFlag);
if (name.includes('-') || isImportant) {
style.setProperty(name, isImportant ? value.slice(0, flagTrim) : value, isImportant ? important : '');
} else {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
style[name] = value;
}
}
}
return noChange;
}
}
/**
* A directive that applies CSS properties to an element.
*
* `styleMap` can only be used in the `style` attribute and must be the only
* expression in the attribute. It takes the property names in the
* {@link StyleInfo styleInfo} object and adds the properties to the inline
* style of the element.
*
* Property names with dashes (`-`) are assumed to be valid CSS
* property names and set on the element's style object using `setProperty()`.
* Names without dashes are assumed to be camelCased JavaScript property names
* and set on the element's style object using property assignment, allowing the
* style object to translate JavaScript-style names to CSS property names.
*
* For example `styleMap({backgroundColor: 'red', 'border-top': '5px', '--size':
* '0'})` sets the `background-color`, `border-top` and `--size` properties.
*
* @param styleInfo
* @see {@link https://lit.dev/docs/templates/directives/#stylemap styleMap code samples on Lit.dev}
*/
const styleMap = directive(StyleMapDirective);
const style$1 = css`@supports(scrollbar-width: thin){.options-list{scrollbar-color:var(--zui-gray-400) var(--zui-gray-50);scrollbar-width:thin}}@supports not (scrollbar-width: thin){.options-list::-webkit-scrollbar{width:7px;height:7px;background-color:var(--zui-gray-50)}.options-list::-webkit-scrollbar-thumb{background-color:var(--zui-gray-400);border-radius:10px}}:host{display:block}.wrapper{position:relative;width:100%}@media(min-width: 45em){.wrapper{width:auto}}.input-wrapper{position:relative;background:#fff;border-radius:.25rem}.input-wrapper zui-icon{--zui-icon-size: 1.0625rem;position:absolute;top:calc(50% - 0.53125rem);left:.8125rem;z-index:1;fill:var(--zui-gray-400)}.input-wrapper input{position:relative;display:inline-block;width:100%;min-width:0;min-height:2.625rem;z-index:2;vertical-align:middle;padding:0 .625rem 0 2.5rem;background-color:rgba(0,0,0,0);border:.0625rem solid var(--zui-gray-200);border-radius:.25rem;font:inherit;color:inherit;transition:border 100ms ease-in-out,box-shadow 100ms ease-in-out,width 250ms ease-in-out;box-sizing:border-box;appearance:textfield}@media(min-width: 45em){.input-wrapper input{min-width:69ch}}.input-wrapper input::placeholder{color:var(--zui-gray-300)}.input-wrapper input::-webkit-input-placeholder{color:var(--zui-gray-300)}.input-wrapper input::-moz-placeholder{opacity:1;color:var(--zui-gray-300)}.input-wrapper input:-moz-placeholder{opacity:1;color:var(--zui-gray-300)}.input-wrapper input:-ms-input-placeholder{color:var(--zui-gray-300)}.input-wrapper input::-ms-clear{display:none;width:0;height:0}.input-wrapper input::-ms-reveal{display:none;width:0;height:0}.input-wrapper input:not(output):-moz-ui-invalid{box-shadow:none}.input-wrapper input::-webkit-search-decoration,.input-wrapper input::-webkit-search-cancel-button,.input-wrapper input::-webkit-search-results-button,.input-wrapper input::-webkit-search-results-decoration{display:none}.input-wrapper input:hover{border-color:var(--zui-gray-300)}.input-wrapper input:focus{border-color:var(--zui-blue-400);box-shadow:0 0 0 .0625rem var(--zui-blue-400);outline:none}.input-wrapper input[disabled]{background-color:var(--zui-gray-100);cursor:not-allowed;color:var(--zui-gray)}.input-wrapper input[disabled]:hover{border:.0625rem solid var(--zui-gray-200)}.input-wrapper input[readonly]{background:rgba(0,0,0,0);border:0;outline:none;color:var(--zui-gray);pointer-events:none}.options-list{width:calc(100% - 2.5rem);max-width:69ch;max-height:80vh;z-index:1;overflow-y:auto;padding:1.5625rem 0 .9375rem;background-color:#fff;border:0;border-radius:.25rem;box-shadow:0 .0625rem .1875rem .0625rem rgba(0,0,0,.16);color:var(--zui-gray-700);transition:max-height .2s,padding .2s,box-shadow .2s,transform .3s;scroll-behavior:smooth}@media(min-width: 45em){.options-list{width:69ch;max-width:none}}.options-list:popover-open{position:fixed;inset:unset}.options-list .options-group{display:block}.options-list .group-label{display:flex;justify-content:space-between;align-items:center;padding:1.25rem 1.5625rem .75rem;font-size:.75rem;font-weight:700;text-transform:uppercase}.options-list .group-label:first-child{padding-top:0}.options-list .option{display:flex;height:2.6rem;align-items:center;padding:.5rem 1.5625rem;background-color:rgba(0,0,0,0);cursor:pointer;color:var(--zui-gray-600);transition:background-color 100ms ease-in-out;text-decoration:none}.options-list .option:hover{background-color:var(--zui-gray-50)}.options-list .option:focus{background-color:var(--zui-gray-50);outline:none}.options-list .option zui-icon{--zui-icon-size: 1.25rem;align-self:flex-start;margin-top:.5ch;margin-right:.9375rem}.options-list .description{width:calc(100% - 1.875rem);align-self:stretch}.options-list .first-line{font-weight:700}.options-list .second-line{font-size:.75rem;color:var(--zui-gray-500)}.options-list .first-line,.options-list .second-line{display:block;width:100%;overflow:hidden;white-space:nowrap;text-overflow:ellipsis}.options-list .no-results{padding:.5rem 1.5625rem;font-style:italic;color:var(--zui-gray-800)}.see-more{display:block;padding:0 1.5625rem}.see-more a{display:inline-block;vertical-align:baseline;margin:0;padding:.5rem 0 .625rem;font-size:.75rem;font-weight:600;cursor:pointer;color:var(--zui-blue);text-decoration:none}.see-more a:hover{color:var(--zui-blue-400)}.see-more a:focus{outline:.0625rem solid var(--zui-blue);outline-offset:.25rem;text-decoration:none}.see-more a:active{outline:none;color:var(--zui-blue-600);text-decoration:underline}.see-more+.group-label,.no-results+.group-label{margin-top:.625rem;border-top:1px solid var(--zui-gray-100)}.skeleton{position:relative;display:inline-block;height:1.6em;overflow:hidden;background-color:var(--zui-gray-100);border-radius:.1875rem}.skeleton::after{position:absolute;top:0;right:0;bottom:0;left:0;content:"";background-image:linear-gradient(90deg, rgba(255, 255, 255, 0) 0, rgba(255, 255, 255, 0.2) 20%, rgba(255, 255, 255, 0.5) 60%, rgba(255, 255, 255, 0));animation:shimmer 2s infinite;transform:translateX(-100%)}@keyframes shimmer{100%{transform:translateX(100%)}}zui-icon.skeleton{width:1.25rem;height:1.25rem;border-radius:50%}.skeleton.group-label{width:10ch;margin:0 1.5625rem .75rem;padding:0}.skeleton.first-line,.skeleton.second-line{height:1.6em}.skeleton.first-line{margin-bottom:.15625rem}.skeleton.second-line{width:70%;margin-top:.15625rem}.skeleton.see-more{width:8ch;margin-left:1.5625rem;padding:0}`;
var __decorate$2 = undefined && undefined.__decorate || function (decorators, target, key, desc) {
var c = arguments.length,
r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc,
d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
/**
* @element 'zywave-shell-omnisearch'
*
* @slot - The default slot
*/
class ZywaveShellOmnisearchElement extends ZywaveBaseElement {
#searchResult;
#value;
#timeoutId;
#polyfillReady;
#controller;
#isActive;
#isLoading;
#initialListTopValue;
#handleTopbarHeightChange;
get value() {
return this.#value;
}
set value(val) {
const oldVal = this.#value;
this.#value = val;
this.requestUpdate("value", oldVal);
}
static get styles() {
return [style$1];
}
get #query() {
return this._input?.value;
}
constructor() {
super();
this.#value = "";
this.#isActive = false;
this.#isLoading = false;
/**
* Default placeholder (ghost) text
*/
this.placeholder = "Search..."; // temporary default
this.#handleTopbarHeightChange = e => {
this.#onTopbarHeightChange(e);
};
}
connectedCallback() {
super.connectedCallback();
window.addEventListener("topbarheightchange", this.#handleTopbarHeightChange);
}
disconnectedCallback() {
super.disconnectedCallback();
window.removeEventListener("topbarheightchange", this.#handleTopbarHeightChange);
}
async firstUpdated(changedProps) {
super.firstUpdated(changedProps);
this.#polyfillReady = window.zywave?.zapi?._popover?.requiresPolyfill ? window.zywave.zapi._popover.loadPolyfill() : Promise.resolve();
await this.#polyfillReady;
// one final update to get the input/list in sync
this.requestUpdate();
this.#initialListTopValue = this._input.getBoundingClientRect().top;
}
render() {
return html`
${this.#renderPolyfillStyles()}
<div class="wrapper">${this.#renderInput()} ${this.#renderOptions()}</div>
<slot></slot>
`;
}
#renderPolyfillStyles() {
return window.zywave?.zapi?._popover?.requiresPolyfill ? html`<link href="${window.zywave.zapi._popover.cssUri}" rel="stylesheet" />` : nothing;
}
#renderInput() {
return html`
<div class="input-wrapper">
<zui-icon icon="zui-search"></zui-icon>
<input
id="search"
aria-label="Search"
type="search"
.value="${this.value || ""}"
placeholder="${this.placeholder ?? ""}"
@input="${() => this.#onSearchInputUpdate()}"
@keyup="${e => this.#onInputKeyUp(e)}"
popovertarget="list"
autocomplete="nope"
/>
</div>
`;
}
#renderOptions() {
if (!this._input) {
return nothing;
}
const inputPosition = this._input.getBoundingClientRect();
const inlineStyles = {};
if (window.zywave?.zapi?._popover?.requiresPolyfill) {
inlineStyles.position = "fixed";
inlineStyles.inset = "unset";
}
inlineStyles.top = `${inputPosition.height}px`;
// if for whatever reason the input position changes,
// such as toggling mobile search into and out of view,
// we need to update the top value to properly position the list
const newTopValue = this._input.getBoundingClientRect().top;
if (this.#initialListTopValue !== newTopValue) {
this.#initialListTopValue = newTopValue;
}
if (!window.zywave?.zapi?._popover?.requiresPolyfill) {
inlineStyles.top = `${this.#initialListTopValue + inputPosition.height}px`;
inlineStyles.left = `${inputPosition.left}px`;
}
// we make a deep clone in case the user resumes querying while loading results
const results = structuredClone(this.#searchResult ?? []);
const renderResultItem = item => {
return html`<a href="${item.href}" class="option" @click="${() => this.#onResultItemClick(item)}"
><zui-icon icon="${item.icon ?? "zui-rocket"}" class="small"></zui-icon>
<div class="description">
<div class="first-line" title="${item.title}">${item.title}</div>
<div class="second-line" title="${item.subtitle}">${item.subtitle}</div>
</div></a
></a>`;
};
const renderResults = list => {
return html`<div class="group-label">${list.entityName}</div>
${list.items.length > 0 ? repeat(list.items, renderResultItem) : html`<div class="no-results">No results found.</div>`}
${list.items.length > 0 ? html`<div class="see-more">
<a href="${list.items[0].searchPageUri}" @click="${() => this.#onSeeMoreClick(list)}">See all results</a>
</div>` : nothing}`;
};
return html`
<div id="list" class="options-list" style="${styleMap(inlineStyles)}" popover @toggle="${this.#onPopoverToggle}">
${this.#isLoading ? this.#renderSkeleton() : repeat(results, renderResults)}
<slot></slot>
</div>
`;
}
#multiplySkeletonOptions() {
const skeletonOption = () => {
return html`<div class="option">
<zui-icon icon="" class="small skeleton"></zui-icon>
<div class="description">
<div class="first-line skeleton"> </div>
<div class="second-line skeleton"> </div>
</div>
</div>`;
};
return html`${repeat([1, 2, 3, 4, 5], skeletonOption)}`;
}
#renderSkeleton() {
return html`<div class="group-label skeleton"> </div>
${this.#multiplySkeletonOptions()}`;
}
async #loadSearchResults(query, signal) {
const start = performance.now();
const url = new URL("shell/v2.0/search", this.apiUrl);
if (query) {
url.searchParams.set("query", query);
}
url.searchParams.set("host", window.location.hostname);
const resp = await this._apiClient.fetch(url, {
signal
});
if (resp instanceof Response && resp.status === 200 && !signal.aborted) {
const end = performance.now();
this.#searchResult = await resp.json();
const eventDetails = {
duration: end - start,
query
};
for (const entitySearchResult of this.#searchResult) {
eventDetails[`${entitySearchResult.entityName} - Count`] = entitySearchResult.items?.length ?? 0;
eventDetails[`${entitySearchResult.entityName} - Timed Out`] = entitySearchResult.timedOut;
eventDetails[`${entitySearchResult.entityName} - Elapsed Milliseconds`] = entitySearchResult.elapsedMs;
}
this.dispatchEvent(new CustomEvent("search", {
detail: eventDetails,
bubbles: false
}));
this.requestUpdate();
}
}
#onSearchInputUpdate() {
this.#showPopover();
this.#isLoading = true;
this.requestUpdate();
if (this.#timeoutId) {
window.clearTimeout(this.#timeoutId);
this.#controller?.abort();
}
this.#controller = new AbortController();
const signal = this.#controller.signal;
const task = async () => {
if (!signal.aborted) {
await this.#loadSearchResults(this.#query, this.#controller.signal);
this.#isLoading = false;
this.requestUpdate();
}
};
this.#timeoutId = window.setTimeout(() => {
task();
}, 300);
}
#onPopoverToggle(event) {
this.#isActive = event.oldState === "closed" && event.newState === "open";
if (!this.#isActive) {
this.#onDismiss();
}
}
#showPopover() {
const show = () => {
this._list?.showPopover?.();
this._input?.focus();
this.#isActive = true;
};
if (this.#polyfillReady) {
this.#polyfillReady?.then(show);
} else {
show();
}
}
#onResultItemClick(item) {
const itemDetails = {
entityName: item.entityName,
title: item.title,
subtitle: item.subtitle,
href: item.href,
query: this.#query
};
this.dispatchEvent(new CustomEvent("clickresult", {
detail: itemDetails,
bubbles: false
}));
}
#onSeeMoreClick(list) {
const listDetails = {
entityName: list.entityName,
searchPageUri: list.items[0].searchPageUri,
query: this.#query
};
this.dispatchEvent(new CustomEvent("clickse