luxen-ui 0.18.2 → 0.19.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 (77) hide show
  1. package/README.md +1 -0
  2. package/cdn/color-scheme.d.ts +76 -0
  3. package/cdn/color-scheme.d.ts.map +1 -0
  4. package/cdn/color-scheme.js +2 -0
  5. package/cdn/color-scheme.js.map +1 -0
  6. package/cdn/config.d.ts +23 -0
  7. package/cdn/config.d.ts.map +1 -1
  8. package/cdn/custom-elements.json +142 -2
  9. package/cdn/elements/color-scheme-icon/color-scheme-icon.d.ts +55 -0
  10. package/cdn/elements/color-scheme-icon/color-scheme-icon.d.ts.map +1 -0
  11. package/cdn/elements/color-scheme-icon/color-scheme-icon.js +44 -0
  12. package/cdn/elements/color-scheme-icon/color-scheme-icon.js.map +1 -0
  13. package/cdn/elements/color-scheme-icon/index.d.ts +2 -0
  14. package/cdn/elements/color-scheme-icon/index.d.ts.map +1 -0
  15. package/cdn/elements/color-scheme-icon/index.js +2 -0
  16. package/cdn/elements/color-scheme-icon/index.js.map +1 -0
  17. package/cdn/elements/dropdown-item/dropdown-item.d.ts +9 -1
  18. package/cdn/elements/dropdown-item/dropdown-item.d.ts.map +1 -1
  19. package/cdn/elements/dropdown-item/dropdown-item.js +32 -23
  20. package/cdn/elements/dropdown-item/dropdown-item.js.map +1 -1
  21. package/cdn/elements/segmented-control/segmented-control.d.ts +2 -0
  22. package/cdn/elements/segmented-control/segmented-control.d.ts.map +1 -1
  23. package/cdn/elements/segmented-control/segmented-control.js +1 -1
  24. package/cdn/elements/segmented-control/segmented-control.js.map +1 -1
  25. package/cdn/elements/tabs/tabs.d.ts +28 -1
  26. package/cdn/elements/tabs/tabs.d.ts.map +1 -1
  27. package/cdn/elements/tabs/tabs.js +1 -1
  28. package/cdn/elements/tabs/tabs.js.map +1 -1
  29. package/cdn/index.d.ts +2 -0
  30. package/cdn/index.d.ts.map +1 -1
  31. package/cdn/index.js +1 -1
  32. package/cdn/registry.d.ts +1 -1
  33. package/cdn/registry.d.ts.map +1 -1
  34. package/cdn/registry.js.map +1 -1
  35. package/cdn/standalone.css +23 -4
  36. package/cdn/standalone.js.map +1 -1
  37. package/cdn/styles/elements/tabs/enclosed.css +9 -1
  38. package/cdn/styles/elements/tabs/line.css +14 -3
  39. package/dist/color-scheme.d.ts +76 -0
  40. package/dist/color-scheme.d.ts.map +1 -0
  41. package/dist/color-scheme.js +203 -0
  42. package/dist/config.d.ts +23 -0
  43. package/dist/config.d.ts.map +1 -1
  44. package/dist/css/elements/tabs/enclosed.css +9 -1
  45. package/dist/css/elements/tabs/line.css +14 -3
  46. package/dist/custom-elements.json +142 -2
  47. package/dist/elements/color-scheme-icon/color-scheme-icon.css +98 -0
  48. package/dist/elements/color-scheme-icon/color-scheme-icon.d.ts +55 -0
  49. package/dist/elements/color-scheme-icon/color-scheme-icon.d.ts.map +1 -0
  50. package/dist/elements/color-scheme-icon/color-scheme-icon.js +138 -0
  51. package/dist/elements/color-scheme-icon/index.d.ts +2 -0
  52. package/dist/elements/color-scheme-icon/index.d.ts.map +1 -0
  53. package/dist/elements/color-scheme-icon/index.js +4 -0
  54. package/dist/elements/dropdown-item/dropdown-item.d.ts +9 -1
  55. package/dist/elements/dropdown-item/dropdown-item.d.ts.map +1 -1
  56. package/dist/elements/dropdown-item/dropdown-item.js +49 -26
  57. package/dist/elements/segmented-control/segmented-control.d.ts +2 -0
  58. package/dist/elements/segmented-control/segmented-control.d.ts.map +1 -1
  59. package/dist/elements/segmented-control/segmented-control.js +11 -1
  60. package/dist/elements/tabs/tabs.d.ts +28 -1
  61. package/dist/elements/tabs/tabs.d.ts.map +1 -1
  62. package/dist/elements/tabs/tabs.js +121 -26
  63. package/dist/index.d.ts +2 -0
  64. package/dist/index.d.ts.map +1 -1
  65. package/dist/index.js +1 -0
  66. package/dist/metadata/color-scheme-icon.json +66 -0
  67. package/dist/metadata/dropdown-item.json +9 -1
  68. package/dist/metadata/index.json +82 -3
  69. package/dist/metadata/tabs.json +6 -1
  70. package/dist/registry.d.ts +1 -1
  71. package/dist/registry.d.ts.map +1 -1
  72. package/dist/templates/elements/color-scheme-icon.md +279 -0
  73. package/dist/templates/elements/dropdown.md +54 -1
  74. package/dist/templates/elements/tabs.md +40 -7
  75. package/elements.json +8 -0
  76. package/package.json +5 -1
  77. package/vite-plugin.ts +71 -8
@@ -5406,11 +5406,19 @@ In dark mode, mixes the base color with black (default 15% black).
5406
5406
  }
5407
5407
 
