@adobe/spectrum-wc-core 2.0.0-beta.2 → 2.0.0-beta.4

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.
Files changed (157) hide show
  1. package/README.md +1 -1
  2. package/dist/components/accordion/Accordion.base.js +28 -22
  3. package/dist/components/accordion/Accordion.base.js.map +1 -1
  4. package/dist/components/accordion/AccordionItem.base.d.ts +16 -6
  5. package/dist/components/accordion/AccordionItem.base.js +6 -3
  6. package/dist/components/accordion/AccordionItem.base.js.map +1 -1
  7. package/dist/components/action-group/ActionGroup.base.d.ts +108 -0
  8. package/dist/components/action-group/ActionGroup.base.js +53 -0
  9. package/dist/components/action-group/ActionGroup.base.js.map +1 -0
  10. package/dist/components/action-group/ActionGroup.types.d.ts +17 -0
  11. package/dist/components/action-group/ActionGroup.types.js +12 -0
  12. package/dist/components/action-group/ActionGroup.types.js.map +1 -0
  13. package/dist/components/action-group/index.d.ts +13 -0
  14. package/dist/components/action-group/index.js +3 -0
  15. package/dist/components/alert-banner/AlertBanner.base.js +11 -10
  16. package/dist/components/alert-banner/AlertBanner.base.js.map +1 -1
  17. package/dist/components/asset/Asset.base.d.ts +89 -10
  18. package/dist/components/asset/Asset.base.js +117 -12
  19. package/dist/components/asset/Asset.base.js.map +1 -1
  20. package/dist/components/asset/Asset.types.d.ts +20 -2
  21. package/dist/components/asset/Asset.types.js +10 -2
  22. package/dist/components/asset/Asset.types.js.map +1 -1
  23. package/dist/components/asset/index.js +3 -3
  24. package/dist/components/avatar/Avatar.base.js +18 -22
  25. package/dist/components/avatar/Avatar.base.js.map +1 -1
  26. package/dist/components/badge/Badge.base.d.ts +24 -8
  27. package/dist/components/badge/Badge.base.js +22 -17
  28. package/dist/components/badge/Badge.base.js.map +1 -1
  29. package/dist/components/button/Button.base.d.ts +20 -10
  30. package/dist/components/button/Button.base.js +17 -12
  31. package/dist/components/button/Button.base.js.map +1 -1
  32. package/dist/components/button-group/ButtonGroup.base.js +19 -12
  33. package/dist/components/button-group/ButtonGroup.base.js.map +1 -1
  34. package/dist/components/card/Card.base.d.ts +95 -46
  35. package/dist/components/card/Card.base.js +71 -23
  36. package/dist/components/card/Card.base.js.map +1 -1
  37. package/dist/components/card/Card.types.d.ts +1 -0
  38. package/dist/components/card/Card.types.js +2 -2
  39. package/dist/components/card/Card.types.js.map +1 -1
  40. package/dist/components/card/index.js +3 -3
  41. package/dist/components/divider/Divider.base.js +13 -8
  42. package/dist/components/divider/Divider.base.js.map +1 -1
  43. package/dist/components/dropzone/Dropzone.base.d.ts +74 -0
  44. package/dist/components/dropzone/Dropzone.base.js +82 -0
  45. package/dist/components/dropzone/Dropzone.base.js.map +1 -0
  46. package/dist/components/dropzone/Dropzone.types.d.ts +43 -0
  47. package/dist/components/dropzone/Dropzone.types.js +15 -0
  48. package/dist/components/dropzone/Dropzone.types.js.map +1 -0
  49. package/dist/components/dropzone/index.d.ts +13 -0
  50. package/dist/components/dropzone/index.js +3 -0
  51. package/dist/components/icon/Icon.base.d.ts +11 -7
  52. package/dist/components/icon/Icon.base.js +13 -20
  53. package/dist/components/icon/Icon.base.js.map +1 -1
  54. package/dist/components/illustrated-message/IllustratedMessage.base.d.ts +22 -8
  55. package/dist/components/illustrated-message/IllustratedMessage.base.js +33 -20
  56. package/dist/components/illustrated-message/IllustratedMessage.base.js.map +1 -1
  57. package/dist/components/infield-button/InfieldButton.base.d.ts +28 -0
  58. package/dist/components/infield-button/InfieldButton.base.js +22 -0
  59. package/dist/components/infield-button/InfieldButton.base.js.map +1 -0
  60. package/dist/components/infield-button/InfieldButton.types.d.ts +13 -0
  61. package/dist/components/infield-button/InfieldButton.types.js +11 -0
  62. package/dist/components/infield-button/InfieldButton.types.js.map +1 -0
  63. package/dist/components/infield-button/index.d.ts +13 -0
  64. package/dist/components/infield-button/index.js +3 -0
  65. package/dist/components/meter/Meter.base.js +11 -9
  66. package/dist/components/meter/Meter.base.js.map +1 -1
  67. package/dist/components/popover/Popover.base.d.ts +1 -21
  68. package/dist/components/popover/Popover.base.js +57 -77
  69. package/dist/components/popover/Popover.base.js.map +1 -1
  70. package/dist/components/progress-circle/ProgressCircle.base.d.ts +0 -1
  71. package/dist/components/progress-circle/ProgressCircle.base.js +26 -22
  72. package/dist/components/progress-circle/ProgressCircle.base.js.map +1 -1
  73. package/dist/components/status-light/StatusLight.base.d.ts +1 -1
  74. package/dist/components/status-light/StatusLight.base.js +12 -9
  75. package/dist/components/status-light/StatusLight.base.js.map +1 -1
  76. package/dist/components/tabs/TabPanel.base.d.ts +1 -1
  77. package/dist/components/tabs/TabPanel.base.js.map +1 -1
  78. package/dist/components/tabs/Tabs.base.js +44 -32
  79. package/dist/components/tabs/Tabs.base.js.map +1 -1
  80. package/dist/components/tooltip/Tooltip.base.js +57 -22
  81. package/dist/components/tooltip/Tooltip.base.js.map +1 -1
  82. package/dist/controllers/color-controller/src/color-controller.js +45 -44
  83. package/dist/controllers/color-controller/src/color-controller.js.map +1 -1
  84. package/dist/controllers/drag-and-drop-controller/index.d.ts +12 -0
  85. package/dist/controllers/drag-and-drop-controller/index.js +2 -0
  86. package/dist/controllers/drag-and-drop-controller/src/drag-and-drop-controller.d.ts +68 -0
  87. package/dist/controllers/drag-and-drop-controller/src/drag-and-drop-controller.js +56 -0
  88. package/dist/controllers/drag-and-drop-controller/src/drag-and-drop-controller.js.map +1 -0
  89. package/dist/controllers/focusgroup-navigation-controller/src/focusgroup-navigation-controller.d.ts +1 -1
  90. package/dist/controllers/focusgroup-navigation-controller/src/focusgroup-navigation-controller.js.map +1 -1
  91. package/dist/controllers/index.d.ts +6 -2
  92. package/dist/controllers/index.js +15 -11
  93. package/dist/controllers/language-resolution.d.ts +1 -1
  94. package/dist/controllers/language-resolution.js.map +1 -1
  95. package/dist/controllers/page-scroll-lock-controller/index.d.ts +12 -0
  96. package/dist/controllers/page-scroll-lock-controller/index.js +2 -0
  97. package/dist/controllers/{page-scroll-lock.js → page-scroll-lock-controller/src/page-scroll-lock.js} +1 -1
  98. package/dist/controllers/page-scroll-lock-controller/src/page-scroll-lock.js.map +1 -0
  99. package/dist/controllers/slot-presence-controller/index.d.ts +12 -0
  100. package/dist/controllers/slot-presence-controller/index.js +2 -0
  101. package/dist/controllers/slot-presence-controller/src/slot-presence-controller.d.ts +57 -0
  102. package/dist/controllers/slot-presence-controller/src/slot-presence-controller.js +44 -0
  103. package/dist/controllers/slot-presence-controller/src/slot-presence-controller.js.map +1 -0
  104. package/dist/controllers/slot-text-controller/index.d.ts +12 -0
  105. package/dist/controllers/slot-text-controller/index.js +2 -0
  106. package/dist/controllers/slot-text-controller/src/slot-text-controller.d.ts +92 -0
  107. package/dist/controllers/slot-text-controller/src/slot-text-controller.js +53 -0
  108. package/dist/controllers/slot-text-controller/src/slot-text-controller.js.map +1 -0
  109. package/dist/controllers/trigger-press-controller/index.d.ts +12 -0
  110. package/dist/controllers/trigger-press-controller/index.js +2 -0
  111. package/dist/controllers/trigger-press-controller/src/trigger-press-controller.d.ts +149 -0
  112. package/dist/controllers/trigger-press-controller/src/trigger-press-controller.js +43 -0
  113. package/dist/controllers/trigger-press-controller/src/trigger-press-controller.js.map +1 -0
  114. package/dist/directives/index.d.ts +1 -1
  115. package/dist/element/define-element.js +1 -1
  116. package/dist/element/define-element.js.map +1 -1
  117. package/dist/element/index.d.ts +1 -1
  118. package/dist/element/index.js +2 -2
  119. package/dist/element/spectrum-element.d.ts +58 -15
  120. package/dist/element/spectrum-element.js +56 -37
  121. package/dist/element/spectrum-element.js.map +1 -1
  122. package/dist/element/version.d.ts +3 -3
  123. package/dist/element/version.js +1 -1
  124. package/dist/element/version.js.map +1 -1
  125. package/dist/mixins/index.d.ts +0 -2
  126. package/dist/mixins/index.js +4 -6
  127. package/dist/mixins/linear-progress-mixin.d.ts +1 -2
  128. package/dist/mixins/linear-progress-mixin.js +35 -34
  129. package/dist/mixins/linear-progress-mixin.js.map +1 -1
  130. package/dist/mixins/pending-mixin.js +12 -11
  131. package/dist/mixins/pending-mixin.js.map +1 -1
  132. package/dist/mixins/sized-mixin.d.ts +28 -0
  133. package/dist/mixins/sized-mixin.js +6 -6
  134. package/dist/mixins/sized-mixin.js.map +1 -1
  135. package/dist/utils/dev-validation.d.ts +88 -0
  136. package/dist/utils/dev-validation.js +33 -0
  137. package/dist/utils/dev-validation.js.map +1 -0
  138. package/dist/utils/index.d.ts +2 -0
  139. package/dist/utils/index.js +12 -10
  140. package/dist/utils/is-focus-visible-in-tree.d.ts +33 -0
  141. package/dist/utils/is-focus-visible-in-tree.js +11 -0
  142. package/dist/utils/is-focus-visible-in-tree.js.map +1 -0
  143. package/dist/utils/resolve-trigger.js.map +1 -1
  144. package/package.json +213 -18
  145. package/CHANGELOG.md +0 -125
  146. package/dist/_virtual/_@oxc-project_runtime@0.124.0/helpers/defineProperty.js +0 -12
  147. package/dist/_virtual/_@oxc-project_runtime@0.124.0/helpers/toPrimitive.js +0 -14
  148. package/dist/_virtual/_@oxc-project_runtime@0.124.0/helpers/toPropertyKey.js +0 -9
  149. package/dist/_virtual/_@oxc-project_runtime@0.124.0/helpers/typeof.js +0 -11
  150. package/dist/controllers/page-scroll-lock.js.map +0 -1
  151. package/dist/mixins/observe-slot-presence.d.ts +0 -12
  152. package/dist/mixins/observe-slot-presence.js +0 -40
  153. package/dist/mixins/observe-slot-presence.js.map +0 -1
  154. package/dist/mixins/observe-slot-text.d.ts +0 -11
  155. package/dist/mixins/observe-slot-text.js +0 -60
  156. package/dist/mixins/observe-slot-text.js.map +0 -1
  157. /package/dist/controllers/{page-scroll-lock.d.ts → page-scroll-lock-controller/src/page-scroll-lock.d.ts} +0 -0
