@govtechsg/sgds-web-component 3.4.0-rc.2 → 3.4.0-rc.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/index.umd.js CHANGED
@@ -1234,7 +1234,7 @@
1234
1234
  };
1235
1235
  issueWarning$4('dev-mode', `Lit is in dev mode. Not recommended for production!`);
1236
1236
  }
1237
- const wrap = global$2.ShadyDOM?.inUse &&
1237
+ const wrap$1 = global$2.ShadyDOM?.inUse &&
1238
1238
  global$2.ShadyDOM?.noPatch === true
1239
1239
  ? global$2.ShadyDOM.wrap
1240
1240
  : (node) => node;
@@ -1286,7 +1286,7 @@
1286
1286
  const nodeMarker = `<${markerMatch}>`;
1287
1287
  const d = document;
1288
1288
  // Creates a dynamic marker. We never have to search for these in the DOM.
1289
- const createMarker = () => d.createComment('');
1289
+ const createMarker$1 = () => d.createComment('');
1290
1290
  const isPrimitive = (value) => value === null || (typeof value != 'object' && typeof value != 'function');
1291
1291
  const isArray = Array.isArray;
1292
1292
  const isIterable = (value) => isArray(value) ||
@@ -1718,7 +1718,7 @@
1718
1718
  // normalized when cloning in IE (could simplify when
1719
1719
  // IE is no longer supported)
1720
1720
  for (let i = 0; i < lastIndex; i++) {
1721
- node.append(strings[i], createMarker());
1721
+ node.append(strings[i], createMarker$1());
1722
1722
  // Walk past the marker node we just added
1723
1723
  walker.nextNode();
1724
1724
  parts.push({ type: CHILD_PART, index: ++nodeIndex });
@@ -1726,7 +1726,7 @@
1726
1726
  // Note because this marker is added after the walker's current
1727
1727
  // node, it will be walked to in the outer loop (and ignored), so
1728
1728
  // we don't need to adjust nodeIndex here
1729
- node.append(strings[lastIndex], createMarker());
1729
+ node.append(strings[lastIndex], createMarker$1());
1730
1730
  }
1731
1731
  }
1732
1732
  }
@@ -1856,7 +1856,7 @@
1856
1856
  if (nodeIndex === templatePart.index) {
1857
1857
  let part;
1858
1858
  if (templatePart.type === CHILD_PART) {
1859
- part = new ChildPart(node, node.nextSibling, this, options);
1859
+ part = new ChildPart$1(node, node.nextSibling, this, options);
1860
1860
  }
1861
1861
  else if (templatePart.type === ATTRIBUTE_PART) {
1862
1862
  part = new templatePart.ctor(node, templatePart.name, templatePart.strings, this, options);
@@ -1906,7 +1906,7 @@
1906
1906
  }
1907
1907
  }
1908
1908
  }
