luxen-ui 0.19.0 → 0.20.0

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 (103) hide show
  1. package/README.md +1 -0
  2. package/cdn/custom-elements.json +99 -5
  3. package/cdn/elements/button-group/button-group.d.ts +12 -7
  4. package/cdn/elements/button-group/button-group.d.ts.map +1 -1
  5. package/cdn/elements/button-group/button-group.js.map +1 -1
  6. package/cdn/elements/input-stepper/input-stepper.d.ts +1 -1
  7. package/cdn/elements/input-stepper/input-stepper.js.map +1 -1
  8. package/cdn/elements/popover/popover.d.ts +15 -0
  9. package/cdn/elements/popover/popover.d.ts.map +1 -1
  10. package/cdn/elements/popover/popover.js +1 -1
  11. package/cdn/elements/popover/popover.js.map +1 -1
  12. package/cdn/elements/radio-group/radio-group.meta.d.ts +41 -0
  13. package/cdn/elements/radio-group/radio-group.meta.d.ts.map +1 -0
  14. package/cdn/elements/radio-group/radio-group.meta.js +0 -0
  15. package/cdn/elements/segmented-control/segmented-control.d.ts +8 -4
  16. package/cdn/elements/segmented-control/segmented-control.d.ts.map +1 -1
  17. package/cdn/elements/segmented-control/segmented-control.js.map +1 -1
  18. package/cdn/elements/select/select.js +1 -1
  19. package/cdn/elements/select/select.js.map +1 -1
  20. package/cdn/elements/tag/tag.d.ts +3 -1
  21. package/cdn/elements/tag/tag.d.ts.map +1 -1
  22. package/cdn/elements/tag/tag.js +1 -1
  23. package/cdn/elements/tag/tag.js.map +1 -1
  24. package/cdn/elements/tooltip/tooltip.d.ts +15 -0
  25. package/cdn/elements/tooltip/tooltip.d.ts.map +1 -1
  26. package/cdn/elements/tooltip/tooltip.js +1 -1
  27. package/cdn/elements/tooltip/tooltip.js.map +1 -1
  28. package/cdn/shared/controllers/popover.d.ts +40 -0
  29. package/cdn/shared/controllers/popover.d.ts.map +1 -1
  30. package/cdn/shared/controllers/popover.js +1 -1
  31. package/cdn/shared/controllers/popover.js.map +1 -1
  32. package/cdn/shared/styles/button-core.styles.js +1 -1
  33. package/cdn/shared/styles/button-core.styles.js.map +1 -1
  34. package/cdn/standalone.css +774 -28
  35. package/cdn/styles/elements/button-group.css +4 -3
  36. package/cdn/styles/elements/button.css +28 -1
  37. package/cdn/styles/elements/disclosure.css +3 -3
  38. package/cdn/styles/elements/input-stepper/default.css +29 -2
  39. package/cdn/styles/elements/input-stepper/pill.css +420 -0
  40. package/cdn/styles/elements/input-stepper/rounded.css +69 -8
  41. package/cdn/styles/elements/kbd.css +2 -2
  42. package/cdn/styles/elements/radio-group.css +206 -0
  43. package/cdn/styles/elements/segmented-control.css +3 -3
  44. package/cdn/styles/elements/tabs/enclosed.css +4 -4
  45. package/cdn/styles/elements/tabs/line.css +2 -2
  46. package/dist/css/elements/button-group.css +4 -3
  47. package/dist/css/elements/button.css +28 -1
  48. package/dist/css/elements/disclosure.css +3 -3
  49. package/dist/css/elements/input-stepper/default.css +29 -2
  50. package/dist/css/elements/input-stepper/pill.css +420 -0
  51. package/dist/css/elements/input-stepper/rounded.css +69 -8
  52. package/dist/css/elements/kbd.css +2 -2
  53. package/dist/css/elements/radio-group.css +206 -0
  54. package/dist/css/elements/segmented-control.css +3 -3
  55. package/dist/css/elements/tabs/enclosed.css +4 -4
  56. package/dist/css/elements/tabs/line.css +2 -2
  57. package/dist/custom-elements.json +99 -5
  58. package/dist/elements/button-group/button-group.d.ts +12 -7
  59. package/dist/elements/button-group/button-group.d.ts.map +1 -1
  60. package/dist/elements/button-group/button-group.js +12 -7
  61. package/dist/elements/input-stepper/input-stepper.d.ts +1 -1
  62. package/dist/elements/input-stepper/input-stepper.js +1 -1
  63. package/dist/elements/popover/popover.d.ts +15 -0
  64. package/dist/elements/popover/popover.d.ts.map +1 -1
  65. package/dist/elements/popover/popover.js +61 -14
  66. package/dist/elements/radio-group/radio-group.meta.d.ts +41 -0
  67. package/dist/elements/radio-group/radio-group.meta.d.ts.map +1 -0
  68. package/dist/elements/radio-group/radio-group.meta.js +51 -0
  69. package/dist/elements/segmented-control/segmented-control.d.ts +8 -4
  70. package/dist/elements/segmented-control/segmented-control.d.ts.map +1 -1
  71. package/dist/elements/segmented-control/segmented-control.js +8 -4
  72. package/dist/elements/select/select.css +14 -0
  73. package/dist/elements/tag/tag.css +128 -49
  74. package/dist/elements/tag/tag.d.ts +3 -1
  75. package/dist/elements/tag/tag.d.ts.map +1 -1
  76. package/dist/elements/tag/tag.js +3 -1
  77. package/dist/elements/tooltip/tooltip.d.ts +15 -0
  78. package/dist/elements/tooltip/tooltip.d.ts.map +1 -1
  79. package/dist/elements/tooltip/tooltip.js +61 -14
  80. package/dist/metadata/button-group.json +2 -2
  81. package/dist/metadata/index.json +95 -7
  82. package/dist/metadata/input-stepper.json +3 -2
  83. package/dist/metadata/popover.json +6 -0
  84. package/dist/metadata/radio-group.json +65 -0
  85. package/dist/metadata/segmented-control.json +1 -1
  86. package/dist/metadata/tag.json +11 -1
  87. package/dist/metadata/tooltip.json +6 -0
  88. package/dist/shared/controllers/popover.d.ts +40 -0
  89. package/dist/shared/controllers/popover.d.ts.map +1 -1
  90. package/dist/shared/controllers/popover.js +69 -0
  91. package/dist/shared/styles/button-core.css +28 -1
  92. package/dist/templates/elements/button-group.md +159 -41
  93. package/dist/templates/elements/button.md +1 -1
  94. package/dist/templates/elements/input-stepper.md +220 -58
  95. package/dist/templates/elements/popover.md +3 -0
  96. package/dist/templates/elements/radio-group.md +351 -0
  97. package/dist/templates/elements/radio.md +34 -30
  98. package/dist/templates/elements/segmented-control.md +5 -63
  99. package/dist/templates/elements/tag.md +72 -3
  100. package/dist/templates/elements/tooltip.md +3 -0
  101. package/elements.json +10 -1
  102. package/package.json +1 -1
  103. package/vite-plugin.ts +44 -14
package/README.md CHANGED
@@ -55,6 +55,7 @@ Visit https://luxen-ui.com to explore the documentation.
55
55
  | Progress | `<progress>` | ⏣ Native HTML Element |
56
56
  | Prose editor | `<l-prose-editor>` | ⬢ Custom HTML Element (with Shadow DOM) |
57
57
  | Radio | `<input>` | ⏣ Native HTML Element |
58
+ | Radio group | `<fieldset>` | ⏣ Native HTML Element |
58
59
  | Rating | `<l-rating>` | ⬢ Custom HTML Element (with Shadow DOM) |
59
60
  | Segmented control | `<l-segmented-control>` | ⬡ Progressive Custom HTML Element |
60
61
  | Select | `<l-select>` | ⬢ Custom HTML Element (with Shadow DOM) |
@@ -1241,11 +1241,11 @@
1241
1241
  },
1242
1242
  "tagName": "l-button-group",
1243
1243
  "customElement": true,
1244
- "summary": "Visually joins related `.l-button` elements into a single segmented\ncontrol. The joined appearance is pure CSS; this element adds the group\nsemantics (`role=\"group\"`, `aria-label`).",
1244
+ "summary": "A wrapper for multiple related buttons, joined into a single unit\nwith shared borders. The joined appearance is pure CSS; this element only\nadds the group semantics (`role=\"group\"`, `aria-label`).\n\nThe group never manages the selection: buttons that carry state expose it\nthemselves with `aria-pressed`, and the group holds no value. For a\nsingle-choice control that owns a value and submits it with a form, use\n`l-segmented-control` instead.",
1245
1245
  "examples": [
1246
1246
  {
1247
1247
  "language": "html",
1248
- "code": "<l-button-group label=\"Alignment\">\n <button class=\"l-button\">Left</button>\n <button class=\"l-button\">Center</button>\n <button class=\"l-button\">Right</button>\n</l-button-group>"
1248
+ "code": "<l-button-group label=\"Record actions\">\n <button class=\"l-button\">Edit</button>\n <button class=\"l-button\">Duplicate</button>\n <button class=\"l-button\">Archive</button>\n</l-button-group>"
1249
1249
  }
1250
1250
  ]
1251
1251
  }
@@ -4793,7 +4793,7 @@
4793
4793
  "name": "InputStepper",
4794
4794
  "cssProperties": [
4795
4795
  {
4796
- "description": "Border color of the stepper container (default appearance) and of each button (rounded appearance). Defaults to `--l-color-border`.",
4796
+ "description": "Edge color of the stepper container (default and pill appearances) and of each button (rounded appearance). Defaults to `--l-color-border`.",
4797
4797
  "name": "--border-color"
4798
4798
  },
4799
4799
  {
@@ -5181,6 +5181,11 @@
5181
5181
  {
5182
5182
  "kind": "method",
5183
5183
  "name": "toggle"
5184
+ },
5185
+ {
5186
+ "kind": "method",
5187
+ "name": "reposition",
5188
+ "description": "Recompute the position against the current trigger. Use it when you move\nthe anchor yourself and the panel has to follow. No-op when closed; hides\nif the anchor is gone."
5184
5189
  }
5185
5190
  ],
5186
5191
  "attributes": [
@@ -5971,6 +5976,82 @@
5971
5976
  }
5972
5977
  ]
5973
5978
  },
5979
+ {
5980
+ "kind": "javascript-module",
5981
+ "path": "src/html/elements/radio-group/radio-group.meta.ts",
5982
+ "declarations": [
5983
+ {
5984
+ "kind": "class",
5985
+ "description": "",
5986
+ "name": "RadioGroupMeta",
5987
+ "attributes": [
5988
+ {
5989
+ "description": "Names the group when no visible `<legend>` fits — a view switcher in a toolbar, say. Prefer a `<legend>`: it is the technique WCAG H71 and RGAA 11.6 ask for.",
5990
+ "name": "aria-label"
5991
+ },
5992
+ {
5993
+ "description": "button — Renders the items as joined buttons: put `.l-button` on each label. Omit it for the radio primitive: put `.l-radio` on each input.",
5994
+ "name": "data-appearance"
5995
+ },
5996
+ {
5997
+ "description": "horizontal | vertical — Stacks the items. Defaults to horizontal.",
5998
+ "name": "data-orientation"
5999
+ }
6000
+ ],
6001
+ "summary": "A set of native radios, shown as the classic dot or as joined buttons — the form counterpart of a segmented control.",
6002
+ "examples": [
6003
+ {
6004
+ "title": "Default appearance",
6005
+ "language": "html",
6006
+ "code": "<fieldset class=\"l-radio-group\" data-orientation=\"vertical\">\n<legend>Notifications</legend>\n<label>\n<input type=\"radio\" class=\"l-radio\" name=\"notifications\" value=\"all\" checked />\nAll new messages\n</label>\n<label>\n<input type=\"radio\" class=\"l-radio\" name=\"notifications\" value=\"none\" />\nNothing\n</label>\n</fieldset>"
6007
+ },
6008
+ {
6009
+ "title": "Button appearance",
6010
+ "language": "html",
6011
+ "code": "<fieldset class=\"l-radio-group\" data-appearance=\"button\">\n<legend>View</legend>\n<label class=\"l-button\">\n<input type=\"radio\" name=\"view\" value=\"list\" checked />\nList\n</label>\n<label class=\"l-button\">\n<input type=\"radio\" name=\"view\" value=\"board\" />\nBoard\n</label>\n</fieldset>"
6012
+ }
6013
+ ],
6014
+ "_cssClasses": [
6015
+ {
6016
+ "name": ".l-radio-group",
6017
+ "description": "Applied to the `<fieldset>` grouping the radios. Its first child is the `<legend>` naming them; each item is a `<label>` wrapping one `<input type=\"radio\">`."
6018
+ }
6019
+ ],
6020
+ "customElement": false,
6021
+ "_native": true,
6022
+ "_nativeTag": "fieldset",
6023
+ "selector": ".l-radio-group",
6024
+ "_attributes": [
6025
+ {
6026
+ "name": "aria-label",
6027
+ "description": "Names the group when no visible `<legend>` fits — a view switcher in a toolbar, say. Prefer a `<legend>`: it is the technique WCAG H71 and RGAA 11.6 ask for."
6028
+ },
6029
+ {
6030
+ "name": "data-appearance",
6031
+ "description": "button — Renders the items as joined buttons: put `.l-button` on each label. Omit it for the radio primitive: put `.l-radio` on each input."
6032
+ },
6033
+ {
6034
+ "name": "data-orientation",
6035
+ "values": [
6036
+ "horizontal",
6037
+ "vertical"
6038
+ ],
6039
+ "description": "Stacks the items. Defaults to horizontal."
6040
+ }
6041
+ ]
6042
+ }
6043
+ ],
6044
+ "exports": [
6045
+ {
6046
+ "kind": "js",
6047
+ "name": "RadioGroupMeta",
6048
+ "declaration": {
6049
+ "name": "RadioGroupMeta",
6050
+ "module": "src/html/elements/radio-group/radio-group.meta.ts"
6051
+ }
6052
+ }
6053
+ ]
6054
+ },
5974
6055
  {
5975
6056
  "kind": "javascript-module",
5976
6057
  "path": "src/html/elements/rating/index.ts",
@@ -6387,7 +6468,7 @@
6387
6468
  },