@@ -0,0 +1,44 @@
1
+ //#region controllers/slot-presence-controller/src/slot-presence-controller.ts
2
+ var e = class {
3
+ constructor(e, t) {
4
+ this.presenceMap = /* @__PURE__ */ new Map(), this.host = e, this.selectors = Array.isArray(t) ? t : [t], this.host.addController(this), this.observer = new MutationObserver(() => {
5
+ this.checkPresence();
6
+ }), this.checkPresence();
7
+ }
8
+ get isPresent() {
9
+ if (this.selectors.length === 1) {
10
+ var e;
11
+ return (e = this.presenceMap.get(this.selectors[0])) == null ? !1 : e;
12
+ }
13
+ throw Error("Multiple selectors provided to SlotPresenceController — use getPresence(selector) instead.");
14
+ }
15
+ getPresence(e) {
16
+ if (this.presenceMap.has(e)) {
17
+ var t;
18
+ return (t = this.presenceMap.get(e)) == null ? !1 : t;
19
+ }
20
+ throw Error(`The provided selector \`${e}\` is not being observed.`);
21
+ }
22
+ checkPresence() {
23
+ let e = !1;
24
+ for (let n of this.selectors) {
25
+ var t;
26
+ let r = !!this.host.querySelector(`:scope > ${n}`);
27
+ ((t = this.presenceMap.get(n)) == null ? !1 : t) !== r && (e = !0), this.presenceMap.set(n, r);
28
+ }
29
+ e && this.host.requestUpdate();
30
+ }
31
+ hostConnected() {
32
+ this.observer.observe(this.host, {
33
+ childList: !0,
34
+ subtree: !0
35
+ }), this.checkPresence();
36
+ }
37
+ hostDisconnected() {
38
+ this.observer.disconnect();
39
+ }
40
+ };
41
+ //#endregion
42
+ export { e as SlotPresenceController };
43
+
44
+ //# sourceMappingURL=slot-presence-controller.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"slot-presence-controller.js","names":[],"sources":["../../../../controllers/slot-presence-controller/src/slot-presence-controller.ts"],"sourcesContent":["/**\n * Copyright 2026 Adobe. All rights reserved.\n * This file is licensed to you under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License. You may obtain a copy\n * of the License at http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software distributed under\n * the License is distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS\n * OF ANY KIND, either express or implied. See the License for the specific language\n * governing permissions and limitations under the License.\n */\n\nimport type { ReactiveController, ReactiveElement } from 'lit';\n\n/**\n * A reactive controller that observes whether slotted content matching\n * given CSS selectors is present in the host's light DOM.\n *\n * Replaces the `ObserveSlotPresence` mixin with a composition-based approach.\n *\n * @example\n * ```typescript\n * class MyComponent extends SpectrumElement {\n * private slotPresence = new SlotPresenceController(this, '[slot=\"icon\"]');\n *\n * get hasIcon(): boolean {\n * return this.slotPresence.isPresent;\n * }\n * }\n * ```\n *\n * @example\n * ```typescript\n * // Observing multiple selectors\n * class MyComponent extends SpectrumElement {\n * private slotPresence = new SlotPresenceController(this, [\n * '[slot=\"icon\"]',\n * '[slot=\"description\"]',\n * ]);\n *\n * get hasIcon(): boolean {\n * return this.slotPresence.getPresence('[slot=\"icon\"]');\n * }\n *\n * get hasDescription(): boolean {\n * return this.slotPresence.getPresence('[slot=\"description\"]');\n * }\n * }\n * ```\n */\nexport class SlotPresenceController implements ReactiveController {\n private host: ReactiveElement;\n private selectors: string[];\n private presenceMap = new Map<string, boolean>();\n private observer: MutationObserver;\n\n constructor(host: ReactiveElement, selectors: string | string[]) {\n this.host = host;\n this.selectors = Array.isArray(selectors) ? selectors : [selectors];\n this.host.addController(this);\n\n this.observer = new MutationObserver(() => {\n this.checkPresence();\n });\n\n // Check initial state synchronously\n this.checkPresence();\n }\n\n /**\n * Whether slotted content is present. Use this when observing a single\n * selector. Throws if multiple selectors were provided — use\n * `getPresence(selector)` instead.\n */\n get isPresent(): boolean {\n if (this.selectors.length === 1) {\n return this.presenceMap.get(this.selectors[0]) ?? false;\n }\n throw new Error(\n 'Multiple selectors provided to SlotPresenceController — use getPresence(selector) instead.'\n );\n }\n\n /**\n * Returns whether content matching the given selector is present.\n */\n getPresence(selector: string): boolean {\n if (this.presenceMap.has(selector)) {\n return this.presenceMap.get(selector) ?? false;\n }\n throw new Error(\n `The provided selector \\`${selector}\\` is not being observed.`\n );\n }\n\n private checkPresence(): void {\n let changed = false;\n for (const selector of this.selectors) {\n const nextValue = !!this.host.querySelector(`:scope > ${selector}`);\n const previousValue = this.presenceMap.get(selector) ?? false;\n if (previousValue !== nextValue) {\n changed = true;\n }\n this.presenceMap.set(selector, nextValue);\n }\n if (changed) {\n // The `ObserveSlotPresence` mixin this controller replaces deferred the\n // re-render with `this.updateComplete.then(() => this.requestUpdate())`.\n // The controller requests the update synchronously instead. This is\n // safe because `checkPresence` only runs from the constructor, the\n // `MutationObserver` callback, and `hostConnected` — never from inside\n // the host's own `update()`/`render()` — so there is no re-entrant\n // update. Lit batches `requestUpdate()` calls into the next microtask,\n // so multiple presence changes in one tick still coalesce into a single\n // render. See the controller stories for the behavioral test.\n this.host.requestUpdate();\n }\n }\n\n hostConnected(): void {\n this.observer.observe(this.host, {\n childList: true,\n subtree: true,\n });\n this.checkPresence();\n }\n\n hostDisconnected(): void {\n this.observer.disconnect();\n }\n}\n"],"mappings":";AAkDA,IAAa,IAAb,MAAkE;CAMhE,YAAY,GAAuB,GAA8B;AAU/D,qCAboB,IAAI,KAAsB,EAI9C,KAAK,OAAO,GACZ,KAAK,YAAY,MAAM,QAAQ,EAAU,GAAG,IAAY,CAAC,EAAU,EACnE,KAAK,KAAK,cAAc,KAAK,EAE7B,KAAK,WAAW,IAAI,uBAAuB;AACzC,QAAK,eAAe;IACpB,EAGF,KAAK,eAAe;;CAQtB,IAAI,YAAqB;AACvB,MAAI,KAAK,UAAU,WAAW,GAAG;;AAC/B,WAAA,IAAO,KAAK,YAAY,IAAI,KAAK,UAAU,GAAG,KAAA,OAAI,KAAJ;;AAEhD,QAAU,MACR,6FACD;;CAMH,YAAY,GAA2B;AACrC,MAAI,KAAK,YAAY,IAAI,EAAS,EAAE;;AAClC,WAAA,IAAO,KAAK,YAAY,IAAI,EAAS,KAAA,OAAI,KAAJ;;AAEvC,QAAU,MACR,2BAA2B,EAAS,2BACrC;;CAGH,gBAA8B;EAC5B,IAAI,IAAU;AACd,OAAK,IAAM,KAAY,KAAK,WAAW;;GACrC,IAAM,IAAY,CAAC,CAAC,KAAK,KAAK,cAAc,YAAY,IAAW;AAKnE,KAHA,IADsB,KAAK,YAAY,IAAI,EAAS,KAAA,OAAI,KAAJ,OAC9B,MACpB,IAAU,KAEZ,KAAK,YAAY,IAAI,GAAU,EAAU;;AAE3C,EAAI,KAUF,KAAK,KAAK,eAAe;;CAI7B,gBAAsB;AAKpB,EAJA,KAAK,SAAS,QAAQ,KAAK,MAAM;GAC/B,WAAW;GACX,SAAS;GACV,CAAC,EACF,KAAK,eAAe;;CAGtB,mBAAyB;AACvB,OAAK,SAAS,YAAY"}
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Copyright 2026 Adobe. All rights reserved.
3
+ * This file is licensed to you under the Apache License, Version 2.0 (the "License");
4
+ * you may not use this file except in compliance with the License. You may obtain a copy
5
+ * of the License at http://www.apache.org/licenses/LICENSE-2.0
6
+ *
7
+ * Unless required by applicable law or agreed to in writing, software distributed under
8
+ * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
9
+ * OF ANY KIND, either express or implied. See the License for the specific language
10
+ * governing permissions and limitations under the License.
11
+ */
12
+ export { SlotTextController, type SlotTextConfig, } from './src/slot-text-controller.js';
@@ -0,0 +1,2 @@
1
+ import { SlotTextController as e } from "./src/slot-text-controller.js";
2
+ export { e as SlotTextController };
@@ -0,0 +1,92 @@
1
+ import { ReactiveController, ReactiveElement } from 'lit';
2
+ export interface SlotTextConfig {
3
+ /**
4
+ * The slot name to observe. Use `''` (empty string) for the default slot.
5
+ *
6
+ * @default '' (default slot)
7
+ */
8
+ slotName?: string;
9
+ /**
10
+ * CSS selectors for elements to exclude when checking for content.
11
+ * Matched elements are ignored — only non-excluded elements and
12
+ * non-empty text nodes count as "content."
13
+ */
14
+ excludeSelectors?: string[];
15
+ }
16
+ /**
17
+ * A reactive controller that observes whether a slot contains meaningful
18
+ * text or element content.
19
+ *
20
+ * Replaces the `ObserveSlotText` mixin with a composition-based approach.
21
+ *
22
+ * Monitors for `characterData` mutations (text changes) and re-evaluates
23
+ * on slot change events. The host is requested to update whenever the
24
+ * content state changes.
25
+ *
26
+ * @example
27
+ * ```typescript
28
+ * class MyButton extends SpectrumElement {
29
+ * private slotText = new SlotTextController(this);
30
+ *
31
+ * get hasLabel(): boolean {
32
+ * return this.slotText.hasContent;
33
+ * }
34
+ *
35
+ * override render() {
36
+ * return html`<slot @slotchange=${this.slotText.handleSlotChange}></slot>`;
37
+ * }
38
+ * }
39
+ * ```
40
+ *
41
+ * @example
42
+ * ```typescript
43
+ * // Named slot with exclusions
44
+ * class MyComponent extends SpectrumElement {
45
+ * private slotText = new SlotTextController(this, {
46
+ * slotName: 'description',
47
+ * excludeSelectors: ['.visually-hidden'],
48
+ * });
49
+ * }
50
+ * ```
51
+ */
52
+ export declare class SlotTextController implements ReactiveController {
53
+ private host;
54
+ private slotName;
55
+ private excludeSelectors;
56
+ private observer;
57
+ private _hasContent;
58
+ constructor(host: ReactiveElement, config?: SlotTextConfig);
59
+ /**
60
+ * Whether the observed slot has meaningful content (text nodes or
61
+ * non-excluded elements).
62
+ */
63
+ get hasContent(): boolean;
64
+ /**
65
+ * Bind this to the slot's `@slotchange` event in the template.
66
+ *
67
+ * @example
68
+ * ```html
69
+ * <slot @slotchange=${this.slotText.handleSlotChange}></slot>
70
+ * ```
71
+ */
72
+ handleSlotChange: (event: Event) => void;
73
+ private evaluateNodes;
74
+ /**
75
+ * Checks initial content from light DOM children before the first render,
76
+ * before slots are assigned.
77
+ *
78
+ * This runs from `hostConnected`, which Lit invokes during the host's
79
+ * `connectedCallback` — before the first `update()`. It sets `_hasContent`
80
+ * directly and intentionally does not call `requestUpdate()`: the first
81
+ * render has not happened yet, so it will read the correct value when it
82
+ * runs. This differs from the `ObserveSlotText` mixin, where `slotHasContent`
83
+ * was a reactive `@property` assigned in `update()`. After the first render,
84
+ * `handleSlotChange` (bound via `@slotchange`) and the `characterData`
85
+ * observer take over and do call `requestUpdate()` on change. The
86
+ * controller stories cover initial content being reflected on first paint.
87
+ */
88
+ private checkInitialContent;
89
+ private checkContent;
90
+ hostConnected(): void;
91
+ hostDisconnected(): void;
92
+ }
@@ -0,0 +1,53 @@
1
+ //#region controllers/slot-text-controller/src/slot-text-controller.ts
2
+ var e = class {
3
+ constructor(e, t = {}) {
4
+ var n, r;
5
+ this._hasContent = !1, this.handleSlotChange = (e) => {
6
+ let t = e.target.assignedNodes({ flatten: !0 });
7
+ this.evaluateNodes(t);
8
+ }, this.host = e, this.slotName = (n = t.slotName) == null ? "" : n, this.excludeSelectors = (r = t.excludeSelectors) == null ? [] : r, this.host.addController(this), this.observer = new MutationObserver((e) => {
9
+ for (let t of e) if (t.type === "characterData") {
10
+ this.checkContent();
11
+ return;
12
+ }
13
+ });
14
+ }
15
+ get hasContent() {
16
+ return this._hasContent;
17
+ }
18
+ evaluateNodes(e) {
19
+ let t = [...e].some((e) => {
20
+ let t = e;
21
+ return t.tagName ? !this.excludeSelectors.some((e) => t.matches(e)) : this.slotName ? !1 : e.nodeType === Node.TEXT_NODE && e.textContent ? e.textContent.trim().length > 0 : !1;
22
+ });
23
+ t !== this._hasContent && (this._hasContent = t, this.host.requestUpdate());
24
+ }
25
+ checkInitialContent() {
26
+ let { childNodes: e } = this.host;
27
+ this._hasContent = [...e].filter((e) => {
28
+ let t = e;
29
+ return t.tagName ? this.excludeSelectors.some((e) => t.matches(e)) ? !1 : this.slotName ? t.getAttribute("slot") === this.slotName : !t.hasAttribute("slot") : this.slotName ? !1 : e.nodeType === Node.TEXT_NODE && e.textContent ? e.textContent.trim().length > 0 : !1;
30
+ }).length > 0;
31
+ }
32
+ checkContent() {
33
+ var e;
34
+ let t = (e = this.host.shadowRoot) == null ? void 0 : e.querySelector(this.slotName ? `slot[name="${this.slotName}"]` : "slot:not([name])");
35
+ if (t) {
36
+ let e = t.assignedNodes({ flatten: !0 });
37
+ this.evaluateNodes(e);
38
+ }
39
+ }
40
+ hostConnected() {
41
+ this.observer.observe(this.host, {
42
+ characterData: !0,
43
+ subtree: !0
44
+ }), this.checkInitialContent();
45
+ }
46
+ hostDisconnected() {
47
+ this.observer.disconnect();
48
+ }
49
+ };
50
+ //#endregion
51
+ export { e as SlotTextController };
52
+
53
+ //# sourceMappingURL=slot-text-controller.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"slot-text-controller.js","names":[],"sources":["../../../../controllers/slot-text-controller/src/slot-text-controller.ts"],"sourcesContent":["/**\n * Copyright 2026 Adobe. All rights reserved.\n * This file is licensed to you under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License. You may obtain a copy\n * of the License at http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software distributed under\n * the License is distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS\n * OF ANY KIND, either express or implied. See the License for the specific language\n * governing permissions and limitations under the License.\n */\n\nimport type { ReactiveController, ReactiveElement } from 'lit';\n\nexport interface SlotTextConfig {\n /**\n * The slot name to observe. Use `''` (empty string) for the default slot.\n *\n * @default '' (default slot)\n */\n slotName?: string;\n\n /**\n * CSS selectors for elements to exclude when checking for content.\n * Matched elements are ignored — only non-excluded elements and\n * non-empty text nodes count as \"content.\"\n */\n excludeSelectors?: string[];\n}\n\n/**\n * A reactive controller that observes whether a slot contains meaningful\n * text or element content.\n *\n * Replaces the `ObserveSlotText` mixin with a composition-based approach.\n *\n * Monitors for `characterData` mutations (text changes) and re-evaluates\n * on slot change events. The host is requested to update whenever the\n * content state changes.\n *\n * @example\n * ```typescript\n * class MyButton extends SpectrumElement {\n * private slotText = new SlotTextController(this);\n *\n * get hasLabel(): boolean {\n * return this.slotText.hasContent;\n * }\n *\n * override render() {\n * return html`<slot @slotchange=${this.slotText.handleSlotChange}></slot>`;\n * }\n * }\n * ```\n *\n * @example\n * ```typescript\n * // Named slot with exclusions\n * class MyComponent extends SpectrumElement {\n * private slotText = new SlotTextController(this, {\n * slotName: 'description',\n * excludeSelectors: ['.visually-hidden'],\n * });\n * }\n * ```\n */\nexport class SlotTextController implements ReactiveController {\n private host: ReactiveElement;\n private slotName: string;\n private excludeSelectors: string[];\n private observer: MutationObserver;\n private _hasContent = false;\n\n constructor(host: ReactiveElement, config: SlotTextConfig = {}) {\n this.host = host;\n this.slotName = config.slotName ?? '';\n this.excludeSelectors = config.excludeSelectors ?? [];\n this.host.addController(this);\n\n this.observer = new MutationObserver((mutations) => {\n for (const mutation of mutations) {\n if (mutation.type === 'characterData') {\n this.checkContent();\n return;\n }\n }\n });\n }\n\n /**\n * Whether the observed slot has meaningful content (text nodes or\n * non-excluded elements).\n */\n get hasContent(): boolean {\n return this._hasContent;\n }\n\n /**\n * Bind this to the slot's `@slotchange` event in the template.\n *\n * @example\n * ```html\n * <slot @slotchange=${this.slotText.handleSlotChange}></slot>\n * ```\n */\n handleSlotChange = (event: Event): void => {\n const slot = event.target as HTMLSlotElement;\n const nodes = slot.assignedNodes({ flatten: true });\n this.evaluateNodes(nodes);\n };\n\n private evaluateNodes(nodes: Iterable<Node>): void {\n const hasContent = [...nodes].some((node) => {\n const el = node as HTMLElement;\n if (el.tagName) {\n return !this.excludeSelectors.some((sel) => el.matches(sel));\n }\n // A bare text node can never carry a `slot` attribute, so it is only\n // ever assigned to the default slot. When observing a named slot it must\n // not count as content.\n if (this.slotName) {\n return false;\n }\n // Only real text nodes count. Comment nodes (e.g. Lit's child-part\n // markers left behind by a `${cond ? nothing : label}` binding) also\n // lack a `tagName`, and their `textContent` is the marker data, which\n // would otherwise be misread as label text.\n return node.nodeType === Node.TEXT_NODE && node.textContent\n ? node.textContent.trim().length > 0\n : false;\n });\n\n if (hasContent !== this._hasContent) {\n this._hasContent = hasContent;\n this.host.requestUpdate();\n }\n }\n\n /**\n * Checks initial content from light DOM children before the first render,\n * before slots are assigned.\n *\n * This runs from `hostConnected`, which Lit invokes during the host's\n * `connectedCallback` — before the first `update()`. It sets `_hasContent`\n * directly and intentionally does not call `requestUpdate()`: the first\n * render has not happened yet, so it will read the correct value when it\n * runs. This differs from the `ObserveSlotText` mixin, where `slotHasContent`\n * was a reactive `@property` assigned in `update()`. After the first render,\n * `handleSlotChange` (bound via `@slotchange`) and the `characterData`\n * observer take over and do call `requestUpdate()` on change. The\n * controller stories cover initial content being reflected on first paint.\n */\n private checkInitialContent(): void {\n const { childNodes } = this.host;\n const relevant = [...childNodes].filter((node) => {\n const el = node as HTMLElement;\n if (el.tagName) {\n const excluded = this.excludeSelectors.some((sel) => el.matches(sel));\n if (excluded) {\n return false;\n }\n // Match slot assignment: named slot checks slot attribute,\n // default slot checks for no slot attribute\n return this.slotName\n ? el.getAttribute('slot') === this.slotName\n : !el.hasAttribute('slot');\n }\n // A bare text node has no `slot` attribute, so it is only assigned to\n // the default slot; it never counts as content for a named slot.\n if (this.slotName) {\n return false;\n }\n // Only real text nodes count. Comment nodes (e.g. Lit's child-part\n // markers left behind by a `${cond ? nothing : label}` binding) also\n // lack a `tagName`, and their `textContent` is the marker data, which\n // would otherwise be misread as label text.\n return node.nodeType === Node.TEXT_NODE && node.textContent\n ? node.textContent.trim().length > 0\n : false;\n });\n this._hasContent = relevant.length > 0;\n }\n\n private checkContent(): void {\n // Re-query assigned nodes from the slot element in the shadow root\n const slot = this.host.shadowRoot?.querySelector(\n this.slotName ? `slot[name=\"${this.slotName}\"]` : 'slot:not([name])'\n ) as HTMLSlotElement | null;\n if (slot) {\n const nodes = slot.assignedNodes({ flatten: true });\n this.evaluateNodes(nodes);\n }\n }\n\n hostConnected(): void {\n this.observer.observe(this.host, {\n characterData: true,\n subtree: true,\n });\n this.checkInitialContent();\n }\n\n hostDisconnected(): void {\n this.observer.disconnect();\n }\n}\n"],"mappings":";AAkEA,IAAa,IAAb,MAA8D;CAO5D,YAAY,GAAuB,IAAyB,EAAE,EAAE;;AAM9D,qBARoB,6BAkCF,MAAuB;GAEzC,IAAM,IADO,EAAM,OACA,cAAc,EAAE,SAAS,IAAM,CAAC;AACnD,QAAK,cAAc,EAAM;KAlCzB,KAAK,OAAO,GACZ,KAAK,YAAA,IAAW,EAAO,aAAA,OAAY,KAAZ,GACvB,KAAK,oBAAA,IAAmB,EAAO,qBAAA,OAAoB,EAAE,GAAtB,GAC/B,KAAK,KAAK,cAAc,KAAK,EAE7B,KAAK,WAAW,IAAI,kBAAkB,MAAc;AAClD,QAAK,IAAM,KAAY,EACrB,KAAI,EAAS,SAAS,iBAAiB;AACrC,SAAK,cAAc;AACnB;;IAGJ;;CAOJ,IAAI,aAAsB;AACxB,SAAO,KAAK;;CAiBd,cAAsB,GAA6B;EACjD,IAAM,IAAa,CAAC,GAAG,EAAM,CAAC,MAAM,MAAS;GAC3C,IAAM,IAAK;AAcX,UAbI,EAAG,UACE,CAAC,KAAK,iBAAiB,MAAM,MAAQ,EAAG,QAAQ,EAAI,CAAC,GAK1D,KAAK,WACA,KAMF,EAAK,aAAa,KAAK,aAAa,EAAK,cAC5C,EAAK,YAAY,MAAM,CAAC,SAAS,IACjC;IACJ;AAEF,EAAI,MAAe,KAAK,gBACtB,KAAK,cAAc,GACnB,KAAK,KAAK,eAAe;;CAkB7B,sBAAoC;EAClC,IAAM,EAAE,kBAAe,KAAK;AA2B5B,OAAK,cA1BY,CAAC,GAAG,EAAW,CAAC,QAAQ,MAAS;GAChD,IAAM,IAAK;AAqBX,UApBI,EAAG,UACY,KAAK,iBAAiB,MAAM,MAAQ,EAAG,QAAQ,EAAI,CAAC,GAE5D,KAIF,KAAK,WACR,EAAG,aAAa,OAAO,KAAK,KAAK,WACjC,CAAC,EAAG,aAAa,OAAO,GAI1B,KAAK,WACA,KAMF,EAAK,aAAa,KAAK,aAAa,EAAK,cAC5C,EAAK,YAAY,MAAM,CAAC,SAAS,IACjC;IACJ,CAC0B,SAAS;;CAGvC,eAA6B;;EAE3B,IAAM,KAAA,IAAO,KAAK,KAAK,eAAA,OAAA,KAAA,IAAA,EAAY,cACjC,KAAK,WAAW,cAAc,KAAK,SAAS,MAAM,mBACnD;AACD,MAAI,GAAM;GACR,IAAM,IAAQ,EAAK,cAAc,EAAE,SAAS,IAAM,CAAC;AACnD,QAAK,cAAc,EAAM;;;CAI7B,gBAAsB;AAKpB,EAJA,KAAK,SAAS,QAAQ,KAAK,MAAM;GAC/B,eAAe;GACf,SAAS;GACV,CAAC,EACF,KAAK,qBAAqB;;CAG5B,mBAAyB;AACvB,OAAK,SAAS,YAAY"}
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Copyright 2026 Adobe. All rights reserved.
3
+ * This file is licensed to you under the Apache License, Version 2.0 (the "License");
4
+ * you may not use this file except in compliance with the License. You may obtain a copy
5
+ * of the License at http://www.apache.org/licenses/LICENSE-2.0
6
+ *
7
+ * Unless required by applicable law or agreed to in writing, software distributed under
8
+ * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
9
+ * OF ANY KIND, either express or implied. See the License for the specific language
10
+ * governing permissions and limitations under the License.
11
+ */
12
+ export { TriggerPressController, type TriggerPressControllerOptions, } from './src/trigger-press-controller.js';
@@ -0,0 +1,2 @@
1
+ import { TriggerPressController as e } from "./src/trigger-press-controller.js";
2
+ export { e as TriggerPressController };
@@ -0,0 +1,149 @@
1
+ import { ReactiveController, ReactiveControllerHost } from 'lit';
2
+ /**
3
+ * Options for {@link TriggerPressController.attach}.
4
+ */
5
+ export interface TriggerPressControllerOptions {
6
+ /**
7
+ * Called on a trigger click that is not consumed as a same-gesture reopen
8
+ * guard (see {@link TriggerPressController} for when a click is
9
+ * consumed instead). Typically flips the host's own `open` property.
10
+ */
11
+ onToggle: () => void;
12
+ }
13
+ /**
14
+ * **TriggerPressController** — click-to-toggle wiring for a trigger that
15
+ * opens a native light-dismissible surface (`popover="auto"`, a non-modal
16
+ * `<dialog>`, or anything else the platform can close on its own), without
17
+ * the surface reopening on the same click that was meant to close it.
18
+ *
19
+ * ### The bug this prevents
20
+ *
21
+ * A naive click-to-toggle handler (`trigger.onclick = () => (open = !open)`)
22
+ * looks correct, but breaks the moment the surface can close itself: pressing
23
+ * the trigger again while the surface is open is, from the surface's own
24
+ * perspective, a press *outside* it. The platform's light-dismiss closes the
25
+ * surface **before** the trailing `click` event fires on the trigger. By the
26
+ * time that `click` handler runs, `open` already reads `false` (synced down
27
+ * by the dismissal), so `!open` flips it back to `true` — the surface
28
+ * silently reopens on the very click that should have closed it.
29
+ *
30
+ * ### How it's fixed
31
+ *
32
+ * A capturing `pointerdown`/`touchstart` listener on the trigger opens a
33
+ * short "gesture window" before the platform gets a chance to light-dismiss.
34
+ * The consumer calls {@link TriggerPressController.noteNativeDismiss}
35
+ * from its own native-close handler (for example a `popover` element's
36
+ * `beforetoggle` reaction) whenever it observes a close that was **not**
37
+ * driven by its own `open` setter. If that close lands inside the gesture
38
+ * window, the controller remembers it, and the trailing `click` — read by
39
+ * this controller's own listener — is consumed instead of calling
40
+ * {@link TriggerPressControllerOptions.onToggle}. A close observed
41
+ * outside the gesture window (Escape, an outside click elsewhere, a
42
+ * programmatic close) is not affected.
43
+ *
44
+ * This is a JS-only fix because a custom element's shadow-internal surface
45
+ * cannot be associated with an external trigger via the native
46
+ * `popovertarget` attribute (that attribute cannot reach across shadow
47
+ * boundaries), so the platform has no way to know the trigger and the
48
+ * surface belong together.
49
+ *
50
+ * ### Usage
51
+ *
52
+ * Construct once per host, then call {@link TriggerPressController.attach}
53
+ * every time the resolved trigger element changes (attaching the same
54
+ * element again is a no-op for the listeners, but still updates the
55
+ * {@link TriggerPressControllerOptions.onToggle} callback). Call
56
+ * {@link TriggerPressController.noteNativeDismiss} from the surface's own
57
+ * native-close reaction, guarded by whatever the host already uses to tell a
58
+ * genuine native dismissal apart from its own programmatic close (typically:
59
+ * the close reaction fires while the host's `open` property is still `true`).
60
+ *
61
+ * @example
62
+ * ```typescript
63
+ * class MyToggle extends LitElement {
64
+ * @property({ type: Boolean, reflect: true }) open = false;
65
+ *
66
+ * private readonly pressGuard = new TriggerPressController(this);
67
+ *
68
+ * private wireTrigger(trigger: HTMLElement | null): void {
69
+ * this.pressGuard.attach(trigger, {
70
+ * onToggle: () => (this.open = !this.open),
71
+ * });
72
+ * }
73
+ *
74
+ * // Bound to the surface's `beforetoggle` in render().
75
+ * private onBeforeToggle = (event: ToggleEvent): void => {
76
+ * if (event.newState === 'open') {
77
+ * return;
78
+ * }
79
+ * if (this.open) {
80
+ * // Not already closed via our own setter, so this is a genuine
81
+ * // native dismissal (Escape, outside click, ...).
82
+ * this.pressGuard.noteNativeDismiss();
83
+ * }
84
+ * this.open = false;
85
+ * };
86
+ * }
87
+ * ```
88
+ */
89
+ export declare class TriggerPressController implements ReactiveController {
90
+ /** The element currently carrying the press/click listeners. */
91
+ private trigger;
92
+ private options;
93
+ /**
94
+ * True between a trigger press start (`pointerdown`/`touchstart`) and its
95
+ * `click`, so a native dismissal observed inside that window is attributed
96
+ * to the press.
97
+ */
98
+ private pointerActive;
99
+ /** Cancels the pending press-end listeners armed in {@link onPressStart}. */
100
+ private pressEndAbort;
101
+ /**
102
+ * Set by {@link noteNativeDismiss} when a native dismissal is observed
103
+ * while {@link pointerActive}, so the trailing `click` of that same
104
+ * gesture is read as the close rather than a reopen.
105
+ */
106
+ private dismissedByPress;
107
+ /**
108
+ * Registers this controller on `host` via `addController`.
109
+ *
110
+ * @param host - Reactive element whose trigger this controller wires.
111
+ */
112
+ constructor(host: ReactiveControllerHost);
113
+ /**
114
+ * Wires (or rewires) the click-to-toggle and press-tracking listeners.
115
+ *
116
+ * A no-op for the listeners themselves when `trigger` is the same element
117
+ * already attached — safe to call on every re-resolution of the trigger
118
+ * (for example from a Lit `updated()` reacting to `for`/`triggerElement`
119
+ * changes) without needing to compare against the previous element
120
+ * yourself. `options` is always updated, even when `trigger` is unchanged.
121
+ *
122
+ * @param trigger - Element to wire, or `null` to only detach.
123
+ * @param options - Callback invoked on an un-consumed click.
124
+ */
125
+ attach(trigger: HTMLElement | null, options: TriggerPressControllerOptions): void;
126
+ /**
127
+ * Removes the currently-wired listeners (if any) and resets gesture state.
128
+ * Safe to call multiple times. Also called automatically by
129
+ * {@link hostDisconnected}.
130
+ */
131
+ detach(): void;
132
+ /**
133
+ * Call from the surface's own native-close reaction when the close was
134
+ * **not** driven by the host's own `open` setter (a genuine native
135
+ * dismissal: Escape, an outside click, or the platform closing it for any
136
+ * other reason). No-op unless a press on the attached trigger is currently
137
+ * in flight; that gesture window is the only time a dismissal needs to be
138
+ * correlated with the trailing click.
139
+ */
140
+ noteNativeDismiss(): void;
141
+ /**
142
+ * Lit `ReactiveController` hook: tears down listeners when the host
143
+ * disconnects.
144
+ */
145
+ hostDisconnected(): void;
146
+ private readonly onPressStart;
147
+ private readonly onPressEnd;
148
+ private readonly onClick;
149
+ }
@@ -0,0 +1,43 @@
1
+ //#region controllers/trigger-press-controller/src/trigger-press-controller.ts
2
+ var e = class {
3
+ constructor(e) {
4
+ this.trigger = null, this.options = null, this.pointerActive = !1, this.pressEndAbort = null, this.dismissedByPress = !1, this.onPressStart = () => {
5
+ var e;
6
+ this.pointerActive = !0, (e = this.pressEndAbort) == null || e.abort(), this.pressEndAbort = new AbortController();
7
+ let { signal: t } = this.pressEndAbort;
8
+ document.addEventListener("pointerup", this.onPressEnd, {
9
+ capture: !0,
10
+ once: !0,
11
+ signal: t
12
+ }), document.addEventListener("pointercancel", this.onPressEnd, {
13
+ capture: !0,
14
+ once: !0,
15
+ signal: t
16
+ });
17
+ }, this.onPressEnd = (e) => {
18
+ var t;
19
+ (t = this.pressEndAbort) == null || t.abort(), !(e.type === "pointerup" && this.trigger !== null && e.composedPath().includes(this.trigger)) && (this.pointerActive = !1, this.dismissedByPress = !1);
20
+ }, this.onClick = () => {
21
+ var e;
22
+ let t = this.dismissedByPress;
23
+ this.pointerActive = !1, this.dismissedByPress = !1, !t && ((e = this.options) == null || e.onToggle());
24
+ }, e.addController(this);
25
+ }
26
+ attach(e, t) {
27
+ this.options = t, e !== this.trigger && (this.detach(), this.trigger = e, e && (e.addEventListener("pointerdown", this.onPressStart, { capture: !0 }), e.addEventListener("touchstart", this.onPressStart, { capture: !0 }), e.addEventListener("click", this.onClick)));
28
+ }
29
+ detach() {
30
+ var e, t, n, r;
31
+ (e = this.trigger) == null || e.removeEventListener("pointerdown", this.onPressStart, { capture: !0 }), (t = this.trigger) == null || t.removeEventListener("touchstart", this.onPressStart, { capture: !0 }), (n = this.trigger) == null || n.removeEventListener("click", this.onClick), this.trigger = null, (r = this.pressEndAbort) == null || r.abort(), this.pressEndAbort = null, this.pointerActive = !1, this.dismissedByPress = !1;
32
+ }
33
+ noteNativeDismiss() {
34
+ this.pointerActive && (this.dismissedByPress = !0);
35
+ }
36
+ hostDisconnected() {
37
+ this.detach();
38
+ }
39
+ };
40
+ //#endregion
41
+ export { e as TriggerPressController };
42
+
43
+ //# sourceMappingURL=trigger-press-controller.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"trigger-press-controller.js","names":[],"sources":["../../../../controllers/trigger-press-controller/src/trigger-press-controller.ts"],"sourcesContent":["/**\n * Copyright 2026 Adobe. All rights reserved.\n * This file is licensed to you under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License. You may obtain a copy\n * of the License at http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software distributed under\n * the License is distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS\n * OF ANY KIND, either express or implied. See the License for the specific language\n * governing permissions and limitations under the License.\n */\n\nimport type { ReactiveController, ReactiveControllerHost } from 'lit';\n\n/**\n * Options for {@link TriggerPressController.attach}.\n */\nexport interface TriggerPressControllerOptions {\n /**\n * Called on a trigger click that is not consumed as a same-gesture reopen\n * guard (see {@link TriggerPressController} for when a click is\n * consumed instead). Typically flips the host's own `open` property.\n */\n onToggle: () => void;\n}\n\n/**\n * **TriggerPressController** — click-to-toggle wiring for a trigger that\n * opens a native light-dismissible surface (`popover=\"auto\"`, a non-modal\n * `<dialog>`, or anything else the platform can close on its own), without\n * the surface reopening on the same click that was meant to close it.\n *\n * ### The bug this prevents\n *\n * A naive click-to-toggle handler (`trigger.onclick = () => (open = !open)`)\n * looks correct, but breaks the moment the surface can close itself: pressing\n * the trigger again while the surface is open is, from the surface's own\n * perspective, a press *outside* it. The platform's light-dismiss closes the\n * surface **before** the trailing `click` event fires on the trigger. By the\n * time that `click` handler runs, `open` already reads `false` (synced down\n * by the dismissal), so `!open` flips it back to `true` — the surface\n * silently reopens on the very click that should have closed it.\n *\n * ### How it's fixed\n *\n * A capturing `pointerdown`/`touchstart` listener on the trigger opens a\n * short \"gesture window\" before the platform gets a chance to light-dismiss.\n * The consumer calls {@link TriggerPressController.noteNativeDismiss}\n * from its own native-close handler (for example a `popover` element's\n * `beforetoggle` reaction) whenever it observes a close that was **not**\n * driven by its own `open` setter. If that close lands inside the gesture\n * window, the controller remembers it, and the trailing `click` — read by\n * this controller's own listener — is consumed instead of calling\n * {@link TriggerPressControllerOptions.onToggle}. A close observed\n * outside the gesture window (Escape, an outside click elsewhere, a\n * programmatic close) is not affected.\n *\n * This is a JS-only fix because a custom element's shadow-internal surface\n * cannot be associated with an external trigger via the native\n * `popovertarget` attribute (that attribute cannot reach across shadow\n * boundaries), so the platform has no way to know the trigger and the\n * surface belong together.\n *\n * ### Usage\n *\n * Construct once per host, then call {@link TriggerPressController.attach}\n * every time the resolved trigger element changes (attaching the same\n * element again is a no-op for the listeners, but still updates the\n * {@link TriggerPressControllerOptions.onToggle} callback). Call\n * {@link TriggerPressController.noteNativeDismiss} from the surface's own\n * native-close reaction, guarded by whatever the host already uses to tell a\n * genuine native dismissal apart from its own programmatic close (typically:\n * the close reaction fires while the host's `open` property is still `true`).\n *\n * @example\n * ```typescript\n * class MyToggle extends LitElement {\n * @property({ type: Boolean, reflect: true }) open = false;\n *\n * private readonly pressGuard = new TriggerPressController(this);\n *\n * private wireTrigger(trigger: HTMLElement | null): void {\n * this.pressGuard.attach(trigger, {\n * onToggle: () => (this.open = !this.open),\n * });\n * }\n *\n * // Bound to the surface's `beforetoggle` in render().\n * private onBeforeToggle = (event: ToggleEvent): void => {\n * if (event.newState === 'open') {\n * return;\n * }\n * if (this.open) {\n * // Not already closed via our own setter, so this is a genuine\n * // native dismissal (Escape, outside click, ...).\n * this.pressGuard.noteNativeDismiss();\n * }\n * this.open = false;\n * };\n * }\n * ```\n */\nexport class TriggerPressController implements ReactiveController {\n /** The element currently carrying the press/click listeners. */\n private trigger: HTMLElement | null = null;\n\n private options: TriggerPressControllerOptions | null = null;\n\n /**\n * True between a trigger press start (`pointerdown`/`touchstart`) and its\n * `click`, so a native dismissal observed inside that window is attributed\n * to the press.\n */\n private pointerActive = false;\n\n /** Cancels the pending press-end listeners armed in {@link onPressStart}. */\n private pressEndAbort: AbortController | null = null;\n\n /**\n * Set by {@link noteNativeDismiss} when a native dismissal is observed\n * while {@link pointerActive}, so the trailing `click` of that same\n * gesture is read as the close rather than a reopen.\n */\n private dismissedByPress = false;\n\n /**\n * Registers this controller on `host` via `addController`.\n *\n * @param host - Reactive element whose trigger this controller wires.\n */\n constructor(host: ReactiveControllerHost) {\n host.addController(this);\n }\n\n /**\n * Wires (or rewires) the click-to-toggle and press-tracking listeners.\n *\n * A no-op for the listeners themselves when `trigger` is the same element\n * already attached — safe to call on every re-resolution of the trigger\n * (for example from a Lit `updated()` reacting to `for`/`triggerElement`\n * changes) without needing to compare against the previous element\n * yourself. `options` is always updated, even when `trigger` is unchanged.\n *\n * @param trigger - Element to wire, or `null` to only detach.\n * @param options - Callback invoked on an un-consumed click.\n */\n public attach(\n trigger: HTMLElement | null,\n options: TriggerPressControllerOptions\n ): void {\n this.options = options;\n if (trigger === this.trigger) {\n return;\n }\n this.detach();\n this.trigger = trigger;\n if (trigger) {\n trigger.addEventListener('pointerdown', this.onPressStart, {\n capture: true,\n });\n trigger.addEventListener('touchstart', this.onPressStart, {\n capture: true,\n });\n trigger.addEventListener('click', this.onClick);\n }\n }\n\n /**\n * Removes the currently-wired listeners (if any) and resets gesture state.\n * Safe to call multiple times. Also called automatically by\n * {@link hostDisconnected}.\n */\n public detach(): void {\n this.trigger?.removeEventListener('pointerdown', this.onPressStart, {\n capture: true,\n });\n this.trigger?.removeEventListener('touchstart', this.onPressStart, {\n capture: true,\n });\n this.trigger?.removeEventListener('click', this.onClick);\n this.trigger = null;\n this.pressEndAbort?.abort();\n this.pressEndAbort = null;\n this.pointerActive = false;\n this.dismissedByPress = false;\n }\n\n /**\n * Call from the surface's own native-close reaction when the close was\n * **not** driven by the host's own `open` setter (a genuine native\n * dismissal: Escape, an outside click, or the platform closing it for any\n * other reason). No-op unless a press on the attached trigger is currently\n * in flight; that gesture window is the only time a dismissal needs to be\n * correlated with the trailing click.\n */\n public noteNativeDismiss(): void {\n if (this.pointerActive) {\n this.dismissedByPress = true;\n }\n }\n\n /**\n * Lit `ReactiveController` hook: tears down listeners when the host\n * disconnects.\n */\n public hostDisconnected(): void {\n this.detach();\n }\n\n // ─────────────────────────\n // IMPLEMENTATION\n // ─────────────────────────\n\n // Capture phase, before a native light-dismiss (which runs on the same\n // press) gets a chance to close the surface first.\n private readonly onPressStart = (): void => {\n this.pointerActive = true;\n // A touch gesture fires both `pointerdown` and `touchstart` on the same\n // trigger, so this runs twice per press. Without aborting the previous\n // controller first, the second `addEventListener` below is a no-op (the\n // DOM dedupes on type/listener/capture, ignoring the new `signal`), so\n // `pressEndAbort` would end up pointing at a controller with nothing\n // attached to it while the real listeners stay tied to the first one.\n this.pressEndAbort?.abort();\n // Catches a press that ends without a click (drag off the trigger, or a\n // cancelled gesture), which would otherwise leave the flag stuck true.\n this.pressEndAbort = new AbortController();\n const { signal } = this.pressEndAbort;\n document.addEventListener('pointerup', this.onPressEnd, {\n capture: true,\n once: true,\n signal,\n });\n document.addEventListener('pointercancel', this.onPressEnd, {\n capture: true,\n once: true,\n signal,\n });\n };\n\n private readonly onPressEnd = (event: PointerEvent): void => {\n this.pressEndAbort?.abort();\n // A same-target pointerup still gets its own click, which reads and\n // clears `dismissedByPress` itself; clearing it here too would race that\n // read. Any other press end (pointercancel, or a pointerup that lands off\n // the trigger) means no click is coming for this gesture, so both flags\n // are cleared here instead — otherwise a dismissal noted during a press\n // that never resolves to a click would incorrectly consume the next,\n // unrelated click on the trigger.\n if (\n event.type === 'pointerup' &&\n this.trigger !== null &&\n event.composedPath().includes(this.trigger)\n ) {\n return;\n }\n this.pointerActive = false;\n this.dismissedByPress = false;\n };\n\n // If a native dismissal already consumed this gesture (recorded via\n // `noteNativeDismiss`), consume this click so it does not toggle back\n // open; otherwise call the consumer's own toggle.\n private readonly onClick = (): void => {\n const dismissedByThisGesture = this.dismissedByPress;\n this.pointerActive = false;\n this.dismissedByPress = false;\n if (dismissedByThisGesture) {\n return;\n }\n this.options?.onToggle();\n };\n}\n"],"mappings":";AAsGA,IAAa,IAAb,MAAkE;CA4BhE,YAAY,GAA8B;AACxC,iBA3BoC,qBAEkB,2BAOhC,yBAGwB,8BAOrB,8BA4FiB;;AAW1C,GAVA,KAAK,gBAAgB,KAOrB,IAAA,KAAK,kBAAA,QAAA,EAAe,OAAO,EAG3B,KAAK,gBAAgB,IAAI,iBAAiB;GAC1C,IAAM,EAAE,cAAW,KAAK;AAMxB,GALA,SAAS,iBAAiB,aAAa,KAAK,YAAY;IACtD,SAAS;IACT,MAAM;IACN;IACD,CAAC,EACF,SAAS,iBAAiB,iBAAiB,KAAK,YAAY;IAC1D,SAAS;IACT,MAAM;IACN;IACD,CAAC;wBAG2B,MAA8B;;AAC3D,IAAA,IAAA,KAAK,kBAAA,QAAA,EAAe,OAAO,EASzB,IAAM,SAAS,eACf,KAAK,YAAY,QACjB,EAAM,cAAc,CAAC,SAAS,KAAK,QAAQ,MAI7C,KAAK,gBAAgB,IACrB,KAAK,mBAAmB;0BAMa;;GACrC,IAAM,IAAyB,KAAK;AACpC,QAAK,gBAAgB,IACrB,KAAK,mBAAmB,IACpB,QAGJ,IAAA,KAAK,YAAA,QAAA,EAAS,UAAU;KA3IxB,EAAK,cAAc,KAAK;;CAe1B,OACE,GACA,GACM;AACN,OAAK,UAAU,GACX,MAAY,KAAK,YAGrB,KAAK,QAAQ,EACb,KAAK,UAAU,GACX,MACF,EAAQ,iBAAiB,eAAe,KAAK,cAAc,EACzD,SAAS,IACV,CAAC,EACF,EAAQ,iBAAiB,cAAc,KAAK,cAAc,EACxD,SAAS,IACV,CAAC,EACF,EAAQ,iBAAiB,SAAS,KAAK,QAAQ;;CASnD,SAAsB;;AAYpB,GAXA,IAAA,KAAK,YAAA,QAAA,EAAS,oBAAoB,eAAe,KAAK,cAAc,EAClE,SAAS,IACV,CAAC,GACF,IAAA,KAAK,YAAA,QAAA,EAAS,oBAAoB,cAAc,KAAK,cAAc,EACjE,SAAS,IACV,CAAC,GACF,IAAA,KAAK,YAAA,QAAA,EAAS,oBAAoB,SAAS,KAAK,QAAQ,EACxD,KAAK,UAAU,OACf,IAAA,KAAK,kBAAA,QAAA,EAAe,OAAO,EAC3B,KAAK,gBAAgB,MACrB,KAAK,gBAAgB,IACrB,KAAK,mBAAmB;;CAW1B,oBAAiC;AAC/B,EAAI,KAAK,kBACP,KAAK,mBAAmB;;CAQ5B,mBAAgC;AAC9B,OAAK,QAAQ"}
@@ -10,6 +10,6 @@
10
10
  * governing permissions and limitations under the License.