1909
- class ChildPart {
1909
+ class ChildPart$1 {
1910
1910
  // See comment in Disconnectable interface for why this is a getter
1911
1911
  get _$isConnected() {
1912
1912
  // ChildParts that are not at the root should always be created with a
@@ -1953,7 +1953,7 @@
1953
1953
  * consists of all child nodes of `.parentNode`.
1954
1954
  */
1955
1955
  get parentNode() {
1956
- let parentNode = wrap(this._$startNode).parentNode;
1956
+ let parentNode = wrap$1(this._$startNode).parentNode;
1957
1957
  const parent = this._$parent;
1958
1958
  if (parent !== undefined &&
1959
1959
  parentNode?.nodeType === 11 /* Node.DOCUMENT_FRAGMENT */) {
@@ -2027,7 +2027,7 @@
2027
2027
  }
2028
2028
  }
2029
2029
  _insert(node) {
2030
- return wrap(wrap(this._$startNode).parentNode).insertBefore(node, this._$endNode);
2030
+ return wrap$1(wrap$1(this._$startNode).parentNode).insertBefore(node, this._$endNode);
2031
2031
  }
2032
2032
  _commitNode(value) {
2033
2033
  if (this._$committedValue !== value) {
@@ -2074,7 +2074,7 @@
2074
2074
  // Text node. We can now just replace the text content (.data) of the node.
2075
2075
  if (this._$committedValue !== nothing &&
2076
2076
  isPrimitive(this._$committedValue)) {
2077
- const node = wrap(this._$startNode).nextSibling;
2077
+ const node = wrap$1(this._$startNode).nextSibling;
2078
2078
  {
2079
2079
  if (this._textSanitizer === undefined) {
2080
2080
  this._textSanitizer = createSanitizer(node, 'data', 'property');
@@ -2200,7 +2200,7 @@
2200
2200
  // TODO (justinfagnani): test perf impact of always creating two parts
2201
2201
  // instead of sharing parts between nodes
2202
2202
  // https://github.com/lit/lit/issues/1266
2203
- itemParts.push((itemPart = new ChildPart(this._insert(createMarker()), this._insert(createMarker()), this, this.options)));
2203
+ itemParts.push((itemPart = new ChildPart$1(this._insert(createMarker$1()), this._insert(createMarker$1()), this, this.options)));
2204
2204
  }
2205
2205
  else {
2206
2206
  // Reuse an existing part
@@ -2211,7 +2211,7 @@
2211
2211
  }
2212
2212
  if (partIndex < itemParts.length) {
2213
2213
  // itemParts always have end nodes
2214
- this._$clear(itemPart && wrap(itemPart._$endNode).nextSibling, partIndex);
2214
+ this._$clear(itemPart && wrap$1(itemPart._$endNode).nextSibling, partIndex);
2215
2215
  // Truncate the parts array so _value reflects the current state
2216
2216
  itemParts.length = partIndex;
2217
2217
  }
@@ -2227,11 +2227,11 @@
2227
2227
  *
2228
2228
  * @internal
2229
2229
  */
2230
- _$clear(start = wrap(this._$startNode).nextSibling, from) {
2230
+ _$clear(start = wrap$1(this._$startNode).nextSibling, from) {
2231
2231
  this._$notifyConnectionChanged?.(false, true, from);
2232
2232
  while (start && start !== this._$endNode) {
2233
- const n = wrap(start).nextSibling;
2234
- wrap(start).remove();
2233
+ const n = wrap$1(start).nextSibling;
2234
+ wrap$1(start).remove();
2235
2235
  start = n;
2236
2236
  }
2237
2237
  }
@@ -2349,7 +2349,7 @@
2349
2349
  /** @internal */
2350
2350
  _commitValue(value) {
2351
2351
  if (value === nothing) {
2352
- wrap(this.element).removeAttribute(this.name);
2352
+ wrap$1(this.element).removeAttribute(this.name);
2353
2353
  }
2354
2354
  else {
2355
2355
  {
@@ -2366,7 +2366,7 @@
2366
2366
  value,
2367
2367
  options: this.options,
2368
2368
  });
2369
- wrap(this.element).setAttribute(this.name, (value ?? ''));
2369
+ wrap$1(this.element).setAttribute(this.name, (value ?? ''));
2370
2370
  }
2371
2371
  }
2372
2372
  }
@@ -2410,7 +2410,7 @@
2410
2410
  value: !!(value && value !== nothing),
2411
2411
  options: this.options,
2412
2412
  });
2413
- wrap(this.element).toggleAttribute(this.name, !!value && value !== nothing);
2413
+ wrap$1(this.element).toggleAttribute(this.name, !!value && value !== nothing);
2414
2414
  }
2415
2415
  }
2416
2416
  class EventPart extends AttributePart {
@@ -2501,10 +2501,46 @@
2501
2501
  resolveDirective(this, value);
2502
2502
  }
2503
2503
  }
2504
+ /**
2505
+ * END USERS SHOULD NOT RELY ON THIS OBJECT.
2506
+ *
2507
+ * Private exports for use by other Lit packages, not intended for use by
2508
+ * external users.
2509
+ *
2510
+ * We currently do not make a mangled rollup build of the lit-ssr code. In order
2511
+ * to keep a number of (otherwise private) top-level exports mangled in the
2512
+ * client side code, we export a _$LH object containing those members (or
2513
+ * helper methods for accessing private fields of those members), and then
2514
+ * re-export them for use in lit-ssr. This keeps lit-ssr agnostic to whether the
2515
+ * client-side code is being used in `dev` mode or `prod` mode.
2516
+ *
2517
+ * This has a unique name, to disambiguate it from private exports in
2518
+ * lit-element, which re-exports all of lit-html.
2519
+ *
2520
+ * @private
2521
+ */
2522
+ const _$LH = {
2523
+ // Used in lit-ssr
2524
+ _boundAttributeSuffix: boundAttributeSuffix,
2525
+ _marker: marker,
2526
+ _markerMatch: markerMatch,
2527
+ _HTML_RESULT: HTML_RESULT,
2528
+ _getTemplateHtml: getTemplateHtml,
2529
+ // Used in tests and private-ssr-support
2530
+ _TemplateInstance: TemplateInstance,
2531
+ _isIterable: isIterable,
2532
+ _resolveDirective: resolveDirective,
2533
+ _ChildPart: ChildPart$1,
2534
+ _AttributePart: AttributePart,
2535
+ _BooleanAttributePart: BooleanAttributePart,
2536
+ _EventPart: EventPart,
2537
+ _PropertyPart: PropertyPart,
2538
+ _ElementPart: ElementPart,
2539
+ };
2504
2540
  // Apply polyfills if available
2505
2541
  const polyfillSupport$2 = global$2.litHtmlPolyfillSupportDevMode
2506
2542
  ;
2507
- polyfillSupport$2?.(Template, ChildPart);
2543
+ polyfillSupport$2?.(Template, ChildPart$1);
2508
2544
  // IMPORTANT: do not change the property name or the assignment expression.
2509
2545
  // This line will be used in regexes to search for lit-html usage.
2510
2546
  (global$2.litHtmlVersions ??= []).push('3.2.0');
@@ -2563,7 +2599,7 @@
2563
2599
  const endNode = options?.renderBefore ?? null;
2564
2600
  // This property needs to remain unminified.
2565
2601
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
2566
- partOwnerNode['_$litPart$'] = part = new ChildPart(container.insertBefore(createMarker(), endNode), endNode, undefined, options ?? {});
2602
+ partOwnerNode['_$litPart$'] = part = new ChildPart$1(container.insertBefore(createMarker$1(), endNode), endNode, undefined, options ?? {});
2567
2603
  }
2568
2604
  part._$setValue(value);
2569
2605
  debugLogEvent$1 &&
@@ -11479,7 +11515,8 @@
11479
11515
  * Copyright 2020 Google LLC
11480
11516
  * SPDX-License-Identifier: BSD-3-Clause
11481
11517
  */
11482
- window.ShadyDOM?.inUse &&
11518
+ const { _ChildPart: ChildPart } = _$LH;
11519
+ const wrap = window.ShadyDOM?.inUse &&
11483
11520
  window.ShadyDOM?.noPatch === true
11484
11521
  ? window.ShadyDOM.wrap
11485
11522
  : (node) => node;
@@ -11492,6 +11529,79 @@
11492
11529
  * parts do not.
11493
11530
  */
11494
11531
  const isSingleExpression = (part) => part.strings === undefined;