6388
6469
  "tagName": "l-segmented-control",
6389
6470
  "customElement": true,
6390
- "summary": "A single-select segmented control: a compact, mutually-exclusive\nswitch between a few options (radio-group semantics with a sliding pill).\nProgressively enhances light-DOM `<button>`s so it works before JS runs.\nForm-associated: give it a `name` and its selected `value` is submitted with\nthe form (and restored on reset), like a native radio group.",
6471
+ "summary": "A hybrid between a button group, radio buttons, and tabs: pick one\nof a few closely related options or views, and the selection applies\nimmediately (radio-group semantics with a sliding pill).\nProgressively enhances light-DOM `<button>`s so it works before JS runs.\n\nThe immediate effect is the point — act on `change`. The control is\nform-associated as well (`name` submits the selected `value`, reset restores\nit), but a value the user confirms by submitting is better served by native\nradios, which get keyboard, submission and validation from the platform.",
6391
6472
  "examples": [
6392
6473
  {
6393
6474
  "title": "Mark the initially-selected segment with `aria-checked=\"true\"` (or set",
@@ -8928,6 +9009,10 @@
8928
9009
  "description": "Text color.",
8929
9010
  "name": "--color"
8930
9011
  },
9012
+ {
9013
+ "description": "Chip border, and the rule that splits off the remove button. Defaults to a tint of `--color`; setting it pins the line in every state, so a token-based theme keeps its own value on hover.",
9014
+ "name": "--border-color"
9015
+ },
8931
9016
  {
8932
9017
  "description": "Text, border, and checkbox accent when selected. Defaults to the library's form-control accent, lightened in dark mode.",
8933
9018
  "name": "--selected-color"
@@ -8935,6 +9020,10 @@
8935
9020
  {
8936
9021
  "description": "Chip background when selected. Defaults to a tint of `--selected-color`.",
8937
9022
  "name": "--selected-background"
9023
+ },
9024
+ {
9025
+ "description": "Chip border when selected. Defaults to a 45% tint of `--selected-color`. Selection replaces the line the same way it replaces the fill and the ink, so a chip themed with `--border-color` still gets its emphasis step; set this to theme that step too.",
9026
+ "name": "--selected-border-color"
8938
9027
  }
8939
9028
  ],
8940
9029
  "cssParts": [
@@ -8955,7 +9044,7 @@
8955
9044
  "name": "checkbox"
8956
9045
  },
8957
9046
  {
8958
- "description": "The remove button.",
9047
+ "description": "The remove button — a full-height segment at the end of the chip.",
8959
9048
  "name": "remove"
8960
9049
  }
8961
9050
  ],
@@ -9805,6 +9894,11 @@
9805
9894
  {
9806
9895
  "kind": "method",
9807
9896
  "name": "toggle"
9897
+ },
9898
+ {
9899
+ "kind": "method",
9900
+ "name": "reposition",
9901
+ "description": "Recompute the position against the current trigger. Use it when you move\nthe anchor yourself and the tooltip has to follow. No-op when closed;\nhides if the anchor is gone."
9808
9902
  }
9809
9903
  ],