11
11
  */
12
12
  /**
13
- * Public exports for render-only Lit template directives shared across 2nd-gen packages.
13
+ * Public exports for render-only Lit template directives shared across gen2 packages.
14
14
  */
15
15
  export { renderPendingSpinner, type PendingSpinnerResult, } from './pending-spinner/index.js';
@@ -1,6 +1,6 @@
1
1
  //#region element/define-element.ts
2
2
  function e(e, t) {
3
- window.__swc && window.__swc.DEBUG && customElements.get(e) && window.__swc.warn(void 0, `Attempted to redefine <${e}>. This usually indicates that multiple versions of the same web component were loaded onto a single page.`, "https://opensource.adobe.com/spectrum-web-components/registry-conflicts"), customElements.define(e, t);
3
+ window.__swc && window.__swc.DEBUG && customElements.get(e) && window.__swc.warn(void 0, `Attempted to redefine <${e}>. This usually indicates that multiple versions of the same web component were loaded onto a single page.`, "https://spectrum-web-components.adobe.com/registry-conflicts"), customElements.define(e, t);
4
4
  }
5
5
  //#endregion
6
6
  export { e as defineElement };
@@ -1 +1 @@
1
- {"version":3,"file":"define-element.js","names":[],"sources":["../../element/define-element.ts"],"sourcesContent":["/**\n * Copyright 2026 Adobe. All rights reserved.\n * This file is licensed to you under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License. You may obtain a copy\n * of the License at http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software distributed under\n * the License is distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS\n * OF ANY KIND, either express or implied. See the License for the specific language\n * governing permissions and limitations under the License.\n */\n\ninterface CustomElementConstructor {\n new (...params: unknown[]): HTMLElement;\n}\n\nexport function defineElement(\n name: string,\n constructor: CustomElementConstructor\n): void {\n if (window.__swc && window.__swc.DEBUG) {\n if (customElements.get(name)) {\n window.__swc.warn(\n undefined,\n `Attempted to redefine <${name}>. This usually indicates that multiple versions of the same web component were loaded onto a single page.`,\n 'https://opensource.adobe.com/spectrum-web-components/registry-conflicts'\n );\n }\n }\n customElements.define(name, constructor);\n}\n"],"mappings":";AAgBA,SAAgB,EACd,GACA,GACM;AAUN,CATI,OAAO,SAAS,OAAO,MAAM,SAC3B,eAAe,IAAI,EAAK,IAC1B,OAAO,MAAM,KACX,KAAA,GACA,0BAA0B,EAAK,6GAC/B,0EACD,EAGL,eAAe,OAAO,GAAM,EAAY"}
1
+ {"version":3,"file":"define-element.js","names":[],"sources":["../../element/define-element.ts"],"sourcesContent":["/**\n * Copyright 2026 Adobe. All rights reserved.\n * This file is licensed to you under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License. You may obtain a copy\n * of the License at http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software distributed under\n * the License is distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS\n * OF ANY KIND, either express or implied. See the License for the specific language\n * governing permissions and limitations under the License.\n */\n\ninterface CustomElementConstructor {\n new (...params: unknown[]): HTMLElement;\n}\n\nexport function defineElement(\n name: string,\n constructor: CustomElementConstructor\n): void {\n if (window.__swc && window.__swc.DEBUG) {\n if (customElements.get(name)) {\n window.__swc.warn(\n undefined,\n `Attempted to redefine <${name}>. This usually indicates that multiple versions of the same web component were loaded onto a single page.`,\n 'https://spectrum-web-components.adobe.com/registry-conflicts'\n );\n }\n }\n customElements.define(name, constructor);\n}\n"],"mappings":";AAgBA,SAAgB,EACd,GACA,GACM;AAUN,CATI,OAAO,SAAS,OAAO,MAAM,SAC3B,eAAe,IAAI,EAAK,IAC1B,OAAO,MAAM,KACX,KAAA,GACA,0BAA0B,EAAK,6GAC/B,+DACD,EAGL,eAAe,OAAO,GAAM,EAAY"}
@@ -10,5 +10,5 @@
10
10
  * governing permissions and limitations under the License.
