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.
- package/README.md +1 -0
- package/cdn/color-scheme.d.ts +76 -0
- package/cdn/color-scheme.d.ts.map +1 -0
- package/cdn/color-scheme.js +2 -0
- package/cdn/color-scheme.js.map +1 -0
- package/cdn/config.d.ts +23 -0
- package/cdn/config.d.ts.map +1 -1
- package/cdn/custom-elements.json +142 -2
- package/cdn/elements/color-scheme-icon/color-scheme-icon.d.ts +55 -0
- package/cdn/elements/color-scheme-icon/color-scheme-icon.d.ts.map +1 -0
- package/cdn/elements/color-scheme-icon/color-scheme-icon.js +44 -0
- package/cdn/elements/color-scheme-icon/color-scheme-icon.js.map +1 -0
- package/cdn/elements/color-scheme-icon/index.d.ts +2 -0
- package/cdn/elements/color-scheme-icon/index.d.ts.map +1 -0
- package/cdn/elements/color-scheme-icon/index.js +2 -0
- package/cdn/elements/color-scheme-icon/index.js.map +1 -0
- package/cdn/elements/dropdown-item/dropdown-item.d.ts +9 -1
- package/cdn/elements/dropdown-item/dropdown-item.d.ts.map +1 -1
- package/cdn/elements/dropdown-item/dropdown-item.js +32 -23
- package/cdn/elements/dropdown-item/dropdown-item.js.map +1 -1
- package/cdn/elements/segmented-control/segmented-control.d.ts +2 -0
- package/cdn/elements/segmented-control/segmented-control.d.ts.map +1 -1
- package/cdn/elements/segmented-control/segmented-control.js +1 -1
- package/cdn/elements/segmented-control/segmented-control.js.map +1 -1
- package/cdn/elements/tabs/tabs.d.ts +28 -1
- package/cdn/elements/tabs/tabs.d.ts.map +1 -1
- package/cdn/elements/tabs/tabs.js +1 -1
- package/cdn/elements/tabs/tabs.js.map +1 -1
- package/cdn/index.d.ts +2 -0
- package/cdn/index.d.ts.map +1 -1
- package/cdn/index.js +1 -1
- package/cdn/registry.d.ts +1 -1
- package/cdn/registry.d.ts.map +1 -1
- package/cdn/registry.js.map +1 -1
- package/cdn/standalone.css +23 -4
- package/cdn/standalone.js.map +1 -1
- package/cdn/styles/elements/tabs/enclosed.css +9 -1
- package/cdn/styles/elements/tabs/line.css +14 -3
- package/dist/color-scheme.d.ts +76 -0
- package/dist/color-scheme.d.ts.map +1 -0
- package/dist/color-scheme.js +203 -0
- package/dist/config.d.ts +23 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/css/elements/tabs/enclosed.css +9 -1
- package/dist/css/elements/tabs/line.css +14 -3
- package/dist/custom-elements.json +142 -2
- package/dist/elements/color-scheme-icon/color-scheme-icon.css +98 -0
- package/dist/elements/color-scheme-icon/color-scheme-icon.d.ts +55 -0
- package/dist/elements/color-scheme-icon/color-scheme-icon.d.ts.map +1 -0
- package/dist/elements/color-scheme-icon/color-scheme-icon.js +138 -0
- package/dist/elements/color-scheme-icon/index.d.ts +2 -0
- package/dist/elements/color-scheme-icon/index.d.ts.map +1 -0
- package/dist/elements/color-scheme-icon/index.js +4 -0
- package/dist/elements/dropdown-item/dropdown-item.d.ts +9 -1
- package/dist/elements/dropdown-item/dropdown-item.d.ts.map +1 -1
- package/dist/elements/dropdown-item/dropdown-item.js +49 -26
- package/dist/elements/segmented-control/segmented-control.d.ts +2 -0
- package/dist/elements/segmented-control/segmented-control.d.ts.map +1 -1
- package/dist/elements/segmented-control/segmented-control.js +11 -1
- package/dist/elements/tabs/tabs.d.ts +28 -1
- package/dist/elements/tabs/tabs.d.ts.map +1 -1
- package/dist/elements/tabs/tabs.js +121 -26
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/metadata/color-scheme-icon.json +66 -0
- package/dist/metadata/dropdown-item.json +9 -1
- package/dist/metadata/index.json +82 -3
- package/dist/metadata/tabs.json +6 -1
- package/dist/registry.d.ts +1 -1
- package/dist/registry.d.ts.map +1 -1
- package/dist/templates/elements/color-scheme-icon.md +279 -0
- package/dist/templates/elements/dropdown.md +54 -1
- package/dist/templates/elements/tabs.md +40 -7
- package/elements.json +8 -0
- package/package.json +5 -1
- package/vite-plugin.ts +71 -8
package/cdn/standalone.css
CHANGED
|
@@ -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
|
}
|
package/cdn/standalone.js.map
CHANGED
|
@@ -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;;;
|
|
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,
|
package/dist/config.d.ts.map
CHANGED
|
@@ -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
|
}
|