9810
9904
  "attributes": [
@@ -1,16 +1,21 @@
1
1
  import { LuxenElement } from '../../shared/luxen-element.js';
2
2
  export type ButtonGroupOrientation = 'horizontal' | 'vertical';
3
3
  /**
4
- * @summary Visually joins related `.l-button` elements into a single segmented
5
- * control. The joined appearance is pure CSS; this element adds the group
6
- * semantics (`role="group"`, `aria-label`).
4
+ * @summary A wrapper for multiple related buttons, joined into a single unit
5
+ * with shared borders. The joined appearance is pure CSS; this element only
6
+ * adds the group semantics (`role="group"`, `aria-label`).
7
+ *
8
+ * The group never manages the selection: buttons that carry state expose it
9
+ * themselves with `aria-pressed`, and the group holds no value. For a
10
+ * single-choice control that owns a value and submits it with a form, use
11
+ * `l-segmented-control` instead.
7
12
  *
8
13
  * @example
9
14
  * ```html
10
- * <l-button-group label="Alignment">
11
- * <button class="l-button">Left</button>
12
- * <button class="l-button">Center</button>
13
- * <button class="l-button">Right</button>
15
+ * <l-button-group label="Record actions">
16
+ * <button class="l-button">Edit</button>
17
+ * <button class="l-button">Duplicate</button>
18
+ * <button class="l-button">Archive</button>
14
19
  * </l-button-group>
15
20
  * ```
16
21
  *
@@ -1 +1 @@
1
- {"version":3,"file":"button-group.d.ts","sourceRoot":"","sources":["../../../src/html/elements/button-group/button-group.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAAE,MAAM,+BAA+B,CAAC;AAE7D,MAAM,MAAM,sBAAsB,GAAG,YAAY,GAAG,UAAU,CAAC;AAE/D;;;;;;;;;;;;;;;GAeG;AACH,qBAAa,WAAY,SAAQ,YAAY;IAClC,gBAAgB;IAIzB,yEAAyE;IAEzE,KAAK,CAAC,EAAE,MAAM,CAAC;IAEf;;;;OAIG;IAEH,WAAW,EAAE,sBAAsB,CAAgB;IAE1C,iBAAiB;IAKjB,OAAO,CAAC,OAAO,EAAE,GAAG,CAAC,MAAM,EAAE,OAAO,CAAC;CAS/C"}
1
+ {"version":3,"file":"button-group.d.ts","sourceRoot":"","sources":["../../../src/html/elements/button-group/button-group.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAAE,MAAM,+BAA+B,CAAC;AAE7D,MAAM,MAAM,sBAAsB,GAAG,YAAY,GAAG,UAAU,CAAC;AAE/D;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,qBAAa,WAAY,SAAQ,YAAY;IAClC,gBAAgB;IAIzB,yEAAyE;IAEzE,KAAK,CAAC,EAAE,MAAM,CAAC;IAEf;;;;OAIG;IAEH,WAAW,EAAE,sBAAsB,CAAgB;IAE1C,iBAAiB;IAKjB,OAAO,CAAC,OAAO,EAAE,GAAG,CAAC,MAAM,EAAE,OAAO,CAAC;CAS/C"}
@@ -1 +1 @@
1
- {"version":3,"file":"button-group.js","names":[],"sources":["../../../src/html/elements/button-group/button-group.ts"],"sourcesContent":["import { property } from 'lit/decorators.js';\nimport { LuxenElement } from '../../shared/luxen-element.js';\n\nexport type ButtonGroupOrientation = 'horizontal' | 'vertical';\n\n/**\n * @summary Visually joins related `.l-button` elements into a single segmented\n * control. The joined appearance is pure CSS; this element adds the group\n * semantics (`role=\"group\"`, `aria-label`).\n *\n * @example\n * ```html\n * <l-button-group label=\"Alignment\">\n * <button class=\"l-button\">Left</button>\n * <button class=\"l-button\">Center</button>\n * <button class=\"l-button\">Right</button>\n * </l-button-group>\n * ```\n *\n * @customElement l-button-group\n */\nexport class ButtonGroup extends LuxenElement {\n override createRenderRoot() {\n return this;\n }\n\n /** Accessible label announced for the group. Not displayed on screen. */\n @property({ reflect: true })\n label?: string;\n\n /**\n * Layout direction of the buttons. Visual only (drives the CSS via the\n * reflected attribute): ARIA 1.2 does not allow `aria-orientation` on\n * `role=\"group\"`, so no ARIA attribute is set.\n */\n @property({ reflect: true })\n orientation: ButtonGroupOrientation = 'horizontal';\n\n override connectedCallback() {\n super.connectedCallback();\n this.setAttribute('role', 'group');\n }\n\n override updated(changed: Map<string, unknown>) {\n if (changed.has('label')) {\n if (this.label) {\n this.setAttribute('aria-label', this.label);\n } else {\n this.removeAttribute('aria-label');\n }\n }\n }\n}\n"],"mappings":"4GAqBA,IAAa,EAAb,cAAiC,CAAa,gDAeN,aAdtC,kBAA4B,CAC1B,OAAO,IACT,CAcA,mBAA6B,CAC3B,MAAM,kBAAkB,EACxB,KAAK,aAAa,OAAQ,OAAO,CACnC,CAEA,QAAiB,EAA+B,CAC1C,EAAQ,IAAI,OAAO,IACjB,KAAK,MACP,KAAK,aAAa,aAAc,KAAK,KAAK,EAE1C,KAAK,gBAAgB,YAAY,EAGvC,CACF,KAzBG,EAAS,CAAE,QAAS,EAAK,CAAC,CAAA,EAAA,EAAA,UAAA,QAAA,IAAA,EAAA,KAQ1B,EAAS,CAAE,QAAS,EAAK,CAAC,CAAA,EAAA,EAAA,UAAA,cAAA,IAAA,EAAA"}
1
+ {"version":3,"file":"button-group.js","names":[],"sources":["../../../src/html/elements/button-group/button-group.ts"],"sourcesContent":["import { property } from 'lit/decorators.js';\nimport { LuxenElement } from '../../shared/luxen-element.js';\n\nexport type ButtonGroupOrientation = 'horizontal' | 'vertical';\n\n/**\n * @summary A wrapper for multiple related buttons, joined into a single unit\n * with shared borders. The joined appearance is pure CSS; this element only\n * adds the group semantics (`role=\"group\"`, `aria-label`).\n *\n * The group never manages the selection: buttons that carry state expose it\n * themselves with `aria-pressed`, and the group holds no value. For a\n * single-choice control that owns a value and submits it with a form, use\n * `l-segmented-control` instead.\n *\n * @example\n * ```html\n * <l-button-group label=\"Record actions\">\n * <button class=\"l-button\">Edit</button>\n * <button class=\"l-button\">Duplicate</button>\n * <button class=\"l-button\">Archive</button>\n * </l-button-group>\n * ```\n *\n * @customElement l-button-group\n */\nexport class ButtonGroup extends LuxenElement {\n override createRenderRoot() {\n return this;\n }\n\n /** Accessible label announced for the group. Not displayed on screen. */\n @property({ reflect: true })\n label?: string;\n\n /**\n * Layout direction of the buttons. Visual only (drives the CSS via the\n * reflected attribute): ARIA 1.2 does not allow `aria-orientation` on\n * `role=\"group\"`, so no ARIA attribute is set.\n */\n @property({ reflect: true })\n orientation: ButtonGroupOrientation = 'horizontal';\n\n override connectedCallback() {\n super.connectedCallback();\n this.setAttribute('role', 'group');\n }\n\n override updated(changed: Map<string, unknown>) {\n if (changed.has('label')) {\n if (this.label) {\n this.setAttribute('aria-label', this.label);\n } else {\n this.removeAttribute('aria-label');\n }\n }\n }\n}\n"],"mappings":"4GA0BA,IAAa,EAAb,cAAiC,CAAa,gDAeN,aAdtC,kBAA4B,CAC1B,OAAO,IACT,CAcA,mBAA6B,CAC3B,MAAM,kBAAkB,EACxB,KAAK,aAAa,OAAQ,OAAO,CACnC,CAEA,QAAiB,EAA+B,CAC1C,EAAQ,IAAI,OAAO,IACjB,KAAK,MACP,KAAK,aAAa,aAAc,KAAK,KAAK,EAE1C,KAAK,gBAAgB,YAAY,EAGvC,CACF,KAzBG,EAAS,CAAE,QAAS,EAAK,CAAC,CAAA,EAAA,EAAA,UAAA,QAAA,IAAA,EAAA,KAQ1B,EAAS,CAAE,QAAS,EAAK,CAAC,CAAA,EAAA,EAAA,UAAA,cAAA,IAAA,EAAA"}
@@ -23,7 +23,7 @@ interface InputStepperEventMap {
23
23
  *
24
24
  * @event change - Fired when the value changes. Bubbles. Properties: `value: number`.
25
25
  *
26
- * @cssproperty --border-color - Border color of the stepper container (default appearance) and of each button (rounded appearance). Defaults to `--l-color-border`.
26
+ * @cssproperty --border-color - Edge color of the stepper container (default and pill appearances) and of each button (rounded appearance). Defaults to `--l-color-border`.
27
27
  * @cssproperty --border-radius - Border radius of the stepper container (default appearance). Defaults to `--radius-md`.
28
28
  *
29
29
  * @customElement l-input-stepper
@@ -1 +1 @@
1
- {"version":3,"file":"input-stepper.js","names":[],"sources":["../../../src/html/elements/input-stepper/input-stepper.ts"],"sourcesContent":["import { property } from 'lit/decorators.js';\nimport { LuxenElement } from '../../shared/luxen-element.js';\nimport { LocalizeController } from '../../shared/localize.js';\nimport { cls, tagName } from '../../registry.js';\n\nexport type InputStepperSize = 'xs' | 'sm' | 'md' | 'lg' | 'xl';\n\n/** Fired when the stepper value changes. Bubbles; not composed. */\nexport class StepperChangeEvent extends Event {\n readonly value: number;\n constructor(value: number) {\n super('change', { bubbles: true, composed: false, cancelable: false });\n this.value = value;\n }\n}\n\ninterface InputStepperEventMap {\n change: StepperChangeEvent;\n}\n\n/**\n * A stepper control that enhances a native `<input type=\"number\">` with\n * decrement/increment buttons and an optional animated number track.\n *\n * @link https://www.nngroup.com/articles/input-steppers/\n *\n * @example\n * ```html\n * <l-input-stepper>\n * <input type=\"number\" min=\"0\" max=\"10\" value=\"5\" />\n * </l-input-stepper>\n * ```\n *\n * @event change - Fired when the value changes. Bubbles. Properties: `value: number`.\n *\n * @cssproperty --border-color - Border color of the stepper container (default appearance) and of each button (rounded appearance). Defaults to `--l-color-border`.\n * @cssproperty --border-radius - Border radius of the stepper container (default appearance). Defaults to `--radius-md`.\n *\n * @customElement l-input-stepper\n */\n// oxlint-disable-next-line typescript/no-unsafe-declaration-merging -- typed addEventListener overloads merged below; no uninitialized properties.\nexport class InputStepper extends LuxenElement {\n private _localize = new LocalizeController(this);\n\n override createRenderRoot() {\n return this;\n }\n\n /** Minimum allowed value. Falls back to the input's `min` attribute. */\n @property({ type: Number })\n min?: number;\n\n /** Maximum allowed value. Falls back to the input's `max` attribute. */\n @property({ type: Number })\n max?: number;\n\n /** Step increment. Falls back to the input's `step` attribute. */\n @property({ type: Number })\n step?: number;\n\n /** Control size. */\n @property({ reflect: true })\n size: InputStepperSize = 'md';\n\n /** Enable the animated number roller overlay. */\n @property({ type: Boolean, reflect: true, attribute: 'with-roller' })\n withRoller = false;\n\n /** Icon name for the decrement button. */\n @property({ attribute: 'decrement-icon' })\n decrementIcon = 'lucide:minus';\n\n /** Icon name for the increment button. */\n @property({ attribute: 'increment-icon' })\n incrementIcon = 'lucide:plus';\n\n private _initialized = false;\n private _setupTimer = 0;\n private _input: HTMLInputElement | null = null;\n private _decrementBtn: HTMLButtonElement | null = null;\n private _incrementBtn: HTMLButtonElement | null = null;\n private _valueWrapper: HTMLDivElement | null = null;\n private _trackDisplay: HTMLDivElement | null = null;\n private _track: HTMLDivElement | null = null;\n private _observer: MutationObserver | null = null;\n private _skipTransition = false;\n\n override connectedCallback() {\n super.connectedCallback();\n // Children may not be parsed yet when the element upgrades mid-parse: try\n // synchronously, then retry once on a macrotask. setTimeout, not rAF — rAF\n // is suspended in hidden documents, so the element would stay inert there.\n if (!this._trySetup()) {\n this._setupTimer = window.setTimeout(() => this._trySetup(), 0);\n }\n }\n\n override disconnectedCallback() {\n super.disconnectedCallback();\n clearTimeout(this._setupTimer);\n this._teardown();\n }\n\n /** @returns true when setup ran or was already done; false to schedule a retry. */\n private _trySetup(): boolean {\n if (this._initialized || !this.isConnected) return true;\n if (!this.querySelector('input[type=\"number\"]')) return false;\n this._setup();\n return true;\n }\n\n override updated(changed: Map<string, unknown>) {\n if (!this._input) return;\n\n if (changed.has('min') || changed.has('max') || changed.has('step')) {\n this._syncConstraints();\n this._updateButtonStates();\n }\n\n // Button labels are set imperatively in _setup() (outside any render), so\n // re-apply them on every update to track a language change (the localize\n // controller calls requestUpdate when <html lang> changes).\n this._decrementBtn?.setAttribute('aria-label', this._localize.term('decreaseValue'));\n this._incrementBtn?.setAttribute('aria-label', this._localize.term('increaseValue'));\n }\n\n /** Decrease the value by one step. */\n decrement() {\n if (!this._input) return;\n const { min, step } = this._getConstraints();\n const newVal = Math.max(this._input.valueAsNumber - step, min);\n this._applyValue(newVal);\n }\n\n /** Increase the value by one step. */\n increment() {\n if (!this._input) return;\n const { max, step } = this._getConstraints();\n const newVal = Math.min(this._input.valueAsNumber + step, max);\n this._applyValue(newVal);\n }\n\n // --- Setup / Teardown ---\n\n private _setup() {\n this._input = this.querySelector('input[type=\"number\"]');\n if (!this._input) return;\n this._initialized = true;\n\n this._input.inputMode = 'numeric';\n\n // Wrap input in value container\n this._valueWrapper = document.createElement('div');\n this._valueWrapper.className = cls('input-stepper-value');\n this._input.replaceWith(this._valueWrapper);\n this._valueWrapper.appendChild(this._input);\n\n // Inject track overlay\n if (this.withRoller) {\n this._buildTrack();\n }\n\n // Create buttons\n this._decrementBtn = this._createButton(\n this.decrementIcon,\n this._localize.term('decreaseValue'),\n );\n this._incrementBtn = this._createButton(\n this.incrementIcon,\n this._localize.term('increaseValue'),\n );\n\n this._valueWrapper.before(this._decrementBtn);\n this._valueWrapper.after(this._incrementBtn);\n\n // Events\n this._decrementBtn.addEventListener('click', this._onDecrement);\n this._incrementBtn.addEventListener('click', this._onIncrement);\n this._input.addEventListener('change', this._onInputChange);\n\n // Observe disabled attribute on input\n this._observer = new MutationObserver(() => this._updateButtonStates());\n this._observer.observe(this._input, { attributes: true, attributeFilter: ['disabled'] });\n\n this._syncConstraints();\n this._updateButtonStates();\n this._updateTrack();\n }\n\n private _teardown() {\n this._decrementBtn?.removeEventListener('click', this._onDecrement);\n this._incrementBtn?.removeEventListener('click', this._onIncrement);\n this._input?.removeEventListener('change', this._onInputChange);\n this._observer?.disconnect();\n\n // Restore input to direct child\n if (this._input && this._valueWrapper) {\n this._valueWrapper.replaceWith(this._input);\n }\n this._decrementBtn?.remove();\n this._incrementBtn?.remove();\n\n this._input = null;\n this._decrementBtn = null;\n this._incrementBtn = null;\n this._valueWrapper = null;\n this._trackDisplay = null;\n this._track = null;\n this._observer = null;\n this._initialized = false;\n }\n\n // --- DOM helpers ---\n\n private _createButton(icon: string, label: string): HTMLButtonElement {\n const btn = document.createElement('button');\n btn.type = 'button';\n btn.setAttribute('aria-label', label);\n const iconEl = document.createElement(tagName('icon'));\n iconEl.setAttribute('name', icon);\n btn.append(iconEl);\n return btn;\n }\n\n private _buildTrack() {\n if (!this._valueWrapper) return;\n\n this._trackDisplay = document.createElement('div');\n this._trackDisplay.className = cls('input-stepper-track-display');\n\n this._track = document.createElement('div');\n this._track.className = cls('input-stepper-track');\n this._track.setAttribute('aria-hidden', 'true');\n\n this._rebuildTrackNumbers();\n this._trackDisplay.appendChild(this._track);\n this._valueWrapper.appendChild(this._trackDisplay);\n }\n\n private _rebuildTrackNumbers() {\n if (!this._track) return;\n\n const { min, max, step } = this._getConstraints();\n const effectiveMax = Number.isFinite(max) ? max : (this._input?.valueAsNumber ?? 0) + 100;\n\n this._track.innerHTML = '';\n for (let i = min; i <= effectiveMax; i += step) {\n const div = document.createElement('div');\n div.textContent = String(i);\n this._track.appendChild(div);\n }\n }\n\n // --- Constraints ---\n\n private _getConstraints() {\n const min = this.min ?? (Number(this._input?.getAttribute('min')) || 0);\n const max =\n this.max ??\n (this._input?.hasAttribute('max') ? Number(this._input.getAttribute('max')) : Infinity);\n const step = this.step ?? (Number(this._input?.getAttribute('step')) || 1);\n return { min, max, step };\n }\n\n private _syncConstraints() {\n if (!this._input) return;\n const { min, max, step } = this._getConstraints();\n\n this._input.min = String(min);\n if (Number.isFinite(max)) this._input.max = String(max);\n this._input.step = String(step);\n\n this._rebuildTrackNumbers();\n this._updateTrack();\n }\n\n // --- Value ---\n\n private _applyValue(val: number) {\n if (!this._input) return;\n this._input.value = String(val);\n this._updateButtonStates();\n this._updateTrack();\n this.dispatchEvent(new StepperChangeEvent(val));\n }\n\n // --- Track ---\n\n private _updateTrack() {\n if (!this._track) return;\n\n const { min, step } = this._getConstraints();\n const value = this._input?.valueAsNumber ?? min;\n const offset = (min - value) / step;\n\n this._track.style.translate = `0 calc(${offset} * var(--_button-size))`;\n\n if (this._skipTransition) {\n this._track.style.transition = 'none';\n void this._track.offsetHeight;\n this._track.style.transition = '';\n this._skipTransition = false;\n }\n }\n\n // --- Button states ---\n\n private _updateButtonStates() {\n if (!this._input || !this._decrementBtn || !this._incrementBtn) return;\n\n const { min, max } = this._getConstraints();\n const value = this._input.valueAsNumber;\n const disabled = this._input.disabled;\n\n this._decrementBtn.disabled = disabled || value <= min;\n this._incrementBtn.disabled = disabled || value >= max;\n }\n\n // --- Event handlers ---\n\n private _onDecrement = () => this.decrement();\n private _onIncrement = () => this.increment();\n\n private _onInputChange = (e: Event) => {\n if (!this._input) return;\n // Swallow the inner <input>'s native `change` so the host emits a single,\n // typed `change` (StepperChangeEvent) rather than two events — the native\n // one bubbles through the light-DOM host alongside our own.\n e.stopPropagation();\n\n const { min, max } = this._getConstraints();\n const clamped = Math.min(Math.max(this._input.valueAsNumber, min), max);\n this._input.value = String(clamped);\n\n this._skipTransition = true;\n this._updateButtonStates();\n this._updateTrack();\n this.dispatchEvent(new StepperChangeEvent(clamped));\n };\n}\n\n// Types `addEventListener('change', …)` as `StepperChangeEvent` on this element\n// (the global event map can't be augmented for the colliding name `change`).\n// See the Tabs interface in tabs.ts for the full rationale and the gotchas.\nexport interface InputStepper {\n addEventListener<K extends keyof InputStepperEventMap>(\n type: K,\n listener: (this: InputStepper, ev: InputStepperEventMap[K]) => void,\n options?: boolean | AddEventListenerOptions,\n ): void;\n addEventListener(\n type: string,\n listener: EventListenerOrEventListenerObject,\n options?: boolean | AddEventListenerOptions,\n ): void;\n removeEventListener<K extends keyof InputStepperEventMap>(\n type: K,\n listener: (this: InputStepper, ev: InputStepperEventMap[K]) => void,\n options?: boolean | EventListenerOptions,\n ): void;\n removeEventListener(\n type: string,\n listener: EventListenerOrEventListenerObject,\n options?: boolean | EventListenerOptions,\n ): void;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"input-stepper.js","names":[],"sources":["../../../src/html/elements/input-stepper/input-stepper.ts"],"sourcesContent":["import { property } from 'lit/decorators.js';\nimport { LuxenElement } from '../../shared/luxen-element.js';\nimport { LocalizeController } from '../../shared/localize.js';\nimport { cls, tagName } from '../../registry.js';\n\nexport type InputStepperSize = 'xs' | 'sm' | 'md' | 'lg' | 'xl';\n\n/** Fired when the stepper value changes. Bubbles; not composed. */\nexport class StepperChangeEvent extends Event {\n readonly value: number;\n constructor(value: number) {\n super('change', { bubbles: true, composed: false, cancelable: false });\n this.value = value;\n }\n}\n\ninterface InputStepperEventMap {\n change: StepperChangeEvent;\n}\n\n/**\n * A stepper control that enhances a native `<input type=\"number\">` with\n * decrement/increment buttons and an optional animated number track.\n *\n * @link https://www.nngroup.com/articles/input-steppers/\n *\n * @example\n * ```html\n * <l-input-stepper>\n * <input type=\"number\" min=\"0\" max=\"10\" value=\"5\" />\n * </l-input-stepper>\n * ```\n *\n * @event change - Fired when the value changes. Bubbles. Properties: `value: number`.\n *\n * @cssproperty --border-color - Edge color of the stepper container (default and pill appearances) and of each button (rounded appearance). Defaults to `--l-color-border`.\n * @cssproperty --border-radius - Border radius of the stepper container (default appearance). Defaults to `--radius-md`.\n *\n * @customElement l-input-stepper\n */\n// oxlint-disable-next-line typescript/no-unsafe-declaration-merging -- typed addEventListener overloads merged below; no uninitialized properties.\nexport class InputStepper extends LuxenElement {\n private _localize = new LocalizeController(this);\n\n override createRenderRoot() {\n return this;\n }\n\n /** Minimum allowed value. Falls back to the input's `min` attribute. */\n @property({ type: Number })\n min?: number;\n\n /** Maximum allowed value. Falls back to the input's `max` attribute. */\n @property({ type: Number })\n max?: number;\n\n /** Step increment. Falls back to the input's `step` attribute. */\n @property({ type: Number })\n step?: number;\n\n /** Control size. */\n @property({ reflect: true })\n size: InputStepperSize = 'md';\n\n /** Enable the animated number roller overlay. */\n @property({ type: Boolean, reflect: true, attribute: 'with-roller' })\n withRoller = false;\n\n /** Icon name for the decrement button. */\n @property({ attribute: 'decrement-icon' })\n decrementIcon = 'lucide:minus';\n\n /** Icon name for the increment button. */\n @property({ attribute: 'increment-icon' })\n incrementIcon = 'lucide:plus';\n\n private _initialized = false;\n private _setupTimer = 0;\n private _input: HTMLInputElement | null = null;\n private _decrementBtn: HTMLButtonElement | null = null;\n private _incrementBtn: HTMLButtonElement | null = null;\n private _valueWrapper: HTMLDivElement | null = null;\n private _trackDisplay: HTMLDivElement | null = null;\n private _track: HTMLDivElement | null = null;\n private _observer: MutationObserver | null = null;\n private _skipTransition = false;\n\n override connectedCallback() {\n super.connectedCallback();\n // Children may not be parsed yet when the element upgrades mid-parse: try\n // synchronously, then retry once on a macrotask. setTimeout, not rAF — rAF\n // is suspended in hidden documents, so the element would stay inert there.\n if (!this._trySetup()) {\n this._setupTimer = window.setTimeout(() => this._trySetup(), 0);\n }\n }\n\n override disconnectedCallback() {\n super.disconnectedCallback();\n clearTimeout(this._setupTimer);\n this._teardown();\n }\n\n /** @returns true when setup ran or was already done; false to schedule a retry. */\n private _trySetup(): boolean {\n if (this._initialized || !this.isConnected) return true;\n if (!this.querySelector('input[type=\"number\"]')) return false;\n this._setup();\n return true;\n }\n\n override updated(changed: Map<string, unknown>) {\n if (!this._input) return;\n\n if (changed.has('min') || changed.has('max') || changed.has('step')) {\n this._syncConstraints();\n this._updateButtonStates();\n }\n\n // Button labels are set imperatively in _setup() (outside any render), so\n // re-apply them on every update to track a language change (the localize\n // controller calls requestUpdate when <html lang> changes).\n this._decrementBtn?.setAttribute('aria-label', this._localize.term('decreaseValue'));\n this._incrementBtn?.setAttribute('aria-label', this._localize.term('increaseValue'));\n }\n\n /** Decrease the value by one step. */\n decrement() {\n if (!this._input) return;\n const { min, step } = this._getConstraints();\n const newVal = Math.max(this._input.valueAsNumber - step, min);\n this._applyValue(newVal);\n }\n\n /** Increase the value by one step. */\n increment() {\n if (!this._input) return;\n const { max, step } = this._getConstraints();\n const newVal = Math.min(this._input.valueAsNumber + step, max);\n this._applyValue(newVal);\n }\n\n // --- Setup / Teardown ---\n\n private _setup() {\n this._input = this.querySelector('input[type=\"number\"]');\n if (!this._input) return;\n this._initialized = true;\n\n this._input.inputMode = 'numeric';\n\n // Wrap input in value container\n this._valueWrapper = document.createElement('div');\n this._valueWrapper.className = cls('input-stepper-value');\n this._input.replaceWith(this._valueWrapper);\n this._valueWrapper.appendChild(this._input);\n\n // Inject track overlay\n if (this.withRoller) {\n this._buildTrack();\n }\n\n // Create buttons\n this._decrementBtn = this._createButton(\n this.decrementIcon,\n this._localize.term('decreaseValue'),\n );\n this._incrementBtn = this._createButton(\n this.incrementIcon,\n this._localize.term('increaseValue'),\n );\n\n this._valueWrapper.before(this._decrementBtn);\n this._valueWrapper.after(this._incrementBtn);\n\n // Events\n this._decrementBtn.addEventListener('click', this._onDecrement);\n this._incrementBtn.addEventListener('click', this._onIncrement);\n this._input.addEventListener('change', this._onInputChange);\n\n // Observe disabled attribute on input\n this._observer = new MutationObserver(() => this._updateButtonStates());\n this._observer.observe(this._input, { attributes: true, attributeFilter: ['disabled'] });\n\n this._syncConstraints();\n this._updateButtonStates();\n this._updateTrack();\n }\n\n private _teardown() {\n this._decrementBtn?.removeEventListener('click', this._onDecrement);\n this._incrementBtn?.removeEventListener('click', this._onIncrement);\n this._input?.removeEventListener('change', this._onInputChange);\n this._observer?.disconnect();\n\n // Restore input to direct child\n if (this._input && this._valueWrapper) {\n this._valueWrapper.replaceWith(this._input);\n }\n this._decrementBtn?.remove();\n this._incrementBtn?.remove();\n\n this._input = null;\n this._decrementBtn = null;\n this._incrementBtn = null;\n this._valueWrapper = null;\n this._trackDisplay = null;\n this._track = null;\n this._observer = null;\n this._initialized = false;\n }\n\n // --- DOM helpers ---\n\n private _createButton(icon: string, label: string): HTMLButtonElement {\n const btn = document.createElement('button');\n btn.type = 'button';\n btn.setAttribute('aria-label', label);\n const iconEl = document.createElement(tagName('icon'));\n iconEl.setAttribute('name', icon);\n btn.append(iconEl);\n return btn;\n }\n\n private _buildTrack() {\n if (!this._valueWrapper) return;\n\n this._trackDisplay = document.createElement('div');\n this._trackDisplay.className = cls('input-stepper-track-display');\n\n this._track = document.createElement('div');\n this._track.className = cls('input-stepper-track');\n this._track.setAttribute('aria-hidden', 'true');\n\n this._rebuildTrackNumbers();\n this._trackDisplay.appendChild(this._track);\n this._valueWrapper.appendChild(this._trackDisplay);\n }\n\n private _rebuildTrackNumbers() {\n if (!this._track) return;\n\n const { min, max, step } = this._getConstraints();\n const effectiveMax = Number.isFinite(max) ? max : (this._input?.valueAsNumber ?? 0) + 100;\n\n this._track.innerHTML = '';\n for (let i = min; i <= effectiveMax; i += step) {\n const div = document.createElement('div');\n div.textContent = String(i);\n this._track.appendChild(div);\n }\n }\n\n // --- Constraints ---\n\n private _getConstraints() {\n const min = this.min ?? (Number(this._input?.getAttribute('min')) || 0);\n const max =\n this.max ??\n (this._input?.hasAttribute('max') ? Number(this._input.getAttribute('max')) : Infinity);\n const step = this.step ?? (Number(this._input?.getAttribute('step')) || 1);\n return { min, max, step };\n }\n\n private _syncConstraints() {\n if (!this._input) return;\n const { min, max, step } = this._getConstraints();\n\n this._input.min = String(min);\n if (Number.isFinite(max)) this._input.max = String(max);\n this._input.step = String(step);\n\n this._rebuildTrackNumbers();\n this._updateTrack();\n }\n\n // --- Value ---\n\n private _applyValue(val: number) {\n if (!this._input) return;\n this._input.value = String(val);\n this._updateButtonStates();\n this._updateTrack();\n this.dispatchEvent(new StepperChangeEvent(val));\n }\n\n // --- Track ---\n\n private _updateTrack() {\n if (!this._track) return;\n\n const { min, step } = this._getConstraints();\n const value = this._input?.valueAsNumber ?? min;\n const offset = (min - value) / step;\n\n this._track.style.translate = `0 calc(${offset} * var(--_button-size))`;\n\n if (this._skipTransition) {\n this._track.style.transition = 'none';\n void this._track.offsetHeight;\n this._track.style.transition = '';\n this._skipTransition = false;\n }\n }\n\n // --- Button states ---\n\n private _updateButtonStates() {\n if (!this._input || !this._decrementBtn || !this._incrementBtn) return;\n\n const { min, max } = this._getConstraints();\n const value = this._input.valueAsNumber;\n const disabled = this._input.disabled;\n\n this._decrementBtn.disabled = disabled || value <= min;\n this._incrementBtn.disabled = disabled || value >= max;\n }\n\n // --- Event handlers ---\n\n private _onDecrement = () => this.decrement();\n private _onIncrement = () => this.increment();\n\n private _onInputChange = (e: Event) => {\n if (!this._input) return;\n // Swallow the inner <input>'s native `change` so the host emits a single,\n // typed `change` (StepperChangeEvent) rather than two events — the native\n // one bubbles through the light-DOM host alongside our own.\n e.stopPropagation();\n\n const { min, max } = this._getConstraints();\n const clamped = Math.min(Math.max(this._input.valueAsNumber, min), max);\n this._input.value = String(clamped);\n\n this._skipTransition = true;\n this._updateButtonStates();\n this._updateTrack();\n this.dispatchEvent(new StepperChangeEvent(clamped));\n };\n}\n\n// Types `addEventListener('change', …)` as `StepperChangeEvent` on this element\n// (the global event map can't be augmented for the colliding name `change`).\n// See the Tabs interface in tabs.ts for the full rationale and the gotchas.\nexport interface InputStepper {\n addEventListener<K extends keyof InputStepperEventMap>(\n type: K,\n listener: (this: InputStepper, ev: InputStepperEventMap[K]) => void,\n options?: boolean | AddEventListenerOptions,\n ): void;\n addEventListener(\n type: string,\n listener: EventListenerOrEventListenerObject,\n options?: boolean | AddEventListenerOptions,\n ): void;\n removeEventListener<K extends keyof InputStepperEventMap>(\n type: K,\n listener: (this: InputStepper, ev: InputStepperEventMap[K]) => void,\n options?: boolean | EventListenerOptions,\n ): void;\n removeEventListener(\n type: string,\n listener: EventListenerOrEventListenerObject,\n options?: boolean | EventListenerOptions,\n ): void;\n}\n"],"mappings":"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"}
@@ -39,6 +39,7 @@ export declare class Popover extends LuxenElement {
39
39
  accessor trigger: string;
40
40
  private _hasTrigger;
41
41
  private get _trigger();
42
+ private _triggerFor;
42
43
  private get _popoverEl();
43
44
  private get _arrowEl();
44
45
  private _getDuration;
@@ -48,6 +49,20 @@ export declare class Popover extends LuxenElement {
48
49
  show(): void;
49
50
  hide(): void;
50
51
  toggle(): void;
52
+ /**
53
+ * Recompute the position against the current trigger. Use it when you move
54
+ * the anchor yourself and the panel has to follow. No-op when closed; hides
55
+ * if the anchor is gone.
56
+ */
57
+ reposition(): Promise<void>;
58
+ /**
59
+ * Move the open panel onto whatever `for` names now — a move, not a re-open,
60
+ * so it deliberately bypasses `_handleOpenChange` and its show animation.
61
+ * Shared by the public `reposition()` and the retarget path so the two
62
+ * cannot drift apart.
63
+ */
64
+ private _repositionToTrigger;
65
+ private _positionOptions;
51
66
  private _handleOpenChange;
52
67
  private _onPointerEnter;
53
68
  private _onPointerLeave;
@@ -1 +1 @@
1
- {"version":3,"file":"popover.d.ts","sourceRoot":"","sources":["../../../src/html/elements/popover/popover.ts"],"names":[],"mappings":"AAAA,OAAO,EAA4B,KAAK,cAAc,EAAE,MAAM,KAAK,CAAC;AACpE,OAAO,EAAE,YAAY,EAAE,MAAM,+BAA+B,CAAC;AAE7D,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAQlD;;;;;;;;;;;;;;;;;GAiBG;AACH,qBAAa,OAAQ,SAAQ,YAAY;IACvC,OAAgB,MAAM,4BAAwB;IAE9C,OAAO,CAAC,UAAU,CAAuB;IAEzC,OAAO,CAAC,SAAS,CAOd;IAEH,yDAAyD;IAEzD,QAAQ,CAAC,GAAG,SAAM;IAElB,8CAA8C;IAE9C,QAAQ,CAAC,SAAS,EAAE,SAAS,CAAY;IAEzC,sDAAsD;IAEtD,QAAQ,CAAC,QAAQ,SAAK;IAEtB,6CAA6C;IAE7C,QAAQ,CAAC,IAAI,UAAS;IAEtB,kCAAkC;IAElC,QAAQ,CAAC,YAAY,UAAS;IAE9B,wEAAwE;IAExE,QAAQ,CAAC,SAAS,UAAS;IAE3B,kFAAkF;IAElF,QAAQ,CAAC,OAAO,SAAW;IAE3B,OAAO,CAAC,WAAW;IAInB,OAAO,KAAK,QAAQ,GAEnB;IAED,OAAO,KAAK,UAAU,GAErB;IAED,OAAO,KAAK,QAAQ,GAEnB;IAED,OAAO,CAAC,YAAY;IAKX,iBAAiB;IAKjB,oBAAoB;IAKpB,OAAO,CAAC,OAAO,EAAE,cAAc,CAAC,IAAI,CAAC;IAU9C,IAAI;IAIJ,IAAI;IAIJ,MAAM;YAIQ,iBAAiB;IA+B/B,OAAO,CAAC,eAAe,CAIrB;IAEF,OAAO,CAAC,eAAe,CAGrB;IAEF,OAAO,CAAC,UAAU,CAEhB;IACF,OAAO,CAAC,WAAW,CAEjB;IACF,OAAO,CAAC,QAAQ,CAEd;IAEF,OAAO,CAAC,UAAU,CAKhB;IAEF,2DAA2D;IAC3D,OAAO,CAAC,SAAS,CAKf;IAEF,OAAO,CAAC,oBAAoB;IAW5B,OAAO,CAAC,uBAAuB;IAOtB,MAAM;CAoBhB"}
1
+ {"version":3,"file":"popover.d.ts","sourceRoot":"","sources":["../../../src/html/elements/popover/popover.ts"],"names":[],"mappings":"AAAA,OAAO,EAA4B,KAAK,cAAc,EAAE,MAAM,KAAK,CAAC;AACpE,OAAO,EAAE,YAAY,EAAE,MAAM,+BAA+B,CAAC;AAE7D,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAQlD;;;;;;;;;;;;;;;;;GAiBG;AACH,qBAAa,OAAQ,SAAQ,YAAY;IACvC,OAAgB,MAAM,4BAAwB;IAE9C,OAAO,CAAC,UAAU,CAAuB;IAEzC,OAAO,CAAC,SAAS,CAOd;IAEH,yDAAyD;IAEzD,QAAQ,CAAC,GAAG,SAAM;IAElB,8CAA8C;IAE9C,QAAQ,CAAC,SAAS,EAAE,SAAS,CAAY;IAEzC,sDAAsD;IAEtD,QAAQ,CAAC,QAAQ,SAAK;IAEtB,6CAA6C;IAE7C,QAAQ,CAAC,IAAI,UAAS;IAEtB,kCAAkC;IAElC,QAAQ,CAAC,YAAY,UAAS;IAE9B,wEAAwE;IAExE,QAAQ,CAAC,SAAS,UAAS;IAE3B,kFAAkF;IAElF,QAAQ,CAAC,OAAO,SAAW;IAE3B,OAAO,CAAC,WAAW;IAInB,OAAO,KAAK,QAAQ,GAEnB;IAED,OAAO,CAAC,WAAW;IAQnB,OAAO,KAAK,UAAU,GAErB;IAED,OAAO,KAAK,QAAQ,GAEnB;IAED,OAAO,CAAC,YAAY;IAKX,iBAAiB;IAKjB,oBAAoB;IAOpB,OAAO,CAAC,OAAO,EAAE,cAAc,CAAC,IAAI,CAAC;IAgB9C,IAAI;IAIJ,IAAI;IAIJ,MAAM;IAIN;;;;OAIG;IACG,UAAU;IAKhB;;;;;OAKG;YACW,oBAAoB;IAkBlC,OAAO,CAAC,gBAAgB;YAQV,iBAAiB;IA2B/B,OAAO,CAAC,eAAe,CAIrB;IAEF,OAAO,CAAC,eAAe,CAGrB;IAEF,OAAO,CAAC,UAAU,CAEhB;IACF,OAAO,CAAC,WAAW,CAEjB;IACF,OAAO,CAAC,QAAQ,CAEd;IAEF,OAAO,CAAC,UAAU,CAKhB;IAEF,2DAA2D;IAC3D,OAAO,CAAC,SAAS,CAKf;IAEF,OAAO,CAAC,oBAAoB;IAW5B,OAAO,CAAC,uBAAuB;IAItB,MAAM;CAoBhB"}
@@ -1,4 +1,4 @@
1
- import{uniqueId as e}from"../../registry.js";import{i as t}from"../../chunks/lit.js";import{i as n,t as r}from"../../chunks/decorate.js";import{a as i,t as a}from"../../chunks/lit-html.js";import{LuxenElement as o}from"../../shared/luxen-element.js";import s from"../../shared/styles/host.styles.js";import{PopoverController as c}from"../../shared/controllers/popover.js";var l=t(`:host{--background:var(--l-color-surface-overlay);--color:inherit;--border-radius:6px;--max-width:320px;--shadow:var(--l-shadow-md);--arrow-size:8px;--show-duration:.15s;--hide-duration:.15s;--_border-color:var(--l-color-border-overlay);display:contents}[popover]{inset:unset;isolation:isolate;box-sizing:border-box;width:max-content;max-width:var(--max-width);border:1px solid var(--_border-color);border-radius:var(--border-radius);background:var(--background);color:var(--color);box-shadow:var(--shadow);font-size:.875rem;line-height:1.5;overflow:visible}:host([full-width]) [popover]{width:100vw;max-width:none}i{width:var(--arrow-size);height:var(--arrow-size);background:var(--background);z-index:-1;display:block;position:absolute;transform:rotate(45deg)}:host([data-placement^=top]) i{border-right:1px solid var(--_border-color);border-bottom:1px solid var(--_border-color)}:host([data-placement^=bottom]) i{border-top:1px solid var(--_border-color);border-left:1px solid var(--_border-color)}:host([data-placement^=left]) i{border-right:1px solid var(--_border-color);border-top:1px solid var(--_border-color)}:host([data-placement^=right]) i{border-left:1px solid var(--_border-color);border-bottom:1px solid var(--_border-color)}`),u=class extends o{constructor(...t){super(...t),this._popoverId=e(`popover`),this._floating=new c(this,{getTriggerElement:()=>this._trigger,getFloatingElement:()=>this._popoverEl,getArrowElement:()=>this._arrowEl,onPlacementChange:e=>{this.dataset.placement=e}}),this.#e=``,this.#t=`bottom`,this.#n=8,this.#r=!1,this.#i=!1,this.#a=!1,this.#o=`click`,this._onPointerEnter=()=>{this._hasTrigger(`hover`)&&(this._floating.cleanupSafePolygon(),this.show())},this._onPointerLeave=e=>{!this._hasTrigger(`hover`)||!this.open||this._floating.handlePointerLeave(e,()=>this.hide())},this._onFocusIn=()=>{this._hasTrigger(`focus`)&&this.show()},this._onFocusOut=()=>{this._hasTrigger(`focus`)&&this.hide()},this._onClick=()=>{this._hasTrigger(`click`)&&this.toggle()},this._onKeyDown=e=>{this.open&&e.key===`Escape`&&(e.stopPropagation(),this.hide())},this._onToggle=e=>{e.newState===`closed`&&this.open&&(this.open=!1)}}static{this.styles=[s,l]}#e;get for(){return this.#e}set for(e){this.#e=e}#t;get placement(){return this.#t}set placement(e){this.#t=e}#n;get distance(){return this.#n}set distance(e){this.#n=e}#r;get open(){return this.#r}set open(e){this.#r=e}#i;get withoutArrow(){return this.#i}set withoutArrow(e){this.#i=e}#a;get fullWidth(){return this.#a}set fullWidth(e){this.#a=e}#o;get trigger(){return this.#o}set trigger(e){this.#o=e}_hasTrigger(e){return this.trigger.split(` `).includes(e)}get _trigger(){return this.for?this.getRootNode().getElementById(this.for):null}get _popoverEl(){return this.shadowRoot.querySelector(`[popover]`)}get _arrowEl(){return this.withoutArrow?null:this.shadowRoot.querySelector(`i`)}_getDuration(e){let t=parseFloat(getComputedStyle(this).getPropertyValue(e));return Number.isNaN(t)?150:t}connectedCallback(){super.connectedCallback(),requestAnimationFrame(()=>this._addTriggerListeners())}disconnectedCallback(){super.disconnectedCallback(),this._removeTriggerListeners()}updated(e){e.has(`open`)&&this._handleOpenChange(),e.has(`for`)&&(this._removeTriggerListeners(e.get(`for`)),this._addTriggerListeners())}show(){this.open||=!0}hide(){this.open&&=!1}toggle(){this.open=!this.open}async _handleOpenChange(){let e=this._popoverEl;if(!e)return;let t={placement:this.placement,distance:this.distance,fullWidth:this.fullWidth};if(this.open){if(e.showPopover(),await this._floating.updatePosition(t),!this.open)return;await this._floating.animateShow(e,this._getDuration(`--show-duration`)),this._floating.startPositioning(t),this._trigger?.setAttribute(`aria-expanded`,`true`)}else this._floating.stopPositioning(),this._floating.cleanupSafePolygon(),this._trigger?.setAttribute(`aria-expanded`,`false`),await this._floating.animateHide(e,this._getDuration(`--hide-duration`)),e.matches(`:popover-open`)&&e.hidePopover()}_addTriggerListeners(){this._floating.addTriggerListeners({onPointerEnter:this._onPointerEnter,onPointerLeave:this._onPointerLeave,onFocusIn:this._onFocusIn,onFocusOut:this._onFocusOut,onClick:this._onClick,onKeyDown:this._onKeyDown})}_removeTriggerListeners(e){let t=e?this.getRootNode().getElementById(e):void 0;this._floating.removeTriggerListeners(t)}render(){return i`
1
+ import{uniqueId as e}from"../../registry.js";import{i as t}from"../../chunks/lit.js";import{i as n,t as r}from"../../chunks/decorate.js";import{a as i,t as a}from"../../chunks/lit-html.js";import{LuxenElement as o}from"../../shared/luxen-element.js";import s from"../../shared/styles/host.styles.js";import{PopoverController as c}from"../../shared/controllers/popover.js";var l=t(`:host{--background:var(--l-color-surface-overlay);--color:inherit;--border-radius:6px;--max-width:320px;--shadow:var(--l-shadow-md);--arrow-size:8px;--show-duration:.15s;--hide-duration:.15s;--_border-color:var(--l-color-border-overlay);display:contents}[popover]{inset:unset;isolation:isolate;box-sizing:border-box;width:max-content;max-width:var(--max-width);border:1px solid var(--_border-color);border-radius:var(--border-radius);background:var(--background);color:var(--color);box-shadow:var(--shadow);font-size:.875rem;line-height:1.5;overflow:visible}:host([full-width]) [popover]{width:100vw;max-width:none}i{width:var(--arrow-size);height:var(--arrow-size);background:var(--background);z-index:-1;display:block;position:absolute;transform:rotate(45deg)}:host([data-placement^=top]) i{border-right:1px solid var(--_border-color);border-bottom:1px solid var(--_border-color)}:host([data-placement^=bottom]) i{border-top:1px solid var(--_border-color);border-left:1px solid var(--_border-color)}:host([data-placement^=left]) i{border-right:1px solid var(--_border-color);border-top:1px solid var(--_border-color)}:host([data-placement^=right]) i{border-left:1px solid var(--_border-color);border-bottom:1px solid var(--_border-color)}`),u=class extends o{constructor(...t){super(...t),this._popoverId=e(`popover`),this._floating=new c(this,{getTriggerElement:()=>this._trigger,getFloatingElement:()=>this._popoverEl,getArrowElement:()=>this._arrowEl,onPlacementChange:e=>{this.dataset.placement=e}}),this.#e=``,this.#t=`bottom`,this.#n=8,this.#r=!1,this.#i=!1,this.#a=!1,this.#o=`click`,this._onPointerEnter=()=>{this._hasTrigger(`hover`)&&(this._floating.cleanupSafePolygon(),this.show())},this._onPointerLeave=e=>{!this._hasTrigger(`hover`)||!this.open||this._floating.handlePointerLeave(e,()=>this.hide())},this._onFocusIn=()=>{this._hasTrigger(`focus`)&&this.show()},this._onFocusOut=()=>{this._hasTrigger(`focus`)&&this.hide()},this._onClick=()=>{this._hasTrigger(`click`)&&this.toggle()},this._onKeyDown=e=>{this.open&&e.key===`Escape`&&(e.stopPropagation(),this.hide())},this._onToggle=e=>{e.newState===`closed`&&this.open&&(this.open=!1)}}static{this.styles=[s,l]}#e;get for(){return this.#e}set for(e){this.#e=e}#t;get placement(){return this.#t}set placement(e){this.#t=e}#n;get distance(){return this.#n}set distance(e){this.#n=e}#r;get open(){return this.#r}set open(e){this.#r=e}#i;get withoutArrow(){return this.#i}set withoutArrow(e){this.#i=e}#a;get fullWidth(){return this.#a}set fullWidth(e){this.#a=e}#o;get trigger(){return this.#o}set trigger(e){this.#o=e}_hasTrigger(e){return this.trigger.split(` `).includes(e)}get _trigger(){return this._triggerFor(this.for)}_triggerFor(e){if(!e)return null;let t=this.getRootNode();return t instanceof Document||t instanceof ShadowRoot?t.getElementById(e):null}get _popoverEl(){return this.shadowRoot.querySelector(`[popover]`)}get _arrowEl(){return this.withoutArrow?null:this.shadowRoot.querySelector(`i`)}_getDuration(e){let t=parseFloat(getComputedStyle(this).getPropertyValue(e));return Number.isNaN(t)?150:t}connectedCallback(){super.connectedCallback(),requestAnimationFrame(()=>this._addTriggerListeners())}disconnectedCallback(){super.disconnectedCallback(),this._removeTriggerListeners()}updated(e){e.has(`for`)&&(this._removeTriggerListeners(e.get(`for`)),this._addTriggerListeners(),this.open&&!e.has(`open`)&&this._repositionToTrigger()),e.has(`open`)&&this._handleOpenChange()}show(){this.open||=!0}hide(){this.open&&=!1}toggle(){this.open=!this.open}async reposition(){this.open&&await this._repositionToTrigger()}async _repositionToTrigger(){this._floating.cleanupSafePolygon();let e=this.for,t=await this._floating.reposition(this._positionOptions(),()=>this.open&&this.for===e);if(t!==`stale`){if(t===`no-anchor`){this.hide();return}this._floating.markTrigger(`aria-expanded`,`true`,`false`)}}_positionOptions(){return{placement:this.placement,distance:this.distance,fullWidth:this.fullWidth}}async _handleOpenChange(){let e=this._popoverEl;if(!e)return;let t=this._positionOptions();if(this.open){if(e.showPopover(),await this._floating.updatePosition(t),!this.open)return;await this._floating.animateShow(e,this._getDuration(`--show-duration`)),this._floating.startPositioning(t),this._floating.markTrigger(`aria-expanded`,`true`,`false`)}else this._floating.stopPositioning(),this._floating.cleanupSafePolygon(),this._floating.releaseTrigger(`false`),await this._floating.animateHide(e,this._getDuration(`--hide-duration`)),e.matches(`:popover-open`)&&e.hidePopover()}_addTriggerListeners(){this._floating.addTriggerListeners({onPointerEnter:this._onPointerEnter,onPointerLeave:this._onPointerLeave,onFocusIn:this._onFocusIn,onFocusOut:this._onFocusOut,onClick:this._onClick,onKeyDown:this._onKeyDown})}_removeTriggerListeners(e){this._floating.removeTriggerListeners(e?this._triggerFor(e):void 0)}render(){return i`
2
2
  <div
3
3
  id=${this._popoverId}
4
4
  popover="auto"
@@ -1 +1 @@
1
- {"version":3,"file":"popover.js","names":[],"sources":["../../../src/html/elements/popover/popover.css?inline","../../../src/html/elements/popover/popover.ts"],"sourcesContent":[":host {\n --background: var(--l-color-surface-overlay);\n --color: inherit;\n --border-radius: 6px;\n --max-width: 320px;\n --shadow: var(--l-shadow-md);\n --arrow-size: 8px;\n --show-duration: 150ms;\n --hide-duration: 150ms;\n\n --_border-color: var(--l-color-border-overlay);\n\n display: contents;\n}\n\n[popover] {\n inset: unset;\n overflow: visible;\n isolation: isolate;\n box-sizing: border-box;\n width: max-content;\n max-width: var(--max-width);\n border: 1px solid var(--_border-color);\n border-radius: var(--border-radius);\n background: var(--background);\n color: var(--color);\n font-size: 0.875rem;\n line-height: 1.5;\n box-shadow: var(--shadow);\n}\n\n:host([full-width]) [popover] {\n width: 100vw;\n max-width: none;\n}\n\ni {\n position: absolute;\n display: block;\n width: var(--arrow-size);\n height: var(--arrow-size);\n background: var(--background);\n transform: rotate(45deg);\n z-index: -1;\n}\n\n:host([data-placement^='top']) i {\n border-right: 1px solid var(--_border-color);\n border-bottom: 1px solid var(--_border-color);\n}\n\n:host([data-placement^='bottom']) i {\n border-top: 1px solid var(--_border-color);\n border-left: 1px solid var(--_border-color);\n}\n\n:host([data-placement^='left']) i {\n border-right: 1px solid var(--_border-color);\n border-top: 1px solid var(--_border-color);\n}\n\n:host([data-placement^='right']) i {\n border-left: 1px solid var(--_border-color);\n border-bottom: 1px solid var(--_border-color);\n}\n","import { html, nothing, unsafeCSS, type PropertyValues } from 'lit';\nimport { LuxenElement } from '../../shared/luxen-element.js';\nimport { property } from 'lit/decorators.js';\nimport type { Placement } from '@floating-ui/dom';\nimport { PopoverController } from '../../shared/controllers/popover.js';\nimport { uniqueId } from '../../registry.js';\nimport hostStyles from '../../shared/styles/host.styles.js';\nimport rawStyles from './popover.css?inline';\n\nconst styles = unsafeCSS(rawStyles);\n\n/**\n * @summary A popover that displays interactive content anchored to a trigger.\n * @customElement l-popover\n *\n * @slot - Popover content.\n *\n * @csspart body - The popover container.\n * @csspart arrow - The directional arrow element.\n *\n * @cssproperty --background - Background color. Default: `Canvas`.\n * @cssproperty --color - Text color. Default: inherited.\n * @cssproperty --border-radius - Border radius. Default `8px`.\n * @cssproperty --max-width - Maximum width. Default `320px`.\n * @cssproperty --shadow - Box shadow.\n * @cssproperty --arrow-size - Arrow size. Default `8px`.\n * @cssproperty --show-duration - Show animation duration. Default `150ms`.\n * @cssproperty --hide-duration - Hide animation duration. Default `150ms`.\n */\nexport class Popover extends LuxenElement {\n static override styles = [hostStyles, styles];\n\n private _popoverId = uniqueId('popover');\n\n private _floating = new PopoverController(this, {\n getTriggerElement: () => this._trigger,\n getFloatingElement: () => this._popoverEl,\n getArrowElement: () => this._arrowEl,\n onPlacementChange: (p) => {\n this.dataset.placement = p;\n },\n });\n\n /** The HTML id of the element triggering the popover. */\n @property()\n accessor for = '';\n\n /** The preferred placement of the popover. */\n @property()\n accessor placement: Placement = 'bottom';\n\n /** The distance in pixels from the target element. */\n @property({ type: Number })\n accessor distance = 8;\n\n /** Whether or not the popover is visible. */\n @property({ type: Boolean, reflect: true })\n accessor open = false;\n\n /** Hide the directional arrow. */\n @property({ type: Boolean, reflect: true, attribute: 'without-arrow' })\n accessor withoutArrow = false;\n\n /** Stretch the popover to the viewport width. Useful for mega menus. */\n @property({ type: Boolean, reflect: true, attribute: 'full-width' })\n accessor fullWidth = false;\n\n /** Space-separated list of trigger modes: `click`, `hover`, `focus`, `manual`. */\n @property()\n accessor trigger = 'click';\n\n private _hasTrigger(type: string) {\n return this.trigger.split(' ').includes(type);\n }\n\n private get _trigger(): HTMLElement | null {\n return this.for ? (this.getRootNode() as Document | ShadowRoot).getElementById(this.for) : null;\n }\n\n private get _popoverEl(): HTMLElement {\n return this.shadowRoot!.querySelector('[popover]')!;\n }\n\n private get _arrowEl(): HTMLElement | null {\n return this.withoutArrow ? null : this.shadowRoot!.querySelector('i');\n }\n\n private _getDuration(prop: '--show-duration' | '--hide-duration'): number {\n const parsed = parseFloat(getComputedStyle(this).getPropertyValue(prop));\n return Number.isNaN(parsed) ? 150 : parsed;\n }\n\n override connectedCallback() {\n super.connectedCallback();\n requestAnimationFrame(() => this._addTriggerListeners());\n }\n\n override disconnectedCallback() {\n super.disconnectedCallback();\n this._removeTriggerListeners();\n }\n\n override updated(changed: PropertyValues<this>) {\n if (changed.has('open')) {\n void this._handleOpenChange();\n }\n if (changed.has('for')) {\n this._removeTriggerListeners(changed.get('for') as string);\n this._addTriggerListeners();\n }\n }\n\n show() {\n if (!this.open) this.open = true;\n }\n\n hide() {\n if (this.open) this.open = false;\n }\n\n toggle() {\n this.open = !this.open;\n }\n\n private async _handleOpenChange() {\n const popover = this._popoverEl;\n if (!popover) return;\n\n const posOpts = {\n placement: this.placement,\n distance: this.distance,\n fullWidth: this.fullWidth,\n };\n\n if (this.open) {\n popover.showPopover();\n await this._floating.updatePosition(posOpts);\n if (!this.open) return;\n await this._floating.animateShow(popover, this._getDuration('--show-duration'));\n this._floating.startPositioning(posOpts);\n this._trigger?.setAttribute('aria-expanded', 'true');\n // No aria-controls: the panel's id lives in this element's shadow root and\n // IDREFs don't cross shadow boundaries, so the reference would never\n // resolve (invalid per WCAG 4.1.2). aria-expanded alone conveys the state.\n } else {\n this._floating.stopPositioning();\n this._floating.cleanupSafePolygon();\n this._trigger?.setAttribute('aria-expanded', 'false');\n await this._floating.animateHide(popover, this._getDuration('--hide-duration'));\n if (popover.matches(':popover-open')) popover.hidePopover();\n }\n }\n\n // --- Trigger event handlers ---\n\n private _onPointerEnter = () => {\n if (!this._hasTrigger('hover')) return;\n this._floating.cleanupSafePolygon();\n this.show();\n };\n\n private _onPointerLeave = (e: PointerEvent) => {\n if (!this._hasTrigger('hover') || !this.open) return;\n this._floating.handlePointerLeave(e, () => this.hide());\n };\n\n private _onFocusIn = () => {\n if (this._hasTrigger('focus')) this.show();\n };\n private _onFocusOut = () => {\n if (this._hasTrigger('focus')) this.hide();\n };\n private _onClick = () => {\n if (this._hasTrigger('click')) this.toggle();\n };\n\n private _onKeyDown = (e: KeyboardEvent) => {\n if (this.open && e.key === 'Escape') {\n e.stopPropagation();\n this.hide();\n }\n };\n\n /** Sync `open` when popover=\"auto\" light-dismiss fires. */\n private _onToggle = (e: Event) => {\n const toggleEvent = e as ToggleEvent;\n if (toggleEvent.newState === 'closed' && this.open) {\n this.open = false;\n }\n };\n\n private _addTriggerListeners() {\n this._floating.addTriggerListeners({\n onPointerEnter: this._onPointerEnter,\n onPointerLeave: this._onPointerLeave,\n onFocusIn: this._onFocusIn,\n onFocusOut: this._onFocusOut,\n onClick: this._onClick,\n onKeyDown: this._onKeyDown,\n });\n }\n\n private _removeTriggerListeners(forId?: string) {\n const trigger = forId\n ? (this.getRootNode() as Document | ShadowRoot).getElementById(forId)\n : undefined;\n this._floating.removeTriggerListeners(trigger);\n }\n\n override render() {\n return html`\n <div\n id=${this._popoverId}\n popover=\"auto\"\n part=\"body\"\n @toggle=${this._onToggle}\n >\n ${this.withoutArrow\n ? nothing\n : html`\n <i\n part=\"arrow\"\n role=\"presentation\"\n ></i>\n `}\n <slot></slot>\n </div>\n `;\n }\n}\n"],"mappings":"oXCSA,IAAM,EAAS,EAAU,0tCAAS,EAoBrB,EAAb,cAA6B,CAAa,+CAGnB,EAAS,SAAS,iBAEnB,IAAI,EAAkB,KAAM,CAC9C,sBAAyB,KAAK,SAC9B,uBAA0B,KAAK,WAC/B,oBAAuB,KAAK,SAC5B,kBAAoB,GAAM,CACxB,KAAK,QAAQ,UAAY,CAC3B,CACF,CAAC,UAIc,WAIiB,iBAIZ,UAIJ,WAIQ,WAIH,WAIF,iCAsFa,CACzB,KAAK,YAAY,OAAO,IAC7B,KAAK,UAAU,mBAAmB,EAClC,KAAK,KAAK,EACZ,uBAE2B,GAAoB,CACzC,CAAC,KAAK,YAAY,OAAO,GAAK,CAAC,KAAK,MACxC,KAAK,UAAU,mBAAmB,MAAS,KAAK,KAAK,CAAC,CACxD,sBAE2B,CACrB,KAAK,YAAY,OAAO,GAAG,KAAK,KAAK,CAC3C,uBAC4B,CACtB,KAAK,YAAY,OAAO,GAAG,KAAK,KAAK,CAC3C,oBACyB,CACnB,KAAK,YAAY,OAAO,GAAG,KAAK,OAAO,CAC7C,kBAEsB,GAAqB,CACrC,KAAK,MAAQ,EAAE,MAAQ,WACzB,EAAE,gBAAgB,EAClB,KAAK,KAAK,EAEd,iBAGqB,GAAa,CAE5B,EAAY,WAAa,UAAY,KAAK,OAC5C,KAAK,KAAO,GAEhB,qBA/JyB,CAAC,EAAY,CAAM,SAenC,KAAA,4CAIA,WAAA,kDAIA,UAAA,iDAIA,MAAA,6CAIA,cAAA,qDAIA,WAAA,kDAIA,SAAA,yCAET,YAAoB,EAAc,CAChC,OAAO,KAAK,QAAQ,MAAM,GAAG,CAAC,CAAC,SAAS,CAAI,CAC9C,CAEA,IAAY,UAA+B,CACzC,OAAO,KAAK,IAAO,KAAK,YAAY,CAAC,CAA2B,eAAe,KAAK,GAAG,EAAI,IAC7F,CAEA,IAAY,YAA0B,CACpC,OAAO,KAAK,WAAY,cAAc,WAAW,CACnD,CAEA,IAAY,UAA+B,CACzC,OAAO,KAAK,aAAe,KAAO,KAAK,WAAY,cAAc,GAAG,CACtE,CAEA,aAAqB,EAAqD,CACxE,IAAM,EAAS,WAAW,iBAAiB,IAAI,CAAC,CAAC,iBAAiB,CAAI,CAAC,EACvE,OAAO,OAAO,MAAM,CAAM,EAAI,IAAM,CACtC,CAEA,mBAA6B,CAC3B,MAAM,kBAAkB,EACxB,0BAA4B,KAAK,qBAAqB,CAAC,CACzD,CAEA,sBAAgC,CAC9B,MAAM,qBAAqB,EAC3B,KAAK,wBAAwB,CAC/B,CAEA,QAAiB,EAA+B,CAC1C,EAAQ,IAAI,MAAM,GACpB,KAAU,kBAAkB,EAE1B,EAAQ,IAAI,KAAK,IACnB,KAAK,wBAAwB,EAAQ,IAAI,KAAK,CAAW,EACzD,KAAK,qBAAqB,EAE9B,CAEA,MAAO,CACL,AAAgB,KAAK,OAAO,EAC9B,CAEA,MAAO,CACL,AAAe,KAAK,OAAO,EAC7B,CAEA,QAAS,CACP,KAAK,KAAO,CAAC,KAAK,IACpB,CAEA,MAAc,mBAAoB,CAChC,IAAM,EAAU,KAAK,WACrB,GAAI,CAAC,EAAS,OAEd,IAAM,EAAU,CACd,UAAW,KAAK,UAChB,SAAU,KAAK,SACf,UAAW,KAAK,SAClB,EAEA,GAAI,KAAK,KAAM,CAGb,GAFA,EAAQ,YAAY,EACpB,MAAM,KAAK,UAAU,eAAe,CAAO,EACvC,CAAC,KAAK,KAAM,OAChB,MAAM,KAAK,UAAU,YAAY,EAAS,KAAK,aAAa,iBAAiB,CAAC,EAC9E,KAAK,UAAU,iBAAiB,CAAO,EACvC,KAAK,UAAU,aAAa,gBAAiB,MAAM,CAIrD,MACE,KAAK,UAAU,gBAAgB,EAC/B,KAAK,UAAU,mBAAmB,EAClC,KAAK,UAAU,aAAa,gBAAiB,OAAO,EACpD,MAAM,KAAK,UAAU,YAAY,EAAS,KAAK,aAAa,iBAAiB,CAAC,EAC1E,EAAQ,QAAQ,eAAe,GAAG,EAAQ,YAAY,CAE9D,CAwCA,sBAA+B,CAC7B,KAAK,UAAU,oBAAoB,CACjC,eAAgB,KAAK,gBACrB,eAAgB,KAAK,gBACrB,UAAW,KAAK,WAChB,WAAY,KAAK,YACjB,QAAS,KAAK,SACd,UAAW,KAAK,UAClB,CAAC,CACH,CAEA,wBAAgC,EAAgB,CAC9C,IAAM,EAAU,EACX,KAAK,YAAY,CAAC,CAA2B,eAAe,CAAK,EAClE,IAAA,GACJ,KAAK,UAAU,uBAAuB,CAAO,CAC/C,CAEA,QAAkB,CAChB,MAAO,EAAI;;aAEF,KAAK,WAAW;;;kBAGX,KAAK,UAAU;;UAEvB,KAAK,aACH,EACA,CAAI;;;;;cAKF;;;KAIZ,CACF,KAzLG,EAAS,CAAA,EAAA,EAAA,UAAA,MAAA,IAAA,KAIT,EAAS,CAAA,EAAA,EAAA,UAAA,YAAA,IAAA,KAIT,EAAS,CAAE,KAAM,MAAO,CAAC,CAAA,EAAA,EAAA,UAAA,WAAA,IAAA,KAIzB,EAAS,CAAE,KAAM,QAAS,QAAS,EAAK,CAAC,CAAA,EAAA,EAAA,UAAA,OAAA,IAAA,KAIzC,EAAS,CAAE,KAAM,QAAS,QAAS,GAAM,UAAW,eAAgB,CAAC,CAAA,EAAA,EAAA,UAAA,eAAA,IAAA,KAIrE,EAAS,CAAE,KAAM,QAAS,QAAS,GAAM,UAAW,YAAa,CAAC,CAAA,EAAA,EAAA,UAAA,YAAA,IAAA,KAIlE,EAAS,CAAA,EAAA,EAAA,UAAA,UAAA,IAAA"}
1
+ {"version":3,"file":"popover.js","names":[],"sources":["../../../src/html/elements/popover/popover.css?inline","../../../src/html/elements/popover/popover.ts"],"sourcesContent":[":host {\n --background: var(--l-color-surface-overlay);\n --color: inherit;\n --border-radius: 6px;\n --max-width: 320px;\n --shadow: var(--l-shadow-md);\n --arrow-size: 8px;\n --show-duration: 150ms;\n --hide-duration: 150ms;\n\n --_border-color: var(--l-color-border-overlay);\n\n display: contents;\n}\n\n[popover] {\n inset: unset;\n overflow: visible;\n isolation: isolate;\n box-sizing: border-box;\n width: max-content;\n max-width: var(--max-width);\n border: 1px solid var(--_border-color);\n border-radius: var(--border-radius);\n background: var(--background);\n color: var(--color);\n font-size: 0.875rem;\n line-height: 1.5;\n box-shadow: var(--shadow);\n}\n\n:host([full-width]) [popover] {\n width: 100vw;\n max-width: none;\n}\n\ni {\n position: absolute;\n display: block;\n width: var(--arrow-size);\n height: var(--arrow-size);\n background: var(--background);\n transform: rotate(45deg);\n z-index: -1;\n}\n\n:host([data-placement^='top']) i {\n border-right: 1px solid var(--_border-color);\n border-bottom: 1px solid var(--_border-color);\n}\n\n:host([data-placement^='bottom']) i {\n border-top: 1px solid var(--_border-color);\n border-left: 1px solid var(--_border-color);\n}\n\n:host([data-placement^='left']) i {\n border-right: 1px solid var(--_border-color);\n border-top: 1px solid var(--_border-color);\n}\n\n:host([data-placement^='right']) i {\n border-left: 1px solid var(--_border-color);\n border-bottom: 1px solid var(--_border-color);\n}\n","import { html, nothing, unsafeCSS, type PropertyValues } from 'lit';\nimport { LuxenElement } from '../../shared/luxen-element.js';\nimport { property } from 'lit/decorators.js';\nimport type { Placement } from '@floating-ui/dom';\nimport { PopoverController } from '../../shared/controllers/popover.js';\nimport { uniqueId } from '../../registry.js';\nimport hostStyles from '../../shared/styles/host.styles.js';\nimport rawStyles from './popover.css?inline';\n\nconst styles = unsafeCSS(rawStyles);\n\n/**\n * @summary A popover that displays interactive content anchored to a trigger.\n * @customElement l-popover\n *\n * @slot - Popover content.\n *\n * @csspart body - The popover container.\n * @csspart arrow - The directional arrow element.\n *\n * @cssproperty --background - Background color. Default: `Canvas`.\n * @cssproperty --color - Text color. Default: inherited.\n * @cssproperty --border-radius - Border radius. Default `8px`.\n * @cssproperty --max-width - Maximum width. Default `320px`.\n * @cssproperty --shadow - Box shadow.\n * @cssproperty --arrow-size - Arrow size. Default `8px`.\n * @cssproperty --show-duration - Show animation duration. Default `150ms`.\n * @cssproperty --hide-duration - Hide animation duration. Default `150ms`.\n */\nexport class Popover extends LuxenElement {\n static override styles = [hostStyles, styles];\n\n private _popoverId = uniqueId('popover');\n\n private _floating = new PopoverController(this, {\n getTriggerElement: () => this._trigger,\n getFloatingElement: () => this._popoverEl,\n getArrowElement: () => this._arrowEl,\n onPlacementChange: (p) => {\n this.dataset.placement = p;\n },\n });\n\n /** The HTML id of the element triggering the popover. */\n @property()\n accessor for = '';\n\n /** The preferred placement of the popover. */\n @property()\n accessor placement: Placement = 'bottom';\n\n /** The distance in pixels from the target element. */\n @property({ type: Number })\n accessor distance = 8;\n\n /** Whether or not the popover is visible. */\n @property({ type: Boolean, reflect: true })\n accessor open = false;\n\n /** Hide the directional arrow. */\n @property({ type: Boolean, reflect: true, attribute: 'without-arrow' })\n accessor withoutArrow = false;\n\n /** Stretch the popover to the viewport width. Useful for mega menus. */\n @property({ type: Boolean, reflect: true, attribute: 'full-width' })\n accessor fullWidth = false;\n\n /** Space-separated list of trigger modes: `click`, `hover`, `focus`, `manual`. */\n @property()\n accessor trigger = 'click';\n\n private _hasTrigger(type: string) {\n return this.trigger.split(' ').includes(type);\n }\n\n private get _trigger(): HTMLElement | null {\n return this._triggerFor(this.for);\n }\n\n private _triggerFor(id: string): HTMLElement | null {\n if (!id) return null;\n // A detached popover's root is a plain element with no getElementById —\n // async open/close work may land after removal.\n const root = this.getRootNode();\n return root instanceof Document || root instanceof ShadowRoot ? root.getElementById(id) : null;\n }\n\n private get _popoverEl(): HTMLElement {\n return this.shadowRoot!.querySelector('[popover]')!;\n }\n\n private get _arrowEl(): HTMLElement | null {\n return this.withoutArrow ? null : this.shadowRoot!.querySelector('i');\n }\n\n private _getDuration(prop: '--show-duration' | '--hide-duration'): number {\n const parsed = parseFloat(getComputedStyle(this).getPropertyValue(prop));\n return Number.isNaN(parsed) ? 150 : parsed;\n }\n\n override connectedCallback() {\n super.connectedCallback();\n requestAnimationFrame(() => this._addTriggerListeners());\n }\n\n override disconnectedCallback() {\n super.disconnectedCallback();\n // `aria-expanded` is dropped (not set to \"false\") by the controller's\n // hostDisconnected: with the panel gone the trigger expands nothing.\n this._removeTriggerListeners();\n }\n\n override updated(changed: PropertyValues<this>) {\n // `for` before `open`: retargeting an already-open panel has to settle\n // against the new anchor, and when both change in the same update the open\n // path owns the positioning outright (see the guard below).\n if (changed.has('for')) {\n this._removeTriggerListeners(changed.get('for') as string);\n this._addTriggerListeners();\n if (this.open && !changed.has('open')) {\n void this._repositionToTrigger();\n }\n }\n if (changed.has('open')) {\n void this._handleOpenChange();\n }\n }\n\n show() {\n if (!this.open) this.open = true;\n }\n\n hide() {\n if (this.open) this.open = false;\n }\n\n toggle() {\n this.open = !this.open;\n }\n\n /**\n * Recompute the position against the current trigger. Use it when you move\n * the anchor yourself and the panel has to follow. No-op when closed; hides\n * if the anchor is gone.\n */\n async reposition() {\n if (!this.open) return;\n await this._repositionToTrigger();\n }\n\n /**\n * Move the open panel onto whatever `for` names now — a move, not a re-open,\n * so it deliberately bypasses `_handleOpenChange` and its show animation.\n * Shared by the public `reposition()` and the retarget path so the two\n * cannot drift apart.\n */\n private async _repositionToTrigger() {\n this._floating.cleanupSafePolygon();\n const openFor = this.for;\n const result = await this._floating.reposition(\n this._positionOptions(),\n () => this.open && this.for === openFor,\n );\n // Closed or retargeted again while `computePosition` was in flight.\n if (result === 'stale') return;\n // Retargeted onto an id that resolves to nothing: a panel left floating\n // over unrelated content is worse than none.\n if (result === 'no-anchor') {\n this.hide();\n return;\n }\n this._floating.markTrigger('aria-expanded', 'true', 'false');\n }\n\n private _positionOptions() {\n return {\n placement: this.placement,\n distance: this.distance,\n fullWidth: this.fullWidth,\n };\n }\n\n private async _handleOpenChange() {\n const popover = this._popoverEl;\n if (!popover) return;\n\n const posOpts = this._positionOptions();\n\n if (this.open) {\n popover.showPopover();\n await this._floating.updatePosition(posOpts);\n if (!this.open) return;\n await this._floating.animateShow(popover, this._getDuration('--show-duration'));\n this._floating.startPositioning(posOpts);\n this._floating.markTrigger('aria-expanded', 'true', 'false');\n // No aria-controls: the panel's id lives in this element's shadow root and\n // IDREFs don't cross shadow boundaries, so the reference would never\n // resolve (invalid per WCAG 4.1.2). aria-expanded alone conveys the state.\n } else {\n this._floating.stopPositioning();\n this._floating.cleanupSafePolygon();\n this._floating.releaseTrigger('false');\n await this._floating.animateHide(popover, this._getDuration('--hide-duration'));\n if (popover.matches(':popover-open')) popover.hidePopover();\n }\n }\n\n // --- Trigger event handlers ---\n\n private _onPointerEnter = () => {\n if (!this._hasTrigger('hover')) return;\n this._floating.cleanupSafePolygon();\n this.show();\n };\n\n private _onPointerLeave = (e: PointerEvent) => {\n if (!this._hasTrigger('hover') || !this.open) return;\n this._floating.handlePointerLeave(e, () => this.hide());\n };\n\n private _onFocusIn = () => {\n if (this._hasTrigger('focus')) this.show();\n };\n private _onFocusOut = () => {\n if (this._hasTrigger('focus')) this.hide();\n };\n private _onClick = () => {\n if (this._hasTrigger('click')) this.toggle();\n };\n\n private _onKeyDown = (e: KeyboardEvent) => {\n if (this.open && e.key === 'Escape') {\n e.stopPropagation();\n this.hide();\n }\n };\n\n /** Sync `open` when popover=\"auto\" light-dismiss fires. */\n private _onToggle = (e: Event) => {\n const toggleEvent = e as ToggleEvent;\n if (toggleEvent.newState === 'closed' && this.open) {\n this.open = false;\n }\n };\n\n private _addTriggerListeners() {\n this._floating.addTriggerListeners({\n onPointerEnter: this._onPointerEnter,\n onPointerLeave: this._onPointerLeave,\n onFocusIn: this._onFocusIn,\n onFocusOut: this._onFocusOut,\n onClick: this._onClick,\n onKeyDown: this._onKeyDown,\n });\n }\n\n private _removeTriggerListeners(forId?: string) {\n this._floating.removeTriggerListeners(forId ? this._triggerFor(forId) : undefined);\n }\n\n override render() {\n return html`\n <div\n id=${this._popoverId}\n popover=\"auto\"\n part=\"body\"\n @toggle=${this._onToggle}\n >\n ${this.withoutArrow\n ? nothing\n : html`\n <i\n part=\"arrow\"\n role=\"presentation\"\n ></i>\n `}\n <slot></slot>\n </div>\n `;\n }\n}\n"],"mappings":"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"}
@@ -0,0 +1,41 @@
1
+ /**
2
+ * @summary A set of native radios, shown as the classic dot or as joined buttons — the form counterpart of a segmented control.
3
+ *
4
+ * @nativeElement fieldset
5
+ * @selector .l-radio-group
6
+ *
7
+ * @cssClass .l-radio-group - Applied to the `<fieldset>` grouping the radios. Its first child is the `<legend>` naming them; each item is a `<label>` wrapping one `<input type="radio">`.
8
+ *
9
+ * @attribute aria-label - Names the group when no visible `<legend>` fits — a view switcher in a toolbar, say. Prefer a `<legend>`: it is the technique WCAG H71 and RGAA 11.6 ask for.
10
+ * @attribute data-appearance - button — Renders the items as joined buttons: put `.l-button` on each label. Omit it for the radio primitive: put `.l-radio` on each input.
11
+ * @attribute data-orientation - horizontal | vertical — Stacks the items. Defaults to horizontal.
12
+ *
13
+ * @example Default appearance
14
+ * <fieldset class="l-radio-group" data-orientation="vertical">
15
+ * <legend>Notifications</legend>
16
+ * <label>
17
+ * <input type="radio" class="l-radio" name="notifications" value="all" checked />
18
+ * All new messages
19
+ * </label>
20
+ * <label>
21
+ * <input type="radio" class="l-radio" name="notifications" value="none" />
22
+ * Nothing
23
+ * </label>
24
+ * </fieldset>
25
+ *
26
+ * @example Button appearance
27
+ * <fieldset class="l-radio-group" data-appearance="button">
28
+ * <legend>View</legend>
29
+ * <label class="l-button">
30
+ * <input type="radio" name="view" value="list" checked />
31
+ * List
32
+ * </label>
33
+ * <label class="l-button">
34
+ * <input type="radio" name="view" value="board" />
35
+ * Board
36
+ * </label>
37
+ * </fieldset>
38
+ */
39
+ export declare class RadioGroupMeta {
40
+ }
41
+ //# sourceMappingURL=radio-group.meta.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"radio-group.meta.d.ts","sourceRoot":"","sources":["../../../src/html/elements/radio-group/radio-group.meta.ts"],"names":[],"mappings":"AASA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqCG;AAIH,qBAAa,cAAc;CAAG"}
File without changes
@@ -10,11 +10,15 @@ interface SegmentedControlEventMap {
10
10
  change: SegmentChangeEvent;
11
11
  }
12
12
  /**
13
- * @summary A single-select segmented control: a compact, mutually-exclusive
14
- * switch between a few options (radio-group semantics with a sliding pill).
13
+ * @summary A hybrid between a button group, radio buttons, and tabs: pick one
14
+ * of a few closely related options or views, and the selection applies
15
+ * immediately (radio-group semantics with a sliding pill).
15
16
  * Progressively enhances light-DOM `<button>`s so it works before JS runs.
16
- * Form-associated: give it a `name` and its selected `value` is submitted with
17
- * the form (and restored on reset), like a native radio group.
17
+ *
18
+ * The immediate effect is the point — act on `change`. The control is
19
+ * form-associated as well (`name` submits the selected `value`, reset restores
20
+ * it), but a value the user confirms by submitting is better served by native
21
+ * radios, which get keyboard, submission and validation from the platform.
18
22
  *
19
23
  * @example
20
24
  * Mark the initially-selected segment with `aria-checked="true"` (or set
@@ -1 +1 @@
1
- {"version":3,"file":"segmented-control.d.ts","sourceRoot":"","sources":["../../../src/html/elements/segmented-control/segmented-control.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,0BAA0B,EAAE,MAAM,+CAA+C,CAAC;AAE3F,MAAM,MAAM,oBAAoB,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,CAAC;AAE7D,sEAAsE;AACtE,qBAAa,kBAAmB,SAAQ,KAAK;IAC3C,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;gBACX,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM;CAKzC;AAED,UAAU,wBAAwB;IAChC,MAAM,EAAE,kBAAkB,CAAC;CAC5B;AAED;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAEH,qBAAa,gBAAiB,SAAQ,0BAA0B;IACrD,gBAAgB;IAIzB,OAAO,CAAC,YAAY,CAAS;IAC7B,OAAO,CAAC,WAAW,CAAK;IACxB,OAAO,CAAC,QAAQ,CAA2B;IAC3C,OAAO,CAAC,eAAe,CAA+B;IACtD,+EAA+E;IAC/E,OAAO,CAAC,iBAAiB,CAAoC;IAE7D,6EAA6E;IAE7E,KAAK,SAAM;IAEX,+EAA+E;IAE/E,IAAI,EAAE,oBAAoB,CAAQ;IAElC,yEAAyE;IAEzE,KAAK,CAAC,EAAE,MAAM,CAAC;IAEf,oDAAoD;IAEpD,SAAS,UAAS;IAIT,iBAAiB;IAUjB,oBAAoB;IAM7B,mFAAmF;IACnF,OAAO,CAAC,SAAS;IAOR,OAAO,CAAC,OAAO,EAAE,GAAG,CAAC,MAAM,EAAE,OAAO,CAAC;IAsBrC,iBAAiB;IAMjB,wBAAwB,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,SAAS,GAAG,cAAc;IAOjF,OAAO,CAAC,MAAM;IAyDd,OAAO,CAAC,SAAS;IAUjB,wFAAwF;IACxF,OAAO,CAAC,mBAAmB;IAe3B,OAAO,CAAC,mBAAmB;IAI3B,OAAO,CAAC,OAAO;IAuBf,qFAAqF;IACrF,OAAO,CAAC,YAAY;IASpB,OAAO,CAAC,WAAW;IAInB,8EAA8E;IAC9E,OAAO,CAAC,mBAAmB;IAiB3B,OAAO,CAAC,gBAAgB;IASxB,OAAO,CAAC,QAAQ,CAQd;IAKF,OAAO,CAAC,UAAU,CAkChB;CACH;AAQD,MAAM,WAAW,gBAAgB;IAC/B,gBAAgB,CAAC,CAAC,SAAS,MAAM,wBAAwB,EACvD,IAAI,EAAE,CAAC,EACP,QAAQ,EAAE,CAAC,IAAI,EAAE,gBAAgB,EAAE,EAAE,EAAE,wBAAwB,CAAC,CAAC,CAAC,KAAK,IAAI,EAC3E,OAAO,CAAC,EAAE,OAAO,GAAG,uBAAuB,GAC1C,IAAI,CAAC;IACR,gBAAgB,CACd,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,kCAAkC,EAC5C,OAAO,CAAC,EAAE,OAAO,GAAG,uBAAuB,GAC1C,IAAI,CAAC;IACR,mBAAmB,CAAC,CAAC,SAAS,MAAM,wBAAwB,EAC1D,IAAI,EAAE,CAAC,EACP,QAAQ,EAAE,CAAC,IAAI,EAAE,gBAAgB,EAAE,EAAE,EAAE,wBAAwB,CAAC,CAAC,CAAC,KAAK,IAAI,EAC3E,OAAO,CAAC,EAAE,OAAO,GAAG,oBAAoB,GACvC,IAAI,CAAC;IACR,mBAAmB,CACjB,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,kCAAkC,EAC5C,OAAO,CAAC,EAAE,OAAO,GAAG,oBAAoB,GACvC,IAAI,CAAC;CACT"}
1
+ {"version":3,"file":"segmented-control.d.ts","sourceRoot":"","sources":["../../../src/html/elements/segmented-control/segmented-control.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,0BAA0B,EAAE,MAAM,+CAA+C,CAAC;AAE3F,MAAM,MAAM,oBAAoB,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,CAAC;AAE7D,sEAAsE;AACtE,qBAAa,kBAAmB,SAAQ,KAAK;IAC3C,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;gBACX,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM;CAKzC;AAED,UAAU,wBAAwB;IAChC,MAAM,EAAE,kBAAkB,CAAC;CAC5B;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AAEH,qBAAa,gBAAiB,SAAQ,0BAA0B;IACrD,gBAAgB;IAIzB,OAAO,CAAC,YAAY,CAAS;IAC7B,OAAO,CAAC,WAAW,CAAK;IACxB,OAAO,CAAC,QAAQ,CAA2B;IAC3C,OAAO,CAAC,eAAe,CAA+B;IACtD,+EAA+E;IAC/E,OAAO,CAAC,iBAAiB,CAAoC;IAE7D,6EAA6E;IAE7E,KAAK,SAAM;IAEX,+EAA+E;IAE/E,IAAI,EAAE,oBAAoB,CAAQ;IAElC,yEAAyE;IAEzE,KAAK,CAAC,EAAE,MAAM,CAAC;IAEf,oDAAoD;IAEpD,SAAS,UAAS;IAIT,iBAAiB;IAUjB,oBAAoB;IAM7B,mFAAmF;IACnF,OAAO,CAAC,SAAS;IAOR,OAAO,CAAC,OAAO,EAAE,GAAG,CAAC,MAAM,EAAE,OAAO,CAAC;IAsBrC,iBAAiB;IAMjB,wBAAwB,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,SAAS,GAAG,cAAc;IAOjF,OAAO,CAAC,MAAM;IAyDd,OAAO,CAAC,SAAS;IAUjB,wFAAwF;IACxF,OAAO,CAAC,mBAAmB;IAe3B,OAAO,CAAC,mBAAmB;IAI3B,OAAO,CAAC,OAAO;IAuBf,qFAAqF;IACrF,OAAO,CAAC,YAAY;IASpB,OAAO,CAAC,WAAW;IAInB,8EAA8E;IAC9E,OAAO,CAAC,mBAAmB;IAiB3B,OAAO,CAAC,gBAAgB;IASxB,OAAO,CAAC,QAAQ,CAQd;IAKF,OAAO,CAAC,UAAU,CAkChB;CACH;AAQD,MAAM,WAAW,gBAAgB;IAC/B,gBAAgB,CAAC,CAAC,SAAS,MAAM,wBAAwB,EACvD,IAAI,EAAE,CAAC,EACP,QAAQ,EAAE,CAAC,IAAI,EAAE,gBAAgB,EAAE,EAAE,EAAE,wBAAwB,CAAC,CAAC,CAAC,KAAK,IAAI,EAC3E,OAAO,CAAC,EAAE,OAAO,GAAG,uBAAuB,GAC1C,IAAI,CAAC;IACR,gBAAgB,CACd,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,kCAAkC,EAC5C,OAAO,CAAC,EAAE,OAAO,GAAG,uBAAuB,GAC1C,IAAI,CAAC;IACR,mBAAmB,CAAC,CAAC,SAAS,MAAM,wBAAwB,EAC1D,IAAI,EAAE,CAAC,EACP,QAAQ,EAAE,CAAC,IAAI,EAAE,gBAAgB,EAAE,EAAE,EAAE,wBAAwB,CAAC,CAAC,CAAC,KAAK,IAAI,EAC3E,OAAO,CAAC,EAAE,OAAO,GAAG,oBAAoB,GACvC,IAAI,CAAC;IACR,mBAAmB,CACjB,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,kCAAkC,EAC5C,OAAO,CAAC,EAAE,OAAO,GAAG,oBAAoB,GACvC,IAAI,CAAC;CACT"}