11
11
  */
12
12
  export { defineElement } from './define-element.js';
13
- export { SpectrumElement, SpectrumMixin, type SpectrumInterface, } from './spectrum-element.js';
13
+ export { SpectrumElement } from './spectrum-element.js';
14
14
  export { coreVersion, version } from './version.js';
@@ -1,4 +1,4 @@
1
1
  import { defineElement as e } from "./define-element.js";
2
2
  import { coreVersion as t, version as n } from "./version.js";
3
- import { SpectrumElement as r, SpectrumMixin as i } from "./spectrum-element.js";
4
- export { r as SpectrumElement, i as SpectrumMixin, t as coreVersion, e as defineElement, n as version };
3
+ import { SpectrumElement as r } from "./spectrum-element.js";
4
+ export { r as SpectrumElement, t as coreVersion, e as defineElement, n as version };
@@ -1,15 +1,5 @@
1
- import { LitElement, ReactiveElement } from 'lit';
2
- type Constructor<T = Record<string, unknown>> = {
3
- new (...args: any[]): T;
4
- prototype: T;
5
- };
6
- export interface SpectrumInterface {
7
- shadowRoot: ShadowRoot;
8
- hasVisibleFocusInTree(): boolean;
9
- }
10
- export declare function SpectrumMixin<T extends Constructor<ReactiveElement>>(constructor: T): T & Constructor<SpectrumInterface>;
11
- declare const SpectrumElement_base: typeof LitElement & Constructor<SpectrumInterface>;
12
- export declare class SpectrumElement extends SpectrumElement_base {
1
+ import { LitElement } from 'lit';
2
+ export declare class SpectrumElement extends LitElement {
13
3
  /**
14
4
  * @internal
15
5
  */
@@ -18,9 +8,62 @@ export declare class SpectrumElement extends SpectrumElement_base {
18
8
  * @internal
19
9
  */
20
10
  static CORE_VERSION: string;
11
+ }
12
+ /**
13
+ * Builds the deduplication key for a dev-mode warning. The `message` is part of
14
+ * the key so two distinct problems that share a `type`/`level` (the common
15
+ * case) do not suppress each other; only a verbatim repeat of the same warning
16
+ * is deduplicated. Exported so the dedup key can be unit tested without relying
17
+ * on the `NODE_ENV`-gated `window.__swc` setup below.
18
+ */
19
+ export declare function warningId(localName: string, type: WarningType, level: WarningLevel, message: string): BrandedSWCWarningID;
20
+ /**
21
+ * A group of dev-mode warnings that share a `warningId`, collected within a
22
+ * single microtask so they can be emitted as one console line with a count.
23
+ */
24
+ type BatchedWarning = {
25
+ message: string;
26
+ url: string;
27
+ localName: string;
28
+ type: WarningType;
29
+ level: WarningLevel;
30
+ listedIssues: string;
31
+ elements: HTMLElement[];
32
+ count: number;
21
33
  /**
22
- * @internal
34
+ * Elements already counted in this batch, keyed by identity. A repeated
35
+ * `warn()` for an element already here does not bump `count` again.
23
36
  */
24
- get dir(): CSSStyleDeclaration['direction'];
25
- }
37
+ countedElements?: Set<HTMLElement>;
38
+ /**
39
+ * Whether the dev-mode warning (the only warning with no element) has been
40
+ * counted, so repeats of it collapse to one.
41
+ */
42
+ countedDevModeWarning?: boolean;
43
+ };
44
+ /**
45
+ * Builds the `console.warn` arguments for a grouped dev warning: the message
46
+ * and a count line, then the element ref(s) to inspect. By default one
47
+ * representative element is shown; `verbose` shows every collected element
48
+ * (already capped at collection time). Exported so the grouped output can be
49
+ * unit tested without the `NODE_ENV`-gated `window.__swc` setup below.
50
+ *
51
+ * @internal
52
+ */
53
+ export declare function buildGroupedWarningArgs(batch: BatchedWarning, verbose: boolean): unknown[];
54
+ /**
55
+ * Records one warning occurrence into its batch, counting by element identity:
56
+ * `count` becomes the number of distinct elements with this warning, not the
57
+ * number of `warn()` calls. A host that warns once per child (same message,
58
+ * same element) still counts as one affected element. `count` is uncapped;
59
+ * `maxDisplayed` only limits how many element refs are kept for display.
60
+ *
61
+ * @param batch - Grouped-warning batch to record into.
62
+ * @param element - Element the warning is about, or `undefined` for the
63
+ * dev-mode warning, which is not about a specific element.
64
+ * @param maxDisplayed - Cap on element refs kept for display; does not affect
65
+ * `count`.
66
+ * @internal
67
+ */
68
+ export declare function recordAffectedElement(batch: BatchedWarning, element: HTMLElement | undefined, maxDisplayed: number): void;
26
69
  export {};