5408
5408
  @media (hover: hover) {
5409
- &:hover:not([aria-selected='true']) {
5409
+ &:hover:not([aria-selected='true']):not(:disabled):not([aria-disabled='true']) {
5410
5410
  color: var(--l-color-text-primary);
5411
5411
  }
5412
5412
  }
5413
5413
 
5414
+ /* Placed after the selected and hover rules so a disabled tab still greys
5415
+ out when it is the selected one — equal specificity, source order wins. */
5416
+ &:disabled,
5417
+ &[aria-disabled='true'] {
5418
+ cursor: not-allowed;
5419
+ color: var(--l-color-text-disabled);
5420
+ }
5421
+
5414
5422
  &:focus-visible {
5415
5423
  outline: 2px solid var(--l-focus-ring);
5416
5424
  outline-offset: -2px;
@@ -5561,11 +5569,19 @@ In dark mode, mixes the base color with black (default 15% black).
5561
5569
  }
5562
5570
 
5563
5571
  @media (hover: hover) {
5564
- &:hover:not([aria-selected='true']) {
5572
+ &:hover:not([aria-selected='true']):not(:disabled):not([aria-disabled='true']) {
5565
5573
  color: var(--l-color-text-primary);
5566
5574
  }
5567
5575
  }
5568
5576
 
5577
+ /* Placed after the selected and hover rules so a disabled tab still greys
5578
+ out when it is the selected one — equal specificity, source order wins. */
5579
+ &:disabled,
5580
+ &[aria-disabled='true'] {
5581
+ cursor: not-allowed;
5582
+ color: var(--l-color-text-disabled);
5583
+ }
5584
+
5569
5585
  &:focus-visible {
5570
5586
  outline: 2px solid var(--l-focus-ring);
5571
5587
  outline-offset: -2px;
@@ -5649,9 +5665,12 @@ In dark mode, mixes the base color with black (default 15% black).
5649
5665
 
5650
5666
  /* Applies to the selected tab too — the fill is click feedback, not a
5651
5667
  selection cue (the underline carries that), so it would read as dead
5652
- if the active tab were the one place it stopped responding. */
5668
+ if the active tab were the one place it stopped responding. A disabled
5669
+ tab is the exception: `:hover` still matches it (the platform does not
5670
+ gate hover on `disabled`), and promising click feedback where no click
5671
+ will ever register is the wrong signal. */
5653
5672
  @media (hover: hover) {
5654
- &:hover::after {
5673
+ &:hover:not(:disabled):not([aria-disabled='true'])::after {
5655
5674
  background: var(--hover-color, var(--l-color-bg-state-hover));
5656
5675
  }
5657
5676
  }
@@ -1 +1 @@
1
- {"version":3,"file":"standalone.js","names":[],"sources":["../src/html/elements/alert/index.ts","../src/html/elements/alert-dialog/index.ts","../src/html/elements/avatar/index.ts","../src/html/elements/badge/index.ts","../src/html/elements/button-group/index.ts","../src/html/elements/carousel/index.ts","../src/html/elements/carousel-item/index.ts","../src/html/elements/combobox/index.ts","../src/html/elements/dialog/index.ts","../src/html/elements/divider/index.ts","../src/html/elements/drawer/index.ts","../src/html/elements/dropdown/index.ts","../src/html/elements/dropdown-item/index.ts","../src/html/elements/dropdown-label/index.ts","../src/html/elements/form-field/index.ts","../src/html/elements/icon/index.ts","../src/html/elements/input-group/index.ts","../src/html/elements/input-otp/index.ts","../src/html/elements/input-stepper/index.ts","../src/html/elements/popover/index.ts","../src/html/elements/prose-editor/index.ts","../src/html/elements/rating/index.ts","../src/html/elements/segmented-control/index.ts","../src/html/elements/select/index.ts","../src/html/elements/skeleton/index.ts","../src/html/elements/slider/index.ts","../src/html/elements/spinner/index.ts","../src/html/elements/sticky-bar/index.ts","../src/html/elements/tabs/index.ts","../src/html/elements/tag/index.ts","../src/html/elements/toast/index.ts","../src/html/elements/tooltip/index.ts","../src/html/elements/tree/index.ts","../src/html/elements/tree-item/index.ts","../src/html/registry.ts"],"sourcesContent":["import { define } from '../../define.js';\nimport { Alert } from './alert.js';\nexport * from './alert.js';\ndefine('alert', Alert);\n","import { define } from '../../define.js';\nimport { AlertDialog } from './alert-dialog.js';\nexport * from './alert-dialog.js';\ndefine('alert-dialog', AlertDialog);\n","import { define } from '../../define.js';\nimport { Avatar } from './avatar.js';\nexport * from './avatar.js';\ndefine('avatar', Avatar);\n","import { define } from '../../define.js';\nimport { Badge } from './badge.js';\nexport * from './badge.js';\ndefine('badge', Badge);\n","import { define } from '../../define.js';\nimport { ButtonGroup } from './button-group.js';\nexport * from './button-group.js';\ndefine('button-group', ButtonGroup);\n","import { define } from '../../define.js';\nimport { Carousel } from './carousel.js';\nexport * from './carousel.js';\ndefine('carousel', Carousel);\n","import { define } from '../../define.js';\nimport { CarouselItem } from './carousel-item.js';\nexport * from './carousel-item.js';\ndefine('carousel-item', CarouselItem);\n","import { define } from '../../define.js';\nimport { Combobox } from './combobox.js';\nexport * from './combobox.js';\ndefine('combobox', Combobox);\n","import { define } from '../../define.js';\nimport { Dialog } from './dialog.js';\nexport * from './dialog.js';\ndefine('dialog', Dialog);\n","import { define } from '../../define.js';\nimport { Divider } from './divider.js';\nexport * from './divider.js';\ndefine('divider', Divider);\n","import { define } from '../../define.js';\nimport { Drawer } from './drawer.js';\nexport * from './drawer.js';\ndefine('drawer', Drawer);\n","import { define } from '../../define.js';\nimport { Dropdown } from './dropdown.js';\nexport * from './dropdown.js';\ndefine('dropdown', Dropdown);\n","import { define } from '../../define.js';\nimport { DropdownItem } from './dropdown-item.js';\nexport * from './dropdown-item.js';\ndefine('dropdown-item', DropdownItem);\n","import { define } from '../../define.js';\nimport { DropdownLabel } from './dropdown-label.js';\nexport * from './dropdown-label.js';\ndefine('dropdown-label', DropdownLabel);\n","import { define } from '../../define.js';\nimport { FormField } from './form-field.js';\nexport * from './form-field.js';\ndefine('form-field', FormField);\n","import { define } from '../../define.js';\nimport { Icon } from './icon.js';\nexport * from './icon.js';\ndefine('icon', Icon);\n","import { define } from '../../define.js';\nimport { InputGroup } from './input-group.js';\nexport * from './input-group.js';\ndefine('input-group', InputGroup);\n","import { define } from '../../define.js';\nimport { InputOtp } from './input-otp.js';\nexport * from './input-otp.js';\ndefine('input-otp', InputOtp);\n","import { define } from '../../define.js';\nimport { InputStepper } from './input-stepper.js';\nexport * from './input-stepper.js';\ndefine('input-stepper', InputStepper);\n","import { define } from '../../define.js';\nimport { Popover } from './popover.js';\nexport * from './popover.js';\ndefine('popover', Popover);\n","import { define } from '../../define.js';\nimport { ProseEditor } from './prose-editor.js';\nexport * from './prose-editor.js';\ndefine('prose-editor', ProseEditor);\n","import { define } from '../../define.js';\nimport { Rating } from './rating.js';\nexport * from './rating.js';\ndefine('rating', Rating);\n","import { define } from '../../define.js';\nimport { SegmentedControl } from './segmented-control.js';\nexport * from './segmented-control.js';\ndefine('segmented-control', SegmentedControl);\n","import { define } from '../../define.js';\nimport { Select } from './select.js';\nexport * from './select.js';\ndefine('select', Select);\n","import { define } from '../../define.js';\nimport { Skeleton } from './skeleton.js';\nexport * from './skeleton.js';\ndefine('skeleton', Skeleton);\n","import { define } from '../../define.js';\nimport { Slider } from './slider.js';\nexport * from './slider.js';\ndefine('slider', Slider);\n","import { define } from '../../define.js';\nimport { Spinner } from './spinner.js';\nexport * from './spinner.js';\ndefine('spinner', Spinner);\n","import { define } from '../../define.js';\nimport { StickyBar } from './sticky-bar.js';\nexport * from './sticky-bar.js';\ndefine('sticky-bar', StickyBar);\n","import { define } from '../../define.js';\nimport { Tabs } from './tabs.js';\nexport * from './tabs.js';\ndefine('tabs', Tabs);\n","import { define } from '../../define.js';\nimport { Tag } from './tag.js';\nexport * from './tag.js';\ndefine('tag', Tag);\n","import { define } from '../../define.js';\nimport { Toast, ToastItem } from './toast.js';\nexport * from './toast.js';\ndefine('toast', Toast);\ndefine('toast-item', ToastItem);\n","import { define } from '../../define.js';\nimport { Tooltip } from './tooltip.js';\nexport * from './tooltip.js';\ndefine('tooltip', Tooltip);\n","import { define } from '../../define.js';\nimport { Tree } from './tree.js';\nexport * from './tree.js';\ndefine('tree', Tree);\n","import { define } from '../../define.js';\nimport { TreeItem } from './tree-item.js';\nexport * from './tree-item.js';\ndefine('tree-item', TreeItem);\n","/** Every component base name the library ships. */\nexport type ElementBaseName =\n | 'alert'\n | 'alert-dialog'\n | 'avatar'\n | 'badge'\n | 'button-group'\n | 'carousel'\n | 'carousel-item'\n | 'combobox'\n | 'dialog'\n | 'divider'\n | 'drawer'\n | 'dropdown'\n | 'dropdown-item'\n | 'dropdown-label'\n | 'form-field'\n | 'icon'\n | 'input-group'\n | 'input-otp'\n | 'input-stepper'\n | 'popover'\n | 'prose-editor'\n | 'skeleton'\n | 'slider'\n | 'spinner'\n | 'rating'\n | 'segmented-control'\n | 'select'\n | 'sticky-bar'\n | 'stories'\n | 'story'\n | 'stories-viewer'\n | 'tabs'\n | 'tag'\n | 'toast'\n | 'toast-item'\n | 'tooltip'\n | 'tree'\n | 'tree-item';\n\nlet _elementPrefix = 'l';\nlet _cssPrefix = 'l';\nconst _registered = new Set<ElementBaseName>();\n\n/** Set the global prefix. Must be called before defining elements. */\nexport function setPrefix(prefix: { element: string; css: string }): void {\n _elementPrefix = prefix.element;\n _cssPrefix = prefix.css;\n}\n\n/** Get the current prefixes. */\nexport function getPrefix(): { element: string; css: string } {\n return { element: _elementPrefix, css: _cssPrefix };\n}\n\n/** Derive the full tag name from element prefix + base name. */\nexport function tagName(baseName: ElementBaseName): string {\n return `${_elementPrefix}-${baseName}`;\n}\n\n/** Prefix a CSS class name. `cls('toast-icon')` → `'l-toast-icon'` */\nexport function cls(name: string): string {\n return `${_cssPrefix}-${name}`;\n}\n\n/** Generate a globally unique prefixed ID. `uniqueId('toast')` → `'l:toast:1'` */\nlet _uid = 0;\nexport function uniqueId(base: string): string {\n return `${_cssPrefix}:${base}:${++_uid}`;\n}\n\n/** Check if a base name is already registered. */\nexport function isRegistered(baseName: ElementBaseName): boolean {\n return _registered.has(baseName);\n}\n\n/** Mark a base name as registered. Called internally by define(). */\nexport function markRegistered(baseName: ElementBaseName): void {\n _registered.add(baseName);\n}\n"],"mappings":";AAGA,UAAO,SAAS,KAAK;;;ACArB,UAAO,gBAAgB,WAAW;;;ACAlC,UAAO,UAAU,MAAM;;;ACAvB,UAAO,SAAS,KAAK;;;ACArB,UAAO,gBAAgB,WAAW;;;ACAlC,UAAO,YAAY,QAAQ;;;ACA3B,UAAO,iBAAiB,YAAY;;;ACApC,UAAO,YAAY,QAAQ;;;ACA3B,UAAO,UAAU,MAAM;;;ACAvB,UAAO,WAAW,OAAO;;;ACAzB,UAAO,UAAU,MAAM;;;ACAvB,UAAO,YAAY,QAAQ;;;ACA3B,UAAO,iBAAiB,YAAY;;;ACApC,UAAO,kBAAkB,aAAa;;;ACAtC,UAAO,cAAc,SAAS;;;ACA9B,UAAO,QAAQ,IAAI;;;ACAnB,UAAO,eAAe,UAAU;;;ACAhC,UAAO,aAAa,QAAQ;;;ACA5B,UAAO,iBAAiB,YAAY;;;ACApC,UAAO,WAAW,OAAO;;;ACAzB,UAAO,gBAAgB,WAAW;;;ACAlC,UAAO,UAAU,MAAM;;;ACAvB,UAAO,qBAAqB,gBAAgB;;;ACA5C,UAAO,UAAU,MAAM;;;ACAvB,SAAO,YAAY,QAAQ;;;ACA3B,SAAO,UAAU,MAAM;;;ACAvB,SAAO,WAAW,OAAO;;;ACAzB,SAAO,cAAc,SAAS;;;ACA9B,SAAO,QAAQ,IAAI;;;ACAnB,SAAO,OAAO,GAAG;;;ACAjB,SAAO,SAAS,KAAK;AACrB,SAAO,cAAc,SAAS;;;ACD9B,SAAO,WAAW,OAAO;;;ACAzB,SAAO,QAAQ,IAAI;;;ACAnB,OAAO,aAAa,QAAQ;;;ACsC5B,IAAI,iBAAiB;AACrB,IAAI,aAAa;;AAIjB,SAAgB,UAAU,QAAgD;CACxE,iBAAiB,OAAO;CACxB,aAAa,OAAO;AACtB;;AAGA,SAAgB,YAA8C;CAC5D,OAAO;EAAE,SAAS;EAAgB,KAAK;CAAW;AACpD"}
1
+ {"version":3,"file":"standalone.js","names":[],"sources":["../src/html/elements/alert/index.ts","../src/html/elements/alert-dialog/index.ts","../src/html/elements/avatar/index.ts","../src/html/elements/badge/index.ts","../src/html/elements/button-group/index.ts","../src/html/elements/carousel/index.ts","../src/html/elements/carousel-item/index.ts","../src/html/elements/combobox/index.ts","../src/html/elements/dialog/index.ts","../src/html/elements/divider/index.ts","../src/html/elements/drawer/index.ts","../src/html/elements/dropdown/index.ts","../src/html/elements/dropdown-item/index.ts","../src/html/elements/dropdown-label/index.ts","../src/html/elements/form-field/index.ts","../src/html/elements/icon/index.ts","../src/html/elements/input-group/index.ts","../src/html/elements/input-otp/index.ts","../src/html/elements/input-stepper/index.ts","../src/html/elements/popover/index.ts","../src/html/elements/prose-editor/index.ts","../src/html/elements/rating/index.ts","../src/html/elements/segmented-control/index.ts","../src/html/elements/select/index.ts","../src/html/elements/skeleton/index.ts","../src/html/elements/slider/index.ts","../src/html/elements/spinner/index.ts","../src/html/elements/sticky-bar/index.ts","../src/html/elements/tabs/index.ts","../src/html/elements/tag/index.ts","../src/html/elements/toast/index.ts","../src/html/elements/tooltip/index.ts","../src/html/elements/tree/index.ts","../src/html/elements/tree-item/index.ts","../src/html/registry.ts"],"sourcesContent":["import { define } from '../../define.js';\nimport { Alert } from './alert.js';\nexport * from './alert.js';\ndefine('alert', Alert);\n","import { define } from '../../define.js';\nimport { AlertDialog } from './alert-dialog.js';\nexport * from './alert-dialog.js';\ndefine('alert-dialog', AlertDialog);\n","import { define } from '../../define.js';\nimport { Avatar } from './avatar.js';\nexport * from './avatar.js';\ndefine('avatar', Avatar);\n","import { define } from '../../define.js';\nimport { Badge } from './badge.js';\nexport * from './badge.js';\ndefine('badge', Badge);\n","import { define } from '../../define.js';\nimport { ButtonGroup } from './button-group.js';\nexport * from './button-group.js';\ndefine('button-group', ButtonGroup);\n","import { define } from '../../define.js';\nimport { Carousel } from './carousel.js';\nexport * from './carousel.js';\ndefine('carousel', Carousel);\n","import { define } from '../../define.js';\nimport { CarouselItem } from './carousel-item.js';\nexport * from './carousel-item.js';\ndefine('carousel-item', CarouselItem);\n","import { define } from '../../define.js';\nimport { Combobox } from './combobox.js';\nexport * from './combobox.js';\ndefine('combobox', Combobox);\n","import { define } from '../../define.js';\nimport { Dialog } from './dialog.js';\nexport * from './dialog.js';\ndefine('dialog', Dialog);\n","import { define } from '../../define.js';\nimport { Divider } from './divider.js';\nexport * from './divider.js';\ndefine('divider', Divider);\n","import { define } from '../../define.js';\nimport { Drawer } from './drawer.js';\nexport * from './drawer.js';\ndefine('drawer', Drawer);\n","import { define } from '../../define.js';\nimport { Dropdown } from './dropdown.js';\nexport * from './dropdown.js';\ndefine('dropdown', Dropdown);\n","import { define } from '../../define.js';\nimport { DropdownItem } from './dropdown-item.js';\nexport * from './dropdown-item.js';\ndefine('dropdown-item', DropdownItem);\n","import { define } from '../../define.js';\nimport { DropdownLabel } from './dropdown-label.js';\nexport * from './dropdown-label.js';\ndefine('dropdown-label', DropdownLabel);\n","import { define } from '../../define.js';\nimport { FormField } from './form-field.js';\nexport * from './form-field.js';\ndefine('form-field', FormField);\n","import { define } from '../../define.js';\nimport { Icon } from './icon.js';\nexport * from './icon.js';\ndefine('icon', Icon);\n","import { define } from '../../define.js';\nimport { InputGroup } from './input-group.js';\nexport * from './input-group.js';\ndefine('input-group', InputGroup);\n","import { define } from '../../define.js';\nimport { InputOtp } from './input-otp.js';\nexport * from './input-otp.js';\ndefine('input-otp', InputOtp);\n","import { define } from '../../define.js';\nimport { InputStepper } from './input-stepper.js';\nexport * from './input-stepper.js';\ndefine('input-stepper', InputStepper);\n","import { define } from '../../define.js';\nimport { Popover } from './popover.js';\nexport * from './popover.js';\ndefine('popover', Popover);\n","import { define } from '../../define.js';\nimport { ProseEditor } from './prose-editor.js';\nexport * from './prose-editor.js';\ndefine('prose-editor', ProseEditor);\n","import { define } from '../../define.js';\nimport { Rating } from './rating.js';\nexport * from './rating.js';\ndefine('rating', Rating);\n","import { define } from '../../define.js';\nimport { SegmentedControl } from './segmented-control.js';\nexport * from './segmented-control.js';\ndefine('segmented-control', SegmentedControl);\n","import { define } from '../../define.js';\nimport { Select } from './select.js';\nexport * from './select.js';\ndefine('select', Select);\n","import { define } from '../../define.js';\nimport { Skeleton } from './skeleton.js';\nexport * from './skeleton.js';\ndefine('skeleton', Skeleton);\n","import { define } from '../../define.js';\nimport { Slider } from './slider.js';\nexport * from './slider.js';\ndefine('slider', Slider);\n","import { define } from '../../define.js';\nimport { Spinner } from './spinner.js';\nexport * from './spinner.js';\ndefine('spinner', Spinner);\n","import { define } from '../../define.js';\nimport { StickyBar } from './sticky-bar.js';\nexport * from './sticky-bar.js';\ndefine('sticky-bar', StickyBar);\n","import { define } from '../../define.js';\nimport { Tabs } from './tabs.js';\nexport * from './tabs.js';\ndefine('tabs', Tabs);\n","import { define } from '../../define.js';\nimport { Tag } from './tag.js';\nexport * from './tag.js';\ndefine('tag', Tag);\n","import { define } from '../../define.js';\nimport { Toast, ToastItem } from './toast.js';\nexport * from './toast.js';\ndefine('toast', Toast);\ndefine('toast-item', ToastItem);\n","import { define } from '../../define.js';\nimport { Tooltip } from './tooltip.js';\nexport * from './tooltip.js';\ndefine('tooltip', Tooltip);\n","import { define } from '../../define.js';\nimport { Tree } from './tree.js';\nexport * from './tree.js';\ndefine('tree', Tree);\n","import { define } from '../../define.js';\nimport { TreeItem } from './tree-item.js';\nexport * from './tree-item.js';\ndefine('tree-item', TreeItem);\n","/** Every component base name the library ships. */\nexport type ElementBaseName =\n | 'alert'\n | 'alert-dialog'\n | 'avatar'\n | 'badge'\n | 'button-group'\n | 'carousel'\n | 'carousel-item'\n | 'combobox'\n | 'dialog'\n | 'divider'\n | 'drawer'\n | 'dropdown'\n | 'dropdown-item'\n | 'dropdown-label'\n | 'form-field'\n | 'icon'\n | 'input-group'\n | 'input-otp'\n | 'input-stepper'\n | 'popover'\n | 'prose-editor'\n | 'skeleton'\n | 'slider'\n | 'spinner'\n | 'rating'\n | 'segmented-control'\n | 'select'\n | 'sticky-bar'\n | 'stories'\n | 'story'\n | 'stories-viewer'\n | 'tabs'\n | 'tag'\n | 'color-scheme-icon'\n | 'toast'\n | 'toast-item'\n | 'tooltip'\n | 'tree'\n | 'tree-item';\n\nlet _elementPrefix = 'l';\nlet _cssPrefix = 'l';\nconst _registered = new Set<ElementBaseName>();\n\n/** Set the global prefix. Must be called before defining elements. */\nexport function setPrefix(prefix: { element: string; css: string }): void {\n _elementPrefix = prefix.element;\n _cssPrefix = prefix.css;\n}\n\n/** Get the current prefixes. */\nexport function getPrefix(): { element: string; css: string } {\n return { element: _elementPrefix, css: _cssPrefix };\n}\n\n/** Derive the full tag name from element prefix + base name. */\nexport function tagName(baseName: ElementBaseName): string {\n return `${_elementPrefix}-${baseName}`;\n}\n\n/** Prefix a CSS class name. `cls('toast-icon')` → `'l-toast-icon'` */\nexport function cls(name: string): string {\n return `${_cssPrefix}-${name}`;\n}\n\n/** Generate a globally unique prefixed ID. `uniqueId('toast')` → `'l:toast:1'` */\nlet _uid = 0;\nexport function uniqueId(base: string): string {\n return `${_cssPrefix}:${base}:${++_uid}`;\n}\n\n/** Check if a base name is already registered. */\nexport function isRegistered(baseName: ElementBaseName): boolean {\n return _registered.has(baseName);\n}\n\n/** Mark a base name as registered. Called internally by define(). */\nexport function markRegistered(baseName: ElementBaseName): void {\n _registered.add(baseName);\n}\n"],"mappings":";AAGA,UAAO,SAAS,KAAK;;;ACArB,UAAO,gBAAgB,WAAW;;;ACAlC,UAAO,UAAU,MAAM;;;ACAvB,UAAO,SAAS,KAAK;;;ACArB,UAAO,gBAAgB,WAAW;;;ACAlC,UAAO,YAAY,QAAQ;;;ACA3B,UAAO,iBAAiB,YAAY;;;ACApC,UAAO,YAAY,QAAQ;;;ACA3B,UAAO,UAAU,MAAM;;;ACAvB,UAAO,WAAW,OAAO;;;ACAzB,UAAO,UAAU,MAAM;;;ACAvB,UAAO,YAAY,QAAQ;;;ACA3B,UAAO,iBAAiB,YAAY;;;ACApC,UAAO,kBAAkB,aAAa;;;ACAtC,UAAO,cAAc,SAAS;;;ACA9B,UAAO,QAAQ,IAAI;;;ACAnB,UAAO,eAAe,UAAU;;;ACAhC,UAAO,aAAa,QAAQ;;;ACA5B,UAAO,iBAAiB,YAAY;;;ACApC,UAAO,WAAW,OAAO;;;ACAzB,UAAO,gBAAgB,WAAW;;;ACAlC,UAAO,UAAU,MAAM;;;ACAvB,UAAO,qBAAqB,gBAAgB;;;ACA5C,UAAO,UAAU,MAAM;;;ACAvB,SAAO,YAAY,QAAQ;;;ACA3B,SAAO,UAAU,MAAM;;;ACAvB,SAAO,WAAW,OAAO;;;ACAzB,SAAO,cAAc,SAAS;;;ACA9B,SAAO,QAAQ,IAAI;;;ACAnB,SAAO,OAAO,GAAG;;;ACAjB,SAAO,SAAS,KAAK;AACrB,SAAO,cAAc,SAAS;;;ACD9B,SAAO,WAAW,OAAO;;;ACAzB,SAAO,QAAQ,IAAI;;;ACAnB,OAAO,aAAa,QAAQ;;;ACuC5B,IAAI,iBAAiB;AACrB,IAAI,aAAa;;AAIjB,SAAgB,UAAU,QAAgD;CACxE,iBAAiB,OAAO;CACxB,aAAa,OAAO;AACtB;;AAGA,SAAgB,YAA8C;CAC5D,OAAO;EAAE,SAAS;EAAgB,KAAK;CAAW;AACpD"}
@@ -73,11 +73,19 @@
73
73
  }
74
74
 
75
75
  @media (hover: hover) {
76
- &:hover:not([aria-selected='true']) {
76
+ &:hover:not([aria-selected='true']):not(:disabled):not([aria-disabled='true']) {
77
77
  color: var(--l-color-text-primary);
78
78
  }
79
79
  }
80
80
 
81
+ /* Placed after the selected and hover rules so a disabled tab still greys
82
+ out when it is the selected one — equal specificity, source order wins. */
83
+ &:disabled,
84
+ &[aria-disabled='true'] {
85
+ cursor: not-allowed;
86
+ color: var(--l-color-text-disabled);
87
+ }
88
+
81
89
  &:focus-visible {
82
90
  outline: 2px solid var(--l-focus-ring);
83
91
  outline-offset: -2px;
@@ -73,11 +73,19 @@
73
73
  }
74
74
 
75
75
  @media (hover: hover) {
76
- &:hover:not([aria-selected='true']) {
76
+ &:hover:not([aria-selected='true']):not(:disabled):not([aria-disabled='true']) {
77
77
  color: var(--l-color-text-primary);
78
78
  }
79
79
  }
80
80
 
81
+ /* Placed after the selected and hover rules so a disabled tab still greys
82
+ out when it is the selected one — equal specificity, source order wins. */
83
+ &:disabled,
84
+ &[aria-disabled='true'] {
85
+ cursor: not-allowed;
86
+ color: var(--l-color-text-disabled);
87
+ }
88
+
81
89
  &:focus-visible {
82
90
  outline: 2px solid var(--l-focus-ring);
83
91
  outline-offset: -2px;
@@ -161,9 +169,12 @@
161
169
 
162
170
  /* Applies to the selected tab too — the fill is click feedback, not a
163
171
  selection cue (the underline carries that), so it would read as dead
164
- if the active tab were the one place it stopped responding. */
172
+ if the active tab were the one place it stopped responding. A disabled
173
+ tab is the exception: `:hover` still matches it (the platform does not
174
+ gate hover on `disabled`), and promising click feedback where no click
175
+ will ever register is the wrong signal. */
165
176
  @media (hover: hover) {
166
- &:hover::after {
177
+ &:hover:not(:disabled):not([aria-disabled='true'])::after {
167
178
  background: var(--hover-color, var(--l-color-bg-state-hover));
168
179
  }
169
180
  }
@@ -0,0 +1,76 @@
1
+ /**
2
+ * Page-level light/dark state, shared by every control that shows or changes it.
3
+ *
4
+ * Deliberately not an element: the scheme is one value per document, and a
5
+ * document can hold several controls for it — a header button, a menu row, a
6
+ * settings page. Whichever one the user touches, all of them have to agree, so
7
+ * the state belongs to the page rather than to any of them.
8
+ *
9
+ * The model is the one argued in Lea Verou's *Dark mode toggles should be a
10
+ * two-state switch* (2026): three values — an explicit `light`, an explicit
11
+ * `dark`, or no preference at all — but only two ever shown. That falls out of
12
+ * two rules, both implemented here:
13
+ *
14
+ * 1. An override is stored **only when it differs from the OS preference**.
15
+ * Choosing the scheme the OS already reports removes the override instead,
16
+ * handing control back to the system. So a user on a dark OS who switches to
17
+ * light and back is following the system again, not pinned to dark — and
18
+ * "system" stays reachable without a third button.
19
+ * 2. The stored value is read **only when the user acts**. If the OS flips while
20
+ * an explicit override exists, the override survives: the user asked for it,
21
+ * and nothing they did says otherwise.
22
+ *
23
+ * SSR-safe: nothing here touches `window`, `document` or `localStorage` at
24
+ * module scope, and listeners are attached lazily on the first subscription.
25
+ */
26
+ export type ColorScheme = 'light' | 'dark';
27
+ /** Called whenever the effective scheme changes, from any source. */
28
+ export type ColorSchemeListener = (scheme: ColorScheme, overridden: boolean) => void;
29
+ /** Where the store writes `color-scheme`, if anywhere. */
30
+ export type ColorSchemeApply = 'root' | false;
31
+ export interface ColorSchemeConfig {
32
+ /**
33
+ * `localStorage` key holding the override. Set to `''` to disable persistence
34
+ * entirely — the choice then lasts for the session only.
35
+ */
36
+ storageKey?: string;
37
+ /**
38
+ * Write `color-scheme` on `<html>` when the scheme changes. Off by default:
39
+ * most applications already own a color-mode story, and silently rewriting
40
+ * `documentElement` would fight it. Turn it on for a plain page.
41
+ */
42
+ apply?: ColorSchemeApply;
43
+ }
44
+ declare class ColorSchemeStore {
45
+ #private;
46
+ /** The scheme actually in effect: a stored override, else the OS preference. */
47
+ get current(): ColorScheme;
48
+ /** Whether an override is in place, rather than the OS preference being followed. */
49
+ get overridden(): boolean;
50
+ /**
51
+ * Choose a scheme. Rule 1 lives here: the override is written only when it
52
+ * disagrees with the OS, and released when it agrees.
53
+ */
54
+ set(next: ColorScheme): void;
55
+ /** Flip to the other scheme. Returns the scheme now in effect. */
56
+ toggle(): ColorScheme;
57
+ /**
58
+ * Observe the effective scheme. The listener is called **immediately with the
59
+ * current value**, then on a user change, on an OS change with no override in
60
+ * place, and on a change made in another tab. Returns an unsubscribe function;
61
+ * the last unsubscribe releases the listeners.
62
+ *
63
+ * The immediate call is deliberate. A subscriber's job is almost always to
64
+ * mirror the scheme somewhere — an `aria-pressed`, a `checked`, a class — and
65
+ * a change-only contract leaves that mirror wrong until the first change,
66
+ * which is a bug nobody sees until a screen reader hits it. Callers that only
67
+ * want transitions can ignore the first call.
68
+ */
69
+ subscribe(listener: ColorSchemeListener): () => void;
70
+ /** Set the storage key and whether the store writes `color-scheme` on `<html>`. */
71
+ configure(config: ColorSchemeConfig): void;
72
+ }
73
+ /** The document's light/dark state. One instance per page. */
74
+ export declare const colorScheme: ColorSchemeStore;
75
+ export {};
76
+ //# sourceMappingURL=color-scheme.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"color-scheme.d.ts","sourceRoot":"","sources":["../src/html/color-scheme.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAEH,MAAM,MAAM,WAAW,GAAG,OAAO,GAAG,MAAM,CAAC;AAE3C,qEAAqE;AACrE,MAAM,MAAM,mBAAmB,GAAG,CAAC,MAAM,EAAE,WAAW,EAAE,UAAU,EAAE,OAAO,KAAK,IAAI,CAAC;AAErF,0DAA0D;AAC1D,MAAM,MAAM,gBAAgB,GAAG,MAAM,GAAG,KAAK,CAAC;AAE9C,MAAM,WAAW,iBAAiB;IAChC;;;OAGG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB;;;;OAIG;IACH,KAAK,CAAC,EAAE,gBAAgB,CAAC;CAC1B;AA8BD,cAAM,gBAAgB;;IAuBpB,gFAAgF;IAChF,IAAI,OAAO,IAAI,WAAW,CAEzB;IAED,qFAAqF;IACrF,IAAI,UAAU,IAAI,OAAO,CAExB;IAMD;;;OAGG;IACH,GAAG,CAAC,IAAI,EAAE,WAAW,GAAG,IAAI;IAuB5B,kEAAkE;IAClE,MAAM,IAAI,WAAW;IAKrB;;;;;;;;;;;OAWG;IACH,SAAS,CAAC,QAAQ,EAAE,mBAAmB,GAAG,MAAM,IAAI;IAUpD,mFAAmF;IACnF,SAAS,CAAC,MAAM,EAAE,iBAAiB,GAAG,IAAI;CAuD3C;AAED,8DAA8D;AAC9D,eAAO,MAAM,WAAW,kBAAyB,CAAC"}
@@ -0,0 +1,203 @@
1
+ /**
2
+ * Page-level light/dark state, shared by every control that shows or changes it.
3
+ *
4
+ * Deliberately not an element: the scheme is one value per document, and a
5
+ * document can hold several controls for it — a header button, a menu row, a
6
+ * settings page. Whichever one the user touches, all of them have to agree, so
7
+ * the state belongs to the page rather than to any of them.
8
+ *
9
+ * The model is the one argued in Lea Verou's *Dark mode toggles should be a
10
+ * two-state switch* (2026): three values — an explicit `light`, an explicit
11
+ * `dark`, or no preference at all — but only two ever shown. That falls out of
12
+ * two rules, both implemented here:
13
+ *
14
+ * 1. An override is stored **only when it differs from the OS preference**.
15
+ * Choosing the scheme the OS already reports removes the override instead,
16
+ * handing control back to the system. So a user on a dark OS who switches to
17
+ * light and back is following the system again, not pinned to dark — and
18
+ * "system" stays reachable without a third button.
19
+ * 2. The stored value is read **only when the user acts**. If the OS flips while
20
+ * an explicit override exists, the override survives: the user asked for it,
21
+ * and nothing they did says otherwise.
22
+ *
23
+ * SSR-safe: nothing here touches `window`, `document` or `localStorage` at
24
+ * module scope, and listeners are attached lazily on the first subscription.
25
+ */
26
+ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
27
+ if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
28
+ if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
29
+ return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
30
+ };
31
+ var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
32
+ if (kind === "m") throw new TypeError("Private method is not writable");
33
+ if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
34
+ if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
35
+ return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
36
+ };
37
+ var _ColorSchemeStore_instances, _ColorSchemeStore_session, _ColorSchemeStore_listeners, _ColorSchemeStore_media, _ColorSchemeStore_last, _ColorSchemeStore_lastOverridden, _ColorSchemeStore_onOsChange, _ColorSchemeStore_onStorageChange, _ColorSchemeStore_override_get, _ColorSchemeStore_applyToRoot, _ColorSchemeStore_notify, _ColorSchemeStore_bind, _ColorSchemeStore_unbind;
38
+ /*
39
+ * Defaults live as module-level bindings, not class fields, so that
40
+ * `luxen-ui/vite-plugin` can bake a project's `luxen.config.mjs` values in at
41
+ * build time by rewriting these two initialisers — the same mechanism it uses
42
+ * for `_elementPrefix` / `_cssPrefix` in `registry.ts`. `configure()` stays the
43
+ * runtime path for anything the build cannot know.
44
+ */
45
+ let _storageKey = 'luxen-color-scheme';
46
+ let _apply = false;
47
+ const DARK_QUERY = '(prefers-color-scheme: dark)';
48
+ /** The OS-level preference. `light` when the query is unsupported. */
49
+ function osScheme() {
50
+ return globalThis.matchMedia?.(DARK_QUERY).matches ? 'dark' : 'light';
51
+ }
52
+ function readStored(key) {
53
+ if (!key)
54
+ return null;
55
+ try {
56
+ const value = localStorage.getItem(key);
57
+ return value === 'light' || value === 'dark' ? value : null;
58
+ }
59
+ catch {
60
+ // Safari private mode, disabled storage, sandboxed iframe.
61
+ return null;
62
+ }
63
+ }
64
+ class ColorSchemeStore {
65
+ constructor() {
66
+ _ColorSchemeStore_instances.add(this);
67
+ /**
68
+ * In-memory copy of the override, used when persistence is off or when the
69
+ * browser refuses to write. Without it a blocked write would leave every
70
+ * control unable to move at all.
71
+ */
72
+ _ColorSchemeStore_session.set(this, null);
73
+ _ColorSchemeStore_listeners.set(this, new Set());
74
+ _ColorSchemeStore_media.set(this, null);
75
+ _ColorSchemeStore_last.set(this, null);
76
+ _ColorSchemeStore_lastOverridden.set(this, false);
77
+ _ColorSchemeStore_onOsChange.set(this, () => __classPrivateFieldGet(this, _ColorSchemeStore_instances, "m", _ColorSchemeStore_notify).call(this));
78
+ _ColorSchemeStore_onStorageChange.set(this, (event) => {
79
+ // `key === null` means the whole store was cleared.
80
+ if (event.key !== null && event.key !== _storageKey)
81
+ return;
82
+ // Another tab is the authority now: drop this page's session copy so a
83
+ // release made over there is not undone by a stale local value.
84
+ if (_storageKey)
85
+ __classPrivateFieldSet(this, _ColorSchemeStore_session, null, "f");
86
+ __classPrivateFieldGet(this, _ColorSchemeStore_instances, "m", _ColorSchemeStore_notify).call(this);
87
+ });
88
+ }
89
+ /** The scheme actually in effect: a stored override, else the OS preference. */
90
+ get current() {
91
+ return __classPrivateFieldGet(this, _ColorSchemeStore_instances, "a", _ColorSchemeStore_override_get) ?? osScheme();
92
+ }
93
+ /** Whether an override is in place, rather than the OS preference being followed. */
94
+ get overridden() {
95
+ return __classPrivateFieldGet(this, _ColorSchemeStore_instances, "a", _ColorSchemeStore_override_get) !== null;
96
+ }
97
+ /**
98
+ * Choose a scheme. Rule 1 lives here: the override is written only when it
99
+ * disagrees with the OS, and released when it agrees.
100
+ */
101
+ set(next) {
102
+ const release = next === osScheme();
103
+ if (_storageKey) {
104
+ try {
105
+ if (release)
106
+ localStorage.removeItem(_storageKey);
107
+ else
108
+ localStorage.setItem(_storageKey, next);
109
+ // Storage took it, so it is the single authority — drop any session copy
110
+ // left over from a period when it was unavailable.
111
+ __classPrivateFieldSet(this, _ColorSchemeStore_session, null, "f");
112
+ __classPrivateFieldGet(this, _ColorSchemeStore_instances, "m", _ColorSchemeStore_applyToRoot).call(this);
113
+ __classPrivateFieldGet(this, _ColorSchemeStore_instances, "m", _ColorSchemeStore_notify).call(this);
114
+ return;
115
+ }
116
+ catch {
117
+ // Refused — fall through to memory.
118
+ }
119
+ }
120
+ __classPrivateFieldSet(this, _ColorSchemeStore_session, release ? null : next, "f");
121
+ __classPrivateFieldGet(this, _ColorSchemeStore_instances, "m", _ColorSchemeStore_applyToRoot).call(this);
122
+ __classPrivateFieldGet(this, _ColorSchemeStore_instances, "m", _ColorSchemeStore_notify).call(this);
123
+ }
124
+ /** Flip to the other scheme. Returns the scheme now in effect. */
125
+ toggle() {
126
+ this.set(this.current === 'dark' ? 'light' : 'dark');
127
+ return this.current;
128
+ }
129
+ /**
130
+ * Observe the effective scheme. The listener is called **immediately with the
131
+ * current value**, then on a user change, on an OS change with no override in
132
+ * place, and on a change made in another tab. Returns an unsubscribe function;
133
+ * the last unsubscribe releases the listeners.
134
+ *
135
+ * The immediate call is deliberate. A subscriber's job is almost always to
136
+ * mirror the scheme somewhere — an `aria-pressed`, a `checked`, a class — and
137
+ * a change-only contract leaves that mirror wrong until the first change,
138
+ * which is a bug nobody sees until a screen reader hits it. Callers that only
139
+ * want transitions can ignore the first call.
140
+ */
141
+ subscribe(listener) {
142
+ __classPrivateFieldGet(this, _ColorSchemeStore_listeners, "f").add(listener);
143
+ __classPrivateFieldGet(this, _ColorSchemeStore_instances, "m", _ColorSchemeStore_bind).call(this);
144
+ listener(this.current, this.overridden);
145
+ return () => {
146
+ __classPrivateFieldGet(this, _ColorSchemeStore_listeners, "f").delete(listener);
147
+ if (__classPrivateFieldGet(this, _ColorSchemeStore_listeners, "f").size === 0)
148
+ __classPrivateFieldGet(this, _ColorSchemeStore_instances, "m", _ColorSchemeStore_unbind).call(this);
149
+ };
150
+ }
151
+ /** Set the storage key and whether the store writes `color-scheme` on `<html>`. */
152
+ configure(config) {
153
+ if (config.storageKey !== undefined)
154
+ _storageKey = config.storageKey;
155
+ if (config.apply !== undefined) {
156
+ const wasApplying = _apply === 'root';
157
+ _apply = config.apply;
158
+ // Leave no trace when application is switched off.
159
+ if (wasApplying && config.apply === false) {
160
+ globalThis.document?.documentElement.style.removeProperty('color-scheme');
161
+ }
162
+ }
163
+ __classPrivateFieldGet(this, _ColorSchemeStore_instances, "m", _ColorSchemeStore_applyToRoot).call(this);
164
+ __classPrivateFieldGet(this, _ColorSchemeStore_instances, "m", _ColorSchemeStore_notify).call(this);
165
+ }
166
+ }
167
+ _ColorSchemeStore_session = new WeakMap(), _ColorSchemeStore_listeners = new WeakMap(), _ColorSchemeStore_media = new WeakMap(), _ColorSchemeStore_last = new WeakMap(), _ColorSchemeStore_lastOverridden = new WeakMap(), _ColorSchemeStore_onOsChange = new WeakMap(), _ColorSchemeStore_onStorageChange = new WeakMap(), _ColorSchemeStore_instances = new WeakSet(), _ColorSchemeStore_override_get = function _ColorSchemeStore_override_get() {
168
+ return readStored(_storageKey) ?? __classPrivateFieldGet(this, _ColorSchemeStore_session, "f");
169
+ }, _ColorSchemeStore_applyToRoot = function _ColorSchemeStore_applyToRoot() {
170
+ if (_apply !== 'root')
171
+ return;
172
+ const root = globalThis.document?.documentElement;
173
+ if (root)
174
+ root.style.colorScheme = __classPrivateFieldGet(this, _ColorSchemeStore_instances, "a", _ColorSchemeStore_override_get) ?? 'light dark';
175
+ }, _ColorSchemeStore_notify = function _ColorSchemeStore_notify() {
176
+ const next = this.current;
177
+ const overridden = this.overridden;
178
+ // Both halves of the payload are compared, not just the scheme: releasing an
179
+ // override that already agreed with the OS leaves `current` untouched while
180
+ // `overridden` flips, and a subscriber mirroring it — a "following the
181
+ // system" badge, a tri-state settings control — would never hear about it.
182
+ if (next === __classPrivateFieldGet(this, _ColorSchemeStore_last, "f") && overridden === __classPrivateFieldGet(this, _ColorSchemeStore_lastOverridden, "f"))
183
+ return;
184
+ __classPrivateFieldSet(this, _ColorSchemeStore_last, next, "f");
185
+ __classPrivateFieldSet(this, _ColorSchemeStore_lastOverridden, overridden, "f");
186
+ for (const listener of __classPrivateFieldGet(this, _ColorSchemeStore_listeners, "f"))
187
+ listener(next, overridden);
188
+ }, _ColorSchemeStore_bind = function _ColorSchemeStore_bind() {
189
+ if (__classPrivateFieldGet(this, _ColorSchemeStore_media, "f"))
190
+ return;
191
+ __classPrivateFieldSet(this, _ColorSchemeStore_media, globalThis.matchMedia?.(DARK_QUERY) ?? null, "f");
192
+ __classPrivateFieldGet(this, _ColorSchemeStore_media, "f")?.addEventListener('change', __classPrivateFieldGet(this, _ColorSchemeStore_onOsChange, "f"));
193
+ globalThis.addEventListener?.('storage', __classPrivateFieldGet(this, _ColorSchemeStore_onStorageChange, "f"));
194
+ __classPrivateFieldSet(this, _ColorSchemeStore_last, this.current, "f");
195
+ __classPrivateFieldSet(this, _ColorSchemeStore_lastOverridden, this.overridden, "f");
196
+ __classPrivateFieldGet(this, _ColorSchemeStore_instances, "m", _ColorSchemeStore_applyToRoot).call(this);
197
+ }, _ColorSchemeStore_unbind = function _ColorSchemeStore_unbind() {
198
+ __classPrivateFieldGet(this, _ColorSchemeStore_media, "f")?.removeEventListener('change', __classPrivateFieldGet(this, _ColorSchemeStore_onOsChange, "f"));
199
+ __classPrivateFieldSet(this, _ColorSchemeStore_media, null, "f");
200
+ globalThis.removeEventListener?.('storage', __classPrivateFieldGet(this, _ColorSchemeStore_onStorageChange, "f"));
201
+ };
202
+ /** The document's light/dark state. One instance per page. */
203
+ export const colorScheme = new ColorSchemeStore();
package/dist/config.d.ts CHANGED
@@ -43,6 +43,24 @@ export interface LuxenEmitTypesConfig {
43
43
  /** Overwrite the file even when it exists. Default `false`. */
44
44
  force?: boolean;
45
45
  }
46
+ /** Build-time defaults for the `luxen-ui/color-scheme` store. */
47
+ export interface LuxenColorSchemeConfig {
48
+ /**
49
+ * `localStorage` key holding the light/dark override. Default
50
+ * `'luxen-color-scheme'`. Set to `''` to disable persistence entirely.
51
+ *
52
+ * Change it here and the before-first-paint snippet in your `<head>` has to
53
+ * read the same key — that snippet runs before any module and cannot be
54
+ * rewritten by the build.
55
+ */
56
+ storageKey?: string;
57
+ /**
58
+ * Have the store write `color-scheme` on `<html>` itself. Default `false`,
59
+ * because most applications already own a color-mode story; `'root'` is the
60
+ * batteries-included path for a plain page.
61
+ */
62
+ apply?: 'root' | false;
63
+ }
46
64
  /**
47
65
  * Shape of `luxen.config.mjs`. Both the Vite plugin and the CLI read this
48
66
  * file; each tool ignores the fields that don't apply to it.
@@ -66,6 +84,11 @@ export interface LuxenConfig {
66
84
  cssImportPath?: string;
67
85
  /** npm specifier used in `import` examples in `references/`. Default `'luxen-ui'`. */
68
86
  jsImportPath?: string;
87
+ /**
88
+ * Defaults for `luxen-ui/color-scheme`, baked in at build time so a project
89
+ * never has to call `colorScheme.configure()` from its entry point.
90
+ */
91
+ colorScheme?: LuxenColorSchemeConfig;
69
92
  /**
70
93
  * Emit a prefix-aware `.d.ts` for `HTMLElementTagNameMap` (and optionally
71
94
  * Vue's `GlobalComponents`). Pass a path string for the default behaviour,
@@ -1 +1 @@
1
- {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/html/config.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,mDAAmD;AACnD,MAAM,MAAM,oBAAoB,GAAG,MAAM,CAAC;AAE1C,kEAAkE;AAClE,MAAM,WAAW,oBAAoB;IACnC,yEAAyE;IACzE,IAAI,EAAE,MAAM,CAAC;IACb;;;;;;OAMG;IACH,MAAM,CAAC,EAAE,KAAK,GAAG,KAAK,CAAC;IACvB,8DAA8D;IAC9D,QAAQ,CAAC,EAAE,SAAS,oBAAoB,EAAE,CAAC;IAC3C;;;;OAIG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,+DAA+D;IAC/D,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAED;;;GAGG;AACH,MAAM,WAAW,WAAW;IAE1B,6EAA6E;IAC7E,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,iEAAiE;IACjE,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,wDAAwD;IACxD,WAAW,CAAC,EAAE,MAAM,CAAC;IAGrB,uDAAuD;IACvD,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,0FAA0F;IAC1F,SAAS,CAAC,EAAE,MAAM,CAAC;IAGnB,6EAA6E;IAC7E,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,yEAAyE;IACzE,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,2FAA2F;IAC3F,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,sFAAsF;IACtF,YAAY,CAAC,EAAE,MAAM,CAAC;IAGtB;;;;OAIG;IACH,SAAS,CAAC,EAAE,MAAM,GAAG,oBAAoB,CAAC;CAC3C;AAED;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,CAAC,SAAS,WAAW,EAAE,MAAM,EAAE,CAAC,GAAG,CAAC,CAEhE"}
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/html/config.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,mDAAmD;AACnD,MAAM,MAAM,oBAAoB,GAAG,MAAM,CAAC;AAE1C,kEAAkE;AAClE,MAAM,WAAW,oBAAoB;IACnC,yEAAyE;IACzE,IAAI,EAAE,MAAM,CAAC;IACb;;;;;;OAMG;IACH,MAAM,CAAC,EAAE,KAAK,GAAG,KAAK,CAAC;IACvB,8DAA8D;IAC9D,QAAQ,CAAC,EAAE,SAAS,oBAAoB,EAAE,CAAC;IAC3C;;;;OAIG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,+DAA+D;IAC/D,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAED,iEAAiE;AACjE,MAAM,WAAW,sBAAsB;IACrC;;;;;;;OAOG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB;;;;OAIG;IACH,KAAK,CAAC,EAAE,MAAM,GAAG,KAAK,CAAC;CACxB;AAED;;;GAGG;AACH,MAAM,WAAW,WAAW;IAE1B,6EAA6E;IAC7E,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,iEAAiE;IACjE,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,wDAAwD;IACxD,WAAW,CAAC,EAAE,MAAM,CAAC;IAGrB,uDAAuD;IACvD,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,0FAA0F;IAC1F,SAAS,CAAC,EAAE,MAAM,CAAC;IAGnB,6EAA6E;IAC7E,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,yEAAyE;IACzE,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,2FAA2F;IAC3F,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,sFAAsF;IACtF,YAAY,CAAC,EAAE,MAAM,CAAC;IAGtB;;;OAGG;IACH,WAAW,CAAC,EAAE,sBAAsB,CAAC;IAGrC;;;;OAIG;IACH,SAAS,CAAC,EAAE,MAAM,GAAG,oBAAoB,CAAC;CAC3C;AAED;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,CAAC,SAAS,WAAW,EAAE,MAAM,EAAE,CAAC,GAAG,CAAC,CAEhE"}
@@ -73,11 +73,19 @@
73
73
  }
74
74
 
75
75
  @media (hover: hover) {
76
- &:hover:not([aria-selected='true']) {
76
+ &:hover:not([aria-selected='true']):not(:disabled):not([aria-disabled='true']) {
77
77
  color: var(--l-color-text-primary);
78
78
  }
79
79
  }
80
80
 
81
+ /* Placed after the selected and hover rules so a disabled tab still greys
82
+ out when it is the selected one — equal specificity, source order wins. */
83
+ &:disabled,
84
+ &[aria-disabled='true'] {
85
+ cursor: not-allowed;
86
+ color: var(--l-color-text-disabled);
87
+ }
88
+
81
89
  &:focus-visible {
82
90
  outline: 2px solid var(--l-focus-ring);
83
91
  outline-offset: -2px;
@@ -73,11 +73,19 @@
73
73
  }
74
74
 
75
75
  @media (hover: hover) {
76
- &:hover:not([aria-selected='true']) {
76
+ &:hover:not([aria-selected='true']):not(:disabled):not([aria-disabled='true']) {
77
77
  color: var(--l-color-text-primary);
78
78
  }
79
79
  }
80
80
 
81
+ /* Placed after the selected and hover rules so a disabled tab still greys
82
+ out when it is the selected one — equal specificity, source order wins. */
83
+ &:disabled,
84
+ &[aria-disabled='true'] {
85
+ cursor: not-allowed;
86
+ color: var(--l-color-text-disabled);
87
+ }
88
+
81
89
  &:focus-visible {
82
90
  outline: 2px solid var(--l-focus-ring);
83
91
  outline-offset: -2px;
@@ -161,9 +169,12 @@
161
169
 
162
170
  /* Applies to the selected tab too — the fill is click feedback, not a
163
171
  selection cue (the underline carries that), so it would read as dead
164
- if the active tab were the one place it stopped responding. */
172
+ if the active tab were the one place it stopped responding. A disabled
173
+ tab is the exception: `:hover` still matches it (the platform does not
174
+ gate hover on `disabled`), and promising click feedback where no click
175
+ will ever register is the wrong signal. */
165
176
  @media (hover: hover) {
166
- &:hover::after {
177
+ &:hover:not(:disabled):not([aria-disabled='true'])::after {
167
178
  background: var(--hover-color, var(--l-color-bg-state-hover));
168
179
  }
169
180
  }