11532
+ const createMarker = () => document.createComment('');
11533
+ /**
11534
+ * Inserts a ChildPart into the given container ChildPart's DOM, either at the
11535
+ * end of the container ChildPart, or before the optional `refPart`.
11536
+ *
11537
+ * This does not add the part to the containerPart's committed value. That must
11538
+ * be done by callers.
11539
+ *
11540
+ * @param containerPart Part within which to add the new ChildPart
11541
+ * @param refPart Part before which to add the new ChildPart; when omitted the
11542
+ * part added to the end of the `containerPart`
11543
+ * @param part Part to insert, or undefined to create a new part
11544
+ */
11545
+ const insertPart = (containerPart, refPart, part) => {
11546
+ const container = wrap(containerPart._$startNode).parentNode;
11547
+ const refNode = refPart === undefined ? containerPart._$endNode : refPart._$startNode;
11548
+ if (part === undefined) {
11549
+ const startNode = wrap(container).insertBefore(createMarker(), refNode);
11550
+ const endNode = wrap(container).insertBefore(createMarker(), refNode);
11551
+ part = new ChildPart(startNode, endNode, containerPart, containerPart.options);
11552
+ }
11553
+ else {
11554
+ const endNode = wrap(part._$endNode).nextSibling;
11555
+ const oldParent = part._$parent;
11556
+ const parentChanged = oldParent !== containerPart;
11557
+ if (parentChanged) {
11558
+ part._$reparentDisconnectables?.(containerPart);
11559
+ // Note that although `_$reparentDisconnectables` updates the part's
11560
+ // `_$parent` reference after unlinking from its current parent, that
11561
+ // method only exists if Disconnectables are present, so we need to
11562
+ // unconditionally set it here
11563
+ part._$parent = containerPart;
11564
+ // Since the _$isConnected getter is somewhat costly, only
11565
+ // read it once we know the subtree has directives that need
11566
+ // to be notified
11567
+ let newConnectionState;
11568
+ if (part._$notifyConnectionChanged !== undefined &&
11569
+ (newConnectionState = containerPart._$isConnected) !==
11570
+ oldParent._$isConnected) {
11571
+ part._$notifyConnectionChanged(newConnectionState);
11572
+ }
11573
+ }
11574
+ if (endNode !== refNode || parentChanged) {
11575
+ let start = part._$startNode;
11576
+ while (start !== endNode) {
11577
+ const n = wrap(start).nextSibling;
11578
+ wrap(container).insertBefore(start, refNode);
11579
+ start = n;
11580
+ }
11581
+ }
11582
+ }
11583
+ return part;
11584
+ };
11585
+ /**
11586
+ * Sets the value of a Part.
11587
+ *
11588
+ * Note that this should only be used to set/update the value of user-created
11589
+ * parts (i.e. those created using `insertPart`); it should not be used
11590
+ * by directives to set the value of the directive's container part. Directives
11591
+ * should return a value from `update`/`render` to update their part state.
11592
+ *
11593
+ * For directives that require setting their part value asynchronously, they
11594
+ * should extend `AsyncDirective` and call `this.setValue()`.
11595
+ *
11596
+ * @param part Part to set
11597
+ * @param value Value to set
11598
+ * @param index For `AttributePart`s, the index to set
11599
+ * @param directiveParent Used internally; should not be set by user
11600
+ */
11601
+ const setChildPartValue = (part, value, directiveParent = part) => {
11602
+ part._$setValue(value, directiveParent);
11603
+ return part;
11604
+ };
11495
11605
  // A sentinel value that can never appear as a part value except when set by
11496
11606
  // live(). Used to force a dirty-check to fail and cause a re-render.
11497
11607
  const RESET_VALUE = {};
@@ -11507,6 +11617,36 @@
11507
11617
  * @param value
11508
11618
  */
11509
11619
  const setCommittedValue = (part, value = RESET_VALUE) => (part._$committedValue = value);
11620
+ /**
11621
+ * Returns the committed value of a ChildPart.
11622
+ *
11623
+ * The committed value is used for change detection and efficient updates of
11624
+ * the part. It can differ from the value set by the template or directive in
11625
+ * cases where the template value is transformed before being committed.
11626
+ *
11627
+ * - `TemplateResult`s are committed as a `TemplateInstance`
11628
+ * - Iterables are committed as `Array<ChildPart>`
11629
+ * - All other types are committed as the template value or value returned or
11630
+ * set by a directive.
11631
+ *
11632
+ * @param part
11633
+ */
11634
+ const getCommittedValue = (part) => part._$committedValue;
11635
+ /**
11636
+ * Removes a ChildPart from the DOM, including any of its content.
11637
+ *
11638
+ * @param part The Part to remove
11639
+ */
11640
+ const removePart = (part) => {
11641
+ part._$notifyConnectionChanged?.(false, true);
11642
+ let start = part._$startNode;
11643
+ const end = wrap(part._$endNode).nextSibling;
11644
+ while (start !== end) {
11645
+ const n = wrap(start).nextSibling;
11646
+ wrap(start).remove();
11647
+ start = n;
11648
+ }
11649
+ };
11510
11650
 
11511
11651
  /**
11512
11652
  * @license
@@ -14614,6 +14754,416 @@
14614
14754
 
14615
14755
  var css_248z$Q = css`:host{display:block;position:relative}.combobox{display:flex;flex-direction:column;gap:var(--sgds-form-gap-md)}.dropdown{display:flex;height:100%}.sgds.combobox{align-items:stretch;display:flex;flex-wrap:wrap;justify-content:flex-end;position:relative;width:-webkit-fill-available;width:-moz-available}.dropdown-menu{box-sizing:border-box;max-height:10rem;overflow-x:hidden;overflow-y:auto}.visually-hidden{clip:rect(0,0,0,0)!important;border:0!important;height:1px!important;margin:-1px!important;overflow:hidden!important;padding:0!important;position:absolute!important;white-space:nowrap!important;width:1px!important}.form-control-group.disabled{cursor:not-allowed;opacity:var(--sgds-opacity-50)}.form-control-group{align-items:center;background-color:var(--sgds-form-surface-default);border:var(--sgds-form-border-width-default) solid var(--sgds-border-color-default);border-radius:var(--sgds-form-border-radius-md);display:flex;gap:var(--sgds-form-gap-md);justify-content:space-between;min-height:var(--sgds-dimension-48);min-width:var(--sgds-dimension-256);padding:var(--sgds-form-padding-y) var(--sgds-form-padding-x);transition:border-color .15s ease-in-out,box-shadow .15s ease-in-out;width:-webkit-fill-available;width:-moz-available}.form-control{appearance:none;background-clip:padding-box;background:none;border:none;color:var(--sgds-form-color-default);display:inline;flex-grow:1;font-size:var(--sgds-font-size-2);line-height:var(--sgds-line-height-body);outline:none;padding:0}.combobox-input-container{display:flex;flex-wrap:wrap;gap:var(--sgds-gap-xs);width:calc(100% - var(--sgds-icon-size-md, 1.25rem))}.empty-menu{padding:var(--sgds-padding-sm) var(--sgds-padding-lg,20px)}.form-control-group.readonly{border-color:var(--sgds-border-color-muted)}.form-control-group:not(.disabled):not(.is-invalid):hover{border:var(--sgds-form-border-width-thick) solid var(--sgds-border-color-emphasis)}.form-control-group:not(.disabled):not(.is-invalid):focus,.form-control-group:not(.disabled):not(.is-invalid):focus-within{border:var(--sgds-form-border-width-thick) solid var(--sgds-border-color-emphasis);box-shadow:var(--sgds-form-box-shadow-focus);outline:0}.form-control-group.is-invalid{border:var(--sgds-form-border-width-thick) solid var(--sgds-form-danger-border-color-default)}.form-control-group.disabled{background-color:var(--sgds-form-surface-muted)}`;
14616
14756
 
14757
+ /**
14758
+ * @license
14759
+ * Copyright 2017 Google LLC
14760
+ * SPDX-License-Identifier: BSD-3-Clause
14761
+ */
14762
+ // Helper for generating a map of array item to its index over a subset
14763
+ // of an array (used to lazily generate `newKeyToIndexMap` and
14764
+ // `oldKeyToIndexMap`)
14765
+ const generateMap = (list, start, end) => {
14766
+ const map = new Map();
14767
+ for (let i = start; i <= end; i++) {
14768
+ map.set(list[i], i);
14769
+ }
14770
+ return map;
14771
+ };
14772
+ class RepeatDirective extends Directive {
14773
+ constructor(partInfo) {
14774
+ super(partInfo);
14775
+ if (partInfo.type !== PartType.CHILD) {
14776
+ throw new Error('repeat() can only be used in text expressions');
14777
+ }
14778
+ }
14779
+ _getValuesAndKeys(items, keyFnOrTemplate, template) {
14780
+ let keyFn;
14781
+ if (template === undefined) {
14782
+ template = keyFnOrTemplate;
14783
+ }
14784
+ else if (keyFnOrTemplate !== undefined) {
14785
+ keyFn = keyFnOrTemplate;
14786
+ }
14787
+ const keys = [];
14788
+ const values = [];
14789
+ let index = 0;
14790
+ for (const item of items) {
14791
+ keys[index] = keyFn ? keyFn(item, index) : index;
14792
+ values[index] = template(item, index);
14793
+ index++;
14794
+ }
14795
+ return {
14796
+ values,
14797
+ keys,
14798
+ };
14799
+ }
14800
+ render(items, keyFnOrTemplate, template) {
14801
+ return this._getValuesAndKeys(items, keyFnOrTemplate, template).values;
14802
+ }
14803
+ update(containerPart, [items, keyFnOrTemplate, template]) {
14804
+ // Old part & key lists are retrieved from the last update (which may
14805
+ // be primed by hydration)
14806
+ const oldParts = getCommittedValue(containerPart);
14807
+ const { values: newValues, keys: newKeys } = this._getValuesAndKeys(items, keyFnOrTemplate, template);
14808
+ // We check that oldParts, the committed value, is an Array as an
14809
+ // indicator that the previous value came from a repeat() call. If
14810
+ // oldParts is not an Array then this is the first render and we return
14811
+ // an array for lit-html's array handling to render, and remember the
14812
+ // keys.
14813
+ if (!Array.isArray(oldParts)) {
14814
+ this._itemKeys = newKeys;
14815
+ return newValues;
14816
+ }
14817
+ // In SSR hydration it's possible for oldParts to be an array but for us
14818
+ // to not have item keys because the update() hasn't run yet. We set the
14819
+ // keys to an empty array. This will cause all oldKey/newKey comparisons
14820
+ // to fail and execution to fall to the last nested brach below which
14821
+ // reuses the oldPart.
14822
+ const oldKeys = (this._itemKeys ??= []);
14823
+ // New part list will be built up as we go (either reused from
14824
+ // old parts or created for new keys in this update). This is
14825
+ // saved in the above cache at the end of the update.
14826
+ const newParts = [];
14827
+ // Maps from key to index for current and previous update; these
14828
+ // are generated lazily only when needed as a performance
14829
+ // optimization, since they are only required for multiple
14830
+ // non-contiguous changes in the list, which are less common.
14831
+ let newKeyToIndexMap;
14832
+ let oldKeyToIndexMap;
14833
+ // Head and tail pointers to old parts and new values
14834
+ let oldHead = 0;
14835
+ let oldTail = oldParts.length - 1;
14836
+ let newHead = 0;
14837
+ let newTail = newValues.length - 1;
14838
+ // Overview of O(n) reconciliation algorithm (general approach
14839
+ // based on ideas found in ivi, vue, snabbdom, etc.):
14840
+ //
14841
+ // * We start with the list of old parts and new values (and
14842
+ // arrays of their respective keys), head/tail pointers into
14843
+ // each, and we build up the new list of parts by updating
14844
+ // (and when needed, moving) old parts or creating new ones.
14845
+ // The initial scenario might look like this (for brevity of
14846
+ // the diagrams, the numbers in the array reflect keys
14847
+ // associated with the old parts or new values, although keys
14848
+ // and parts/values are actually stored in parallel arrays
14849
+ // indexed using the same head/tail pointers):
14850
+ //
14851
+ // oldHead v v oldTail
14852
+ // oldKeys: [0, 1, 2, 3, 4, 5, 6]
14853
+ // newParts: [ , , , , , , ]
14854
+ // newKeys: [0, 2, 1, 4, 3, 7, 6] <- reflects the user's new
14855
+ // item order
14856
+ // newHead ^ ^ newTail
14857
+ //
14858
+ // * Iterate old & new lists from both sides, updating,
14859
+ // swapping, or removing parts at the head/tail locations
14860
+ // until neither head nor tail can move.
14861
+ //
14862
+ // * Example below: keys at head pointers match, so update old
14863
+ // part 0 in-place (no need to move it) and record part 0 in
14864
+ // the `newParts` list. The last thing we do is advance the
14865
+ // `oldHead` and `newHead` pointers (will be reflected in the
14866
+ // next diagram).
14867
+ //
14868
+ // oldHead v v oldTail
14869
+ // oldKeys: [0, 1, 2, 3, 4, 5, 6]
14870
+ // newParts: [0, , , , , , ] <- heads matched: update 0
14871
+ // newKeys: [0, 2, 1, 4, 3, 7, 6] and advance both oldHead
14872
+ // & newHead
14873
+ // newHead ^ ^ newTail
14874
+ //
14875
+ // * Example below: head pointers don't match, but tail
14876
+ // pointers do, so update part 6 in place (no need to move
14877
+ // it), and record part 6 in the `newParts` list. Last,
14878
+ // advance the `oldTail` and `oldHead` pointers.
14879
+ //
14880
+ // oldHead v v oldTail
14881
+ // oldKeys: [0, 1, 2, 3, 4, 5, 6]
14882
+ // newParts: [0, , , , , , 6] <- tails matched: update 6
14883
+ // newKeys: [0, 2, 1, 4, 3, 7, 6] and advance both oldTail
14884
+ // & newTail
14885
+ // newHead ^ ^ newTail
14886
+ //
14887
+ // * If neither head nor tail match; next check if one of the
14888
+ // old head/tail items was removed. We first need to generate
14889
+ // the reverse map of new keys to index (`newKeyToIndexMap`),
14890
+ // which is done once lazily as a performance optimization,
14891
+ // since we only hit this case if multiple non-contiguous
14892
+ // changes were made. Note that for contiguous removal
14893
+ // anywhere in the list, the head and tails would advance
14894
+ // from either end and pass each other before we get to this
14895
+ // case and removals would be handled in the final while loop
14896
+ // without needing to generate the map.
14897
+ //
14898
+ // * Example below: The key at `oldTail` was removed (no longer
14899
+ // in the `newKeyToIndexMap`), so remove that part from the
14900
+ // DOM and advance just the `oldTail` pointer.
14901
+ //
14902
+ // oldHead v v oldTail
14903
+ // oldKeys: [0, 1, 2, 3, 4, 5, 6]
14904
+ // newParts: [0, , , , , , 6] <- 5 not in new map: remove
14905
+ // newKeys: [0, 2, 1, 4, 3, 7, 6] 5 and advance oldTail
14906
+ // newHead ^ ^ newTail
14907
+ //
14908
+ // * Once head and tail cannot move, any mismatches are due to
14909
+ // either new or moved items; if a new key is in the previous
14910
+ // "old key to old index" map, move the old part to the new
14911
+ // location, otherwise create and insert a new part. Note
14912
+ // that when moving an old part we null its position in the
14913
+ // oldParts array if it lies between the head and tail so we
14914
+ // know to skip it when the pointers get there.
14915
+ //
14916
+ // * Example below: neither head nor tail match, and neither
14917
+ // were removed; so find the `newHead` key in the
14918
+ // `oldKeyToIndexMap`, and move that old part's DOM into the
14919
+ // next head position (before `oldParts[oldHead]`). Last,
14920
+ // null the part in the `oldPart` array since it was
14921
+ // somewhere in the remaining oldParts still to be scanned
14922
+ // (between the head and tail pointers) so that we know to
14923
+ // skip that old part on future iterations.
14924
+ //
14925
+ // oldHead v v oldTail
14926
+ // oldKeys: [0, 1, -, 3, 4, 5, 6]
14927
+ // newParts: [0, 2, , , , , 6] <- stuck: update & move 2
14928
+ // newKeys: [0, 2, 1, 4, 3, 7, 6] into place and advance
14929
+ // newHead
14930
+ // newHead ^ ^ newTail
14931
+ //
14932
+ // * Note that for moves/insertions like the one above, a part
14933
+ // inserted at the head pointer is inserted before the
14934
+ // current `oldParts[oldHead]`, and a part inserted at the
14935
+ // tail pointer is inserted before `newParts[newTail+1]`. The
14936
+ // seeming asymmetry lies in the fact that new parts are
14937
+ // moved into place outside in, so to the right of the head
14938
+ // pointer are old parts, and to the right of the tail
14939
+ // pointer are new parts.
14940
+ //
14941
+ // * We always restart back from the top of the algorithm,
14942
+ // allowing matching and simple updates in place to
14943
+ // continue...
14944
+ //
14945
+ // * Example below: the head pointers once again match, so
14946
+ // simply update part 1 and record it in the `newParts`
14947
+ // array. Last, advance both head pointers.
14948
+ //
14949
+ // oldHead v v oldTail
14950
+ // oldKeys: [0, 1, -, 3, 4, 5, 6]
14951
+ // newParts: [0, 2, 1, , , , 6] <- heads matched: update 1
14952
+ // newKeys: [0, 2, 1, 4, 3, 7, 6] and advance both oldHead
14953
+ // & newHead
14954
+ // newHead ^ ^ newTail
14955
+ //
14956
+ // * As mentioned above, items that were moved as a result of
14957
+ // being stuck (the final else clause in the code below) are
14958
+ // marked with null, so we always advance old pointers over
14959
+ // these so we're comparing the next actual old value on
14960
+ // either end.
14961
+ //
14962
+ // * Example below: `oldHead` is null (already placed in
14963
+ // newParts), so advance `oldHead`.
14964
+ //
14965
+ // oldHead v v oldTail
14966
+ // oldKeys: [0, 1, -, 3, 4, 5, 6] <- old head already used:
14967
+ // newParts: [0, 2, 1, , , , 6] advance oldHead
14968
+ // newKeys: [0, 2, 1, 4, 3, 7, 6]
14969
+ // newHead ^ ^ newTail
14970
+ //
14971
+ // * Note it's not critical to mark old parts as null when they
14972
+ // are moved from head to tail or tail to head, since they
14973
+ // will be outside the pointer range and never visited again.
14974
+ //
14975
+ // * Example below: Here the old tail key matches the new head
14976
+ // key, so the part at the `oldTail` position and move its
14977
+ // DOM to the new head position (before `oldParts[oldHead]`).
14978
+ // Last, advance `oldTail` and `newHead` pointers.
14979
+ //
14980
+ // oldHead v v oldTail
14981
+ // oldKeys: [0, 1, -, 3, 4, 5, 6]
14982
+ // newParts: [0, 2, 1, 4, , , 6] <- old tail matches new
14983
+ // newKeys: [0, 2, 1, 4, 3, 7, 6] head: update & move 4,
14984
+ // advance oldTail & newHead
14985
+ // newHead ^ ^ newTail
14986
+ //
14987
+ // * Example below: Old and new head keys match, so update the
14988
+ // old head part in place, and advance the `oldHead` and
14989
+ // `newHead` pointers.
14990
+ //
14991
+ // oldHead v oldTail
14992
+ // oldKeys: [0, 1, -, 3, 4, 5, 6]
14993
+ // newParts: [0, 2, 1, 4, 3, ,6] <- heads match: update 3
14994
+ // newKeys: [0, 2, 1, 4, 3, 7, 6] and advance oldHead &
14995
+ // newHead
14996
+ // newHead ^ ^ newTail
14997
+ //
14998
+ // * Once the new or old pointers move past each other then all
14999
+ // we have left is additions (if old list exhausted) or
15000
+ // removals (if new list exhausted). Those are handled in the
15001
+ // final while loops at the end.
15002
+ //
15003
+ // * Example below: `oldHead` exceeded `oldTail`, so we're done
15004
+ // with the main loop. Create the remaining part and insert
15005
+ // it at the new head position, and the update is complete.
15006
+ //
15007
+ // (oldHead > oldTail)
15008
+ // oldKeys: [0, 1, -, 3, 4, 5, 6]
15009
+ // newParts: [0, 2, 1, 4, 3, 7 ,6] <- create and insert 7
15010
+ // newKeys: [0, 2, 1, 4, 3, 7, 6]
15011
+ // newHead ^ newTail
15012
+ //
15013
+ // * Note that the order of the if/else clauses is not
15014
+ // important to the algorithm, as long as the null checks
15015
+ // come first (to ensure we're always working on valid old
15016
+ // parts) and that the final else clause comes last (since
15017
+ // that's where the expensive moves occur). The order of
15018
+ // remaining clauses is just a simple guess at which cases
15019
+ // will be most common.
15020
+ //
15021
+ // * Note, we could calculate the longest
15022
+ // increasing subsequence (LIS) of old items in new position,
15023
+ // and only move those not in the LIS set. However that costs
15024
+ // O(nlogn) time and adds a bit more code, and only helps
15025
+ // make rare types of mutations require fewer moves. The
15026
+ // above handles removes, adds, reversal, swaps, and single
15027
+ // moves of contiguous items in linear time, in the minimum
15028
+ // number of moves. As the number of multiple moves where LIS
15029
+ // might help approaches a random shuffle, the LIS
15030
+ // optimization becomes less helpful, so it seems not worth
15031
+ // the code at this point. Could reconsider if a compelling
15032
+ // case arises.
15033
+ while (oldHead <= oldTail && newHead <= newTail) {
15034
+ if (oldParts[oldHead] === null) {
15035
+ // `null` means old part at head has already been used
15036
+ // below; skip
15037
+ oldHead++;
15038
+ }
15039
+ else if (oldParts[oldTail] === null) {
15040
+ // `null` means old part at tail has already been used
15041
+ // below; skip
15042
+ oldTail--;
15043
+ }
15044
+ else if (oldKeys[oldHead] === newKeys[newHead]) {
15045
+ // Old head matches new head; update in place
15046
+ newParts[newHead] = setChildPartValue(oldParts[oldHead], newValues[newHead]);
15047
+ oldHead++;
15048
+ newHead++;
15049
+ }
15050
+ else if (oldKeys[oldTail] === newKeys[newTail]) {
15051
+ // Old tail matches new tail; update in place
15052
+ newParts[newTail] = setChildPartValue(oldParts[oldTail], newValues[newTail]);
15053
+ oldTail--;
15054
+ newTail--;
15055
+ }
15056
+ else if (oldKeys[oldHead] === newKeys[newTail]) {
15057
+ // Old head matches new tail; update and move to new tail
15058
+ newParts[newTail] = setChildPartValue(oldParts[oldHead], newValues[newTail]);
15059
+ insertPart(containerPart, newParts[newTail + 1], oldParts[oldHead]);
15060
+ oldHead++;
15061
+ newTail--;
15062
+ }
15063
+ else if (oldKeys[oldTail] === newKeys[newHead]) {
15064
+ // Old tail matches new head; update and move to new head
15065
+ newParts[newHead] = setChildPartValue(oldParts[oldTail], newValues[newHead]);
15066
+ insertPart(containerPart, oldParts[oldHead], oldParts[oldTail]);
15067
+ oldTail--;
15068
+ newHead++;
15069
+ }
15070
+ else {
15071
+ if (newKeyToIndexMap === undefined) {
15072
+ // Lazily generate key-to-index maps, used for removals &
15073
+ // moves below
15074
+ newKeyToIndexMap = generateMap(newKeys, newHead, newTail);
15075
+ oldKeyToIndexMap = generateMap(oldKeys, oldHead, oldTail);
15076
+ }
15077
+ if (!newKeyToIndexMap.has(oldKeys[oldHead])) {
15078
+ // Old head is no longer in new list; remove
15079
+ removePart(oldParts[oldHead]);
15080
+ oldHead++;
15081
+ }
15082
+ else if (!newKeyToIndexMap.has(oldKeys[oldTail])) {
15083
+ // Old tail is no longer in new list; remove
15084
+ removePart(oldParts[oldTail]);
15085
+ oldTail--;
15086
+ }
15087
+ else {
15088
+ // Any mismatches at this point are due to additions or
15089
+ // moves; see if we have an old part we can reuse and move
15090
+ // into place
15091
+ const oldIndex = oldKeyToIndexMap.get(newKeys[newHead]);
15092
+ const oldPart = oldIndex !== undefined ? oldParts[oldIndex] : null;
15093
+ if (oldPart === null) {
15094
+ // No old part for this value; create a new one and
15095
+ // insert it
15096
+ const newPart = insertPart(containerPart, oldParts[oldHead]);
15097
+ setChildPartValue(newPart, newValues[newHead]);
15098
+ newParts[newHead] = newPart;
15099
+ }
15100
+ else {
15101
+ // Reuse old part
15102
+ newParts[newHead] = setChildPartValue(oldPart, newValues[newHead]);
15103
+ insertPart(containerPart, oldParts[oldHead], oldPart);
15104
+ // This marks the old part as having been used, so that
15105
+ // it will be skipped in the first two checks above
15106
+ oldParts[oldIndex] = null;
15107
+ }
15108
+ newHead++;
15109
+ }
15110
+ }
15111
+ }
15112
+ // Add parts for any remaining new values
15113
+ while (newHead <= newTail) {
15114
+ // For all remaining additions, we insert before last new
15115
+ // tail, since old pointers are no longer valid
15116
+ const newPart = insertPart(containerPart, newParts[newTail + 1]);
15117
+ setChildPartValue(newPart, newValues[newHead]);
15118
+ newParts[newHead++] = newPart;
15119
+ }
15120
+ // Remove any remaining unused old parts
15121
+ while (oldHead <= oldTail) {
15122
+ const oldPart = oldParts[oldHead++];
15123
+ if (oldPart !== null) {
15124
+ removePart(oldPart);
15125
+ }
15126
+ }
15127
+ // Save order of new parts for next round
15128
+ this._itemKeys = newKeys;
15129
+ // Directly set part value, bypassing it's dirty-checking
15130
+ setCommittedValue(containerPart, newParts);
15131
+ return noChange;
15132
+ }
15133
+ }
15134
+ /**
15135
+ * A directive that repeats a series of values (usually `TemplateResults`)
15136
+ * generated from an iterable, and updates those items efficiently when the
15137
+ * iterable changes based on user-provided `keys` associated with each item.
15138
+ *
15139
+ * Note that if a `keyFn` is provided, strict key-to-DOM mapping is maintained,
15140
+ * meaning previous DOM for a given key is moved into the new position if
15141
+ * needed, and DOM will never be reused with values for different keys (new DOM
15142
+ * will always be created for new keys). This is generally the most efficient
15143
+ * way to use `repeat` since it performs minimum unnecessary work for insertions
15144
+ * and removals.
15145
+ *
15146
+ * The `keyFn` takes two parameters, the item and its index, and returns a unique key value.
15147
+ *
15148
+ * ```js
15149
+ * html`
15150
+ * <ol>
15151
+ * ${repeat(this.items, (item) => item.id, (item, index) => {
15152
+ * return html`<li>${index}: ${item.name}</li>`;
15153
+ * })}
15154
+ * </ol>
15155
+ * `
15156
+ * ```
15157
+ *
15158
+ * **Important**: If providing a `keyFn`, keys *must* be unique for all items in a
15159
+ * given call to `repeat`. The behavior when two or more items have the same key
15160
+ * is undefined.
15161
+ *
15162
+ * If no `keyFn` is provided, this directive will perform similar to mapping
15163
+ * items to values, and DOM will be reused against potentially different items.
15164
+ */
15165
+ const repeat = directive(RepeatDirective);
15166
+
14617
15167
  /**
14618
15168
  * @summary ComboBox component is used for users to make one or more selections from a list through user input, keyboard or mouse actions
14619
15169
  *
@@ -14682,6 +15232,29 @@
14682
15232
  if (!this._isTouched && this.value === "")
14683
15233
  return;
14684
15234
  this.invalid = !this._mixinReportValidity();
15235
+ // When value is updated by user and it doesn't map to selectedItems, we should re-map selectedItems
15236
+ const selectedItemVal = this.selectedItems.map(val => val.value).join(";");
15237
+ if (selectedItemVal !== this.value) {
15238
+ this._updateValueAndDisplayValue();
15239
+ }
15240
+ }
15241
+ _handleMenuListChange() {
15242
+ this._updateValueAndDisplayValue();
15243
+ this._renderedMenu = this.menuList;
15244
+ }
15245
+ _updateValueAndDisplayValue() {
15246
+ var _a, _b;
15247
+ const valueArray = this.value.split(";");
15248
+ const initialSelectedItem = this.menuList.filter(({ value }) => valueArray.includes(value));
15249
+ this.selectedItems = [...initialSelectedItem];
15250
+ // When the new filtered items don't match value we update it
15251
+ const updatedValue = initialSelectedItem.map(item => item.value).join(";");
15252
+ if (updatedValue !== this.value) {
15253
+ this.value = updatedValue;
15254
+ }
15255
+ if (!this.multiSelect) {
15256
+ this.displayValue = (_b = (_a = initialSelectedItem[0]) === null || _a === void 0 ? void 0 : _a.label) !== null && _b !== void 0 ? _b : "";
15257
+ }
14685
15258
  }
14686
15259
  // Called each time the user types in the <sgds-input>, we set .value and show the menu
14687
15260
  async _handleInputChange(e) {
@@ -14804,29 +15377,39 @@
14804
15377
  }
14805
15378
  _renderMenu() {
14806
15379
  const emptyMenu = html$1 ` <div class="empty-menu">No options</div> `;
14807
- const menu = this._renderedMenu.map(item => {
14808
- let isActive = false;
14809
- if (this.multiSelect) {
14810
- const selectedItemValueArray = this.selectedItems.map(i => i.value);
14811
- isActive = selectedItemValueArray.includes(item.value);
14812
- }
14813
- else {
14814
- isActive = item.value === this.value;
14815
- }
14816
- return html$1 `
14817
- <sgds-combo-box-item
14818
- ?active=${isActive}
14819
- ?checkbox=${this.multiSelect}
14820
- value=${item.value}
14821
- @sgds-select=${this._handleItemSelected}
14822
- @sgds-unselect=${this._handleItemUnselect}
14823
- >
14824
- ${item.label}
14825
- </sgds-combo-box-item>
14826
- `;
14827
- });
14828
- return this._renderedMenu.length === 0 ? emptyMenu : menu;
15380
+ const menu = this._renderedMenu.length === 0
15381
+ ? emptyMenu
15382
+ : repeat(this._renderedMenu, item => item.value, item => {
15383
+ let isActive = false;
15384
+ if (this.multiSelect) {
15385
+ const selectedItemValueArray = this.selectedItems.map(i => i.value);
15386
+ isActive = selectedItemValueArray.includes(item.value);
15387
+ }
15388
+ else {
15389
+ isActive = item.value === this.value;
15390
+ }
15391
+ return html$1 `
15392
+ <sgds-combo-box-item
15393
+ ?active=${isActive}
15394
+ ?checkbox=${this.multiSelect}
15395
+ value=${item.value}
15396
+ @sgds-select=${this._handleItemSelected}
15397
+ @sgds-unselect=${this._handleItemUnselect}
15398
+ >
15399
+ ${item.label}
15400
+ </sgds-combo-box-item>
15401
+ `;
15402
+ });
15403
+ return menu;
14829
15404
  }
15405
+ /**
15406
+ * Used `repeat` helper from Lit to render instead of .map:
15407
+ * The reassigning of value is affecting the truncation on badge as it is not triggering the slot change event.
15408
+ *
15409
+ * To compare this to lit-html's default handling for lists, consider reversing a large list of names:
15410
+ * For a list created using Array.map, lit-html maintains the DOM nodes for the list items, but reassigns the values
15411
+ * For a list created using repeat, the repeat directive reorders the existing DOM nodes, so the nodes representing the first list item move to the last position.
15412
+ */
14830
15413
  _renderInput() {
14831
15414
  const wantFeedbackStyle = this.hasFeedback;
14832
15415
  return html$1 `
@@ -14842,7 +15425,7 @@
14842
15425
  <div class="combobox-input-container">
14843
15426
  ${this.multiSelect
14844
15427
  ? html$1 `
14845
- ${this.selectedItems.map(item => html$1 `<sgds-badge
15428
+ ${repeat(this.selectedItems, item => item.value, item => html$1 `<sgds-badge
14846
15429
  outlined
14847
15430
  variant="neutral"
14848
15431
  show
@@ -14925,6 +15508,9 @@
14925
15508
  __decorate([
14926
15509
  watch("value", { waitUntilFirstUpdate: true })
14927
15510
  ], SgdsComboBox.prototype, "_handleValueChange", null);
15511
+ __decorate([
15512
+ watch("menuList", { waitUntilFirstUpdate: true })
15513
+ ], SgdsComboBox.prototype, "_handleMenuListChange", null);
14928
15514
 
14929
15515
  register("sgds-combo-box", SgdsComboBox);
14930
15516