@rcarls/rc-listbox 0.1.0 → 0.3.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 +24 -7
- package/dist/custom-elements.json +196 -91
- package/dist/rc-listbox-CTedy14U.js +337 -0
- package/dist/rc-listbox-CTedy14U.js.map +1 -0
- package/dist/rc-listbox-define.js +1 -1
- package/dist/rc-listbox.js +1 -1
- package/dist/types/packages/rc-listbox/src/rc-listbox.d.ts +138 -58
- package/package.json +7 -10
- package/dist/demo.css +0 -85
- package/dist/demo.js +0 -40
- package/dist/rc-listbox-D1vHntE2.js +0 -291
- package/dist/rc-listbox-D1vHntE2.js.map +0 -1
|
@@ -1,43 +1,67 @@
|
|
|
1
1
|
import { LitElement } from 'lit';
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
2
|
+
import { ActiveDescendantController, ItemsCollectionController, ItemsCollectionActionOption, ItemsCollectionOption, ItemsCollectionSelectableOption, ItemsCollectionFilterStrategy } from '@rcarls/rc-common';
|
|
3
|
+
declare global {
|
|
4
|
+
interface HTMLElementTagNameMap {
|
|
5
|
+
'rc-listbox': RCListbox;
|
|
6
|
+
}
|
|
6
7
|
}
|
|
8
|
+
/** Option shape accepted by `rc-listbox`. */
|
|
9
|
+
export type ListboxOption = ItemsCollectionOption;
|
|
10
|
+
export type ListboxSelectableOption = ItemsCollectionSelectableOption;
|
|
11
|
+
export type ListboxActionOption<Action extends string = string> = ItemsCollectionActionOption & {
|
|
12
|
+
action: Action;
|
|
13
|
+
};
|
|
7
14
|
/**
|
|
8
|
-
*
|
|
15
|
+
* Listbox that keeps option DOM in light DOM for aria-activedescendant navigation,
|
|
16
|
+
* following the WAI-ARIA Listbox pattern.
|
|
9
17
|
*
|
|
10
|
-
* - `'prefix'
|
|
11
|
-
* - `'contains'
|
|
12
|
-
* - `function
|
|
18
|
+
* - `'prefix'`: label must start with the query (default, matches native `<select>` type-ahead).
|
|
19
|
+
* - `'contains'`: label must contain the query anywhere.
|
|
20
|
+
* - `function`: custom predicate; receives lowercased label and query, return `true` to show the option.
|
|
13
21
|
*/
|
|
14
|
-
export type FilterStrategy =
|
|
15
|
-
export interface
|
|
22
|
+
export type FilterStrategy = ItemsCollectionFilterStrategy;
|
|
23
|
+
export interface RCListboxSelectChangeEvent {
|
|
24
|
+
reason: 'select';
|
|
16
25
|
/** Canonical selected value for single mode, or selected values for multi mode. */
|
|
17
26
|
value: string | string[];
|
|
18
27
|
/** Whether the option was selected (false means it was deselected). */
|
|
19
28
|
selected: boolean;
|
|
20
|
-
/** The option value that was activated.
|
|
29
|
+
/** The option value that was activated. */
|
|
21
30
|
optionValue: string;
|
|
22
31
|
/** The option that was activated. */
|
|
23
|
-
option:
|
|
24
|
-
/** Selected values
|
|
32
|
+
option: ListboxSelectableOption;
|
|
33
|
+
/** Selected values array (length=1 for single-selection mode). */
|
|
25
34
|
selectedValues: string[];
|
|
26
|
-
/** Selected
|
|
27
|
-
selectedOptions:
|
|
35
|
+
/** Selected options. */
|
|
36
|
+
selectedOptions: ListboxSelectableOption[];
|
|
28
37
|
}
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
38
|
+
export interface RCListboxActionChangeEvent<Action extends string = string> {
|
|
39
|
+
reason: 'action';
|
|
40
|
+
/** Current canonical selected value; action rows do not mutate selection. */
|
|
41
|
+
value: string | string[];
|
|
42
|
+
/** Action activations never select the action row. */
|
|
43
|
+
selected: false;
|
|
44
|
+
/** The action option value that was activated. */
|
|
45
|
+
optionValue: string;
|
|
46
|
+
/** The action option that was activated. */
|
|
47
|
+
option: ListboxActionOption<Action>;
|
|
48
|
+
/** The activated action command. */
|
|
49
|
+
action: Action;
|
|
50
|
+
/** Selected values array (length=1 for single-selection mode). */
|
|
51
|
+
selectedValues: string[];
|
|
52
|
+
/** Selected options. */
|
|
53
|
+
selectedOptions: ListboxSelectableOption[];
|
|
33
54
|
}
|
|
55
|
+
export type RCListboxChangeEvent = RCListboxSelectChangeEvent | RCListboxActionChangeEvent;
|
|
34
56
|
/**
|
|
35
|
-
* A
|
|
57
|
+
* A WAI-ARIA listbox component that renders a `<ul role="presentation">`
|
|
58
|
+
* + `<li role="option">` subtree directly into its own light DOM to facilitate
|
|
59
|
+
* `aria-activedescendant` virtual navigation with IDs inside the same document
|
|
60
|
+
* or shadow root.
|
|
36
61
|
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
* work correctly from the parent trigger.
|
|
62
|
+
* Pre-rendered `<ul>/<li>` children are accepted as a progressive enhancement
|
|
63
|
+
* baseline. The component reads them on first connect if no `options` setter
|
|
64
|
+
* has been called.
|
|
41
65
|
*
|
|
42
66
|
* Consumers drive this component via its JS API:
|
|
43
67
|
* - Set `options` to populate the list
|
|
@@ -46,54 +70,78 @@ declare global {
|
|
|
46
70
|
* - Call `filterOptions()` to filter visible options
|
|
47
71
|
* - Read `navigableItems` to feed `ActiveDescendantController`
|
|
48
72
|
*
|
|
49
|
-
* @
|
|
73
|
+
* @see {@link https://richardcarls.github.io/rc-webcomponents/components/rc-listbox rc-listbox docs}
|
|
74
|
+
* @see {@link https://www.w3.org/WAI/ARIA/apg/patterns/listbox/ WAI-ARIA Listbox pattern}
|
|
75
|
+
*
|
|
76
|
+
* @slot — Accepts pre-rendered `<ul>` with `<li>` children for progressive enhancement.
|
|
77
|
+
*
|
|
50
78
|
* @fires rc-listbox-change - Fired when an option is activated (clicked or Enter/Space)
|
|
51
|
-
*
|
|
52
|
-
* @csspart option
|
|
53
|
-
* @csspart option-
|
|
79
|
+
*
|
|
80
|
+
* @csspart option - Individual `<li role="option">` elements
|
|
81
|
+
* @csspart option-checkmark - The checkmark `<span>` inside each option (when `checkmark` is true)
|
|
54
82
|
* @csspart create-option - The "Create" option when allow-create is active
|
|
83
|
+
*
|
|
84
|
+
* @cssprop [--rc-listbox-option-gap=0.25rem] - Gap between the checkmark and option label.
|
|
85
|
+
* @cssprop [--rc-listbox-option-min-block-size=0px] - Minimum block size (height) of each option row.
|
|
86
|
+
* @cssprop [--rc-listbox-option-padding-block=2px] - Block-axis padding of each option row.
|
|
87
|
+
* @cssprop [--rc-listbox-option-padding-inline=4px] - Inline-axis padding of each option row.
|
|
88
|
+
* @cssprop [--rc-listbox-option-transition] - CSS transition applied to each option row.
|
|
89
|
+
* @cssprop [--rc-listbox-hover-bg] - Background of a hovered option.
|
|
90
|
+
* @cssprop [--rc-listbox-hover-color] - Text color of a hovered option.
|
|
91
|
+
* @cssprop [--rc-listbox-active-bg] - Background of the keyboard-active option.
|
|
92
|
+
* @cssprop [--rc-listbox-active-color] - Text color of the keyboard-active option.
|
|
93
|
+
* @cssprop [--rc-listbox-selected-bg] - Background of a selected option.
|
|
94
|
+
* @cssprop [--rc-listbox-selected-color] - Text color of a selected option.
|
|
95
|
+
* @cssprop [--rc-listbox-disabled-color] - Text color of a disabled option.
|
|
96
|
+
* @cssprop [--rc-listbox-disabled-opacity] - Opacity of a disabled option.
|
|
55
97
|
*/
|
|
56
98
|
export declare class RCListbox extends LitElement {
|
|
57
|
-
|
|
58
|
-
|
|
99
|
+
static styles: import('lit').CSSResult;
|
|
100
|
+
private static readonly _styledRoots;
|
|
101
|
+
private static _ensureBaseStyles;
|
|
59
102
|
/** Allow multiple selection. Reflected as `aria-multiselectable` on the host. */
|
|
60
103
|
multiple: boolean;
|
|
61
104
|
/**
|
|
62
105
|
* Render a checkmark indicator inside each option element.
|
|
106
|
+
*
|
|
63
107
|
* Hidden by default; enable for combobox / select patterns where the
|
|
64
108
|
* consumer's CSS shows it conditionally via `[aria-selected='true']`.
|
|
65
109
|
*/
|
|
66
110
|
checkmark: boolean;
|
|
67
111
|
/**
|
|
68
112
|
* How option labels are matched against the active filter text.
|
|
113
|
+
*
|
|
69
114
|
* Defaults to `'contains'` (substring). Set to `'prefix'` for starts-with
|
|
70
115
|
* matching, or pass a custom predicate for full control.
|
|
71
116
|
* Function values are JS-only; string values may be set via the
|
|
72
117
|
* `filter-strategy` attribute.
|
|
73
118
|
*/
|
|
74
119
|
filterStrategy: FilterStrategy;
|
|
75
|
-
private _options;
|
|
76
|
-
private _selectedValues;
|
|
77
|
-
private _filterText;
|
|
78
|
-
private _createLabel;
|
|
79
120
|
private _defaultValue;
|
|
80
121
|
private _value;
|
|
81
122
|
private _selectionInitialized;
|
|
82
|
-
private
|
|
83
|
-
|
|
123
|
+
private _filterText;
|
|
124
|
+
/** Unique ID prefix for all rendered option elements in this instance. */
|
|
125
|
+
protected readonly _uid: string;
|
|
126
|
+
/** Manages the `<ul>/<li>` option DOM subtree in the component's light DOM. */
|
|
127
|
+
protected readonly _itemsCollectionCtrl: ItemsCollectionController;
|
|
128
|
+
/** Active-descendant controller; tracks keyboard focus within the option list. */
|
|
129
|
+
protected readonly _activeDescendantCtrl: ActiveDescendantController;
|
|
84
130
|
connectedCallback(): void;
|
|
85
131
|
disconnectedCallback(): void;
|
|
86
|
-
updated(): void;
|
|
132
|
+
updated(changed: Map<PropertyKey, unknown>): void;
|
|
133
|
+
protected render(): import('lit').TemplateResult<1>;
|
|
87
134
|
/** All options regardless of filter state. */
|
|
88
135
|
get allOptions(): readonly ListboxOption[];
|
|
89
136
|
/** Options currently passing the active filter. */
|
|
90
137
|
get filteredOptions(): readonly ListboxOption[];
|
|
91
|
-
/** Replace the full options list.
|
|
138
|
+
/** Replace the full options list. */
|
|
92
139
|
get options(): ListboxOption[];
|
|
93
|
-
/** Replace the full options list.
|
|
94
|
-
set options(
|
|
140
|
+
/** Replace the full options list. */
|
|
141
|
+
set options(options: ListboxOption[]);
|
|
95
142
|
/** Append a single option without replacing the list. */
|
|
96
|
-
appendOption(
|
|
143
|
+
appendOption(option: ListboxOption): void;
|
|
144
|
+
/** Current selection as a consistently-array-shaped read-only view. */
|
|
97
145
|
get selectedValues(): string[];
|
|
98
146
|
/** Current selection. Host writes update silently. */
|
|
99
147
|
get value(): string | string[];
|
|
@@ -107,40 +155,72 @@ export declare class RCListbox extends LitElement {
|
|
|
107
155
|
setSelectedValues(values: string[]): void;
|
|
108
156
|
/**
|
|
109
157
|
* Toggle the selected state of the option with `value`.
|
|
110
|
-
* In single-select mode,
|
|
158
|
+
* In single-select mode, toggling a selected item deselects it (and selects
|
|
111
159
|
* the new item). Fires `rc-listbox-change`.
|
|
112
160
|
*/
|
|
113
161
|
toggleOption(value: string): void;
|
|
162
|
+
/** Clears all selected values without firing `rc-listbox-change`. */
|
|
114
163
|
clearSelection(): void;
|
|
115
|
-
/** Filter visible options to those whose label
|
|
164
|
+
/** Filter visible options to those whose label matches `text` (case-insensitive). */
|
|
116
165
|
filterOptions(text: string): void;
|
|
166
|
+
/** Removes any active filter, making all options visible. */
|
|
117
167
|
clearFilter(): void;
|
|
118
168
|
/**
|
|
119
|
-
* Ordered list of option elements
|
|
169
|
+
* Ordered list of currently navigable option elements (visible and not disabled).
|
|
170
|
+
*
|
|
120
171
|
* Feed this to `ActiveDescendantController.items` in the parent component.
|
|
121
172
|
* Includes the create option element when one is set.
|
|
122
173
|
*/
|
|
123
174
|
get navigableItems(): Element[];
|
|
124
175
|
/**
|
|
125
176
|
* Show or hide the "Create" option at the end of the list.
|
|
126
|
-
*
|
|
127
|
-
*
|
|
177
|
+
*
|
|
178
|
+
* Pass `null` to hide it, or a non-empty string to show `Create "{label}"`.
|
|
179
|
+
* Fires `rc-listbox-change` with `reason: 'action'` and `action: 'create'`
|
|
180
|
+
* when activated.
|
|
128
181
|
*/
|
|
129
182
|
setCreateOption(label: string | null): void;
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
private _onKeydown;
|
|
183
|
+
/** Clears the active descendant when focus leaves the listbox. */
|
|
184
|
+
protected _onBlur: () => void;
|
|
133
185
|
/**
|
|
134
|
-
*
|
|
186
|
+
* Handles arrow-key navigation and Enter/Space activation.
|
|
187
|
+
*
|
|
188
|
+
* Passes modifier-key combos through to parent handlers unchanged.
|
|
189
|
+
*/
|
|
190
|
+
protected _onKeydown: (e: KeyboardEvent) => void;
|
|
191
|
+
/** Routes pointer activations from the controller. */
|
|
192
|
+
private _handleActivate;
|
|
193
|
+
/**
|
|
194
|
+
* Selects the active-descendant item without toggling.
|
|
195
|
+
*
|
|
135
196
|
* Used by arrow-key navigation so the item under the cursor is always selected.
|
|
136
197
|
*/
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
198
|
+
protected _selectActiveItem(): void;
|
|
199
|
+
/**
|
|
200
|
+
* Returns `true` when `opt` passes the current `_filterText` and `filterStrategy`.
|
|
201
|
+
*/
|
|
202
|
+
protected _isVisible(option: ListboxOption): boolean;
|
|
203
|
+
/**
|
|
204
|
+
* Directly replaces the selection without firing an event.
|
|
205
|
+
*
|
|
206
|
+
* Enforces the single-select limit when `multiple` is false.
|
|
207
|
+
*/
|
|
208
|
+
protected _applySelection(values: string[]): void;
|
|
209
|
+
/**
|
|
210
|
+
* Re-applies `_value` or `_defaultValue` after `options` or DOM bootstrap changes.
|
|
211
|
+
*/
|
|
212
|
+
protected _applySelectionFromSource(): void;
|
|
213
|
+
/** Coerces a `string | string[]` value to `string[]`. */
|
|
214
|
+
protected _normalizeValue(value: string | string[]): string[];
|
|
215
|
+
protected _activeOption(): ListboxOption | null;
|
|
216
|
+
/** Fires `rc-listbox-change` for a command/action row without mutating selection. */
|
|
217
|
+
protected _dispatchAction(option: ListboxActionOption): void;
|
|
218
|
+
/** Fires `rc-listbox-change` with a fully-populated detail object. */
|
|
219
|
+
protected _dispatchChange(option: ListboxSelectableOption, selected: boolean): void;
|
|
220
|
+
/**
|
|
221
|
+
* Maps value strings to their `ListboxOption` objects.
|
|
222
|
+
* Creates synthetic `{ value, label: value }` entries for values not in the list.
|
|
223
|
+
*/
|
|
224
|
+
protected _selectedOptionsFor(values: string[]): ListboxSelectableOption[];
|
|
145
225
|
}
|
|
146
226
|
export default RCListbox;
|
package/package.json
CHANGED
|
@@ -3,14 +3,14 @@
|
|
|
3
3
|
"publishConfig": {
|
|
4
4
|
"access": "public"
|
|
5
5
|
},
|
|
6
|
-
"version": "0.
|
|
7
|
-
"description": "
|
|
6
|
+
"version": "0.3.0",
|
|
7
|
+
"description": "Listbox that keeps option DOM in light DOM for aria-activedescendant navigation.",
|
|
8
8
|
"repository": {
|
|
9
9
|
"type": "git",
|
|
10
10
|
"url": "git+https://github.com/richardcarls/rc-webcomponents.git",
|
|
11
11
|
"directory": "packages/rc-listbox"
|
|
12
12
|
},
|
|
13
|
-
"homepage": "https://github.
|
|
13
|
+
"homepage": "https://richardcarls.github.io/rc-webcomponents/components/rc-listbox",
|
|
14
14
|
"license": "MIT",
|
|
15
15
|
"type": "module",
|
|
16
16
|
"files": [
|
|
@@ -37,21 +37,18 @@
|
|
|
37
37
|
],
|
|
38
38
|
"customElements": "dist/custom-elements.json",
|
|
39
39
|
"scripts": {
|
|
40
|
-
"dev": "vite",
|
|
41
40
|
"build": "tsc && vite build && cem analyze",
|
|
42
41
|
"cem:analyze": "cem analyze",
|
|
43
42
|
"preview": "vite preview",
|
|
44
|
-
"test:browser": "vitest",
|
|
45
|
-
"test:browser:chrome": "vitest --project=chromium",
|
|
46
|
-
"test:browser:firefox": "vitest --project=firefox"
|
|
47
|
-
"yalc:publish": "yalc publish --push"
|
|
43
|
+
"test:browser": "vitest --run",
|
|
44
|
+
"test:browser:chrome": "vitest --run --project=chromium",
|
|
45
|
+
"test:browser:firefox": "vitest --run --project=firefox"
|
|
48
46
|
},
|
|
49
47
|
"dependencies": {
|
|
50
|
-
"@rcarls/rc-common": "workspace
|
|
48
|
+
"@rcarls/rc-common": "workspace:*"
|
|
51
49
|
},
|
|
52
50
|
"devDependencies": {
|
|
53
51
|
"@custom-elements-manifest/analyzer": "0.11.0",
|
|
54
|
-
"@guanghechen/rollup-plugin-copy": "^6.0.9",
|
|
55
52
|
"@vitest/browser-playwright": "4.1.5",
|
|
56
53
|
"lit": "^3.0.0",
|
|
57
54
|
"playwright": "^1.56.0",
|
package/dist/demo.css
DELETED
|
@@ -1,85 +0,0 @@
|
|
|
1
|
-
*, *::before, *::after {
|
|
2
|
-
box-sizing: border-box;
|
|
3
|
-
}
|
|
4
|
-
|
|
5
|
-
:root {
|
|
6
|
-
color-scheme: light dark;
|
|
7
|
-
--rc-accent: Highlight; /* Chrome doesn't support AccentColor on the open web */
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
@supports (color: AccentColor) {
|
|
11
|
-
:root {
|
|
12
|
-
--rc-accent: AccentColor;
|
|
13
|
-
}
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
[data-theme="light"] { color-scheme: light; }
|
|
17
|
-
[data-theme="dark"] { color-scheme: dark; }
|
|
18
|
-
|
|
19
|
-
[data-theme="solarized-light"] {
|
|
20
|
-
color-scheme: light;
|
|
21
|
-
--rc-surface: #fdf6e3;
|
|
22
|
-
--rc-text: #657b83;
|
|
23
|
-
--rc-border: 1px solid #93a1a1;
|
|
24
|
-
--rc-shadow: 0 2px 8px rgba(0, 0, 0, .12);
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
[data-theme="solarized-dark"] {
|
|
28
|
-
color-scheme: dark;
|
|
29
|
-
--rc-surface: #002b36;
|
|
30
|
-
--rc-text: #839496;
|
|
31
|
-
--rc-border: 1px solid #586e75;
|
|
32
|
-
--rc-shadow: 0 2px 8px rgba(0, 0, 0, .3);
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
body {
|
|
36
|
-
font-family: system-ui, sans-serif;
|
|
37
|
-
margin: 0;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
.demo-page {
|
|
41
|
-
max-width: 60rem;
|
|
42
|
-
margin: 0 auto;
|
|
43
|
-
padding: 2rem 1.5rem;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
.demo-controls {
|
|
47
|
-
display: flex;
|
|
48
|
-
align-items: center;
|
|
49
|
-
gap: 1rem;
|
|
50
|
-
margin-bottom: 2rem;
|
|
51
|
-
padding-bottom: 1rem;
|
|
52
|
-
border-bottom: 1px solid ButtonBorder;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
.demo-controls .demo-title {
|
|
56
|
-
flex: 1;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
.demo-controls h1 {
|
|
60
|
-
margin: 0 0 0.15rem;
|
|
61
|
-
font-size: 1.5rem;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
.demo-controls a {
|
|
65
|
-
font-size: 0.8rem;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
.demo-section {
|
|
69
|
-
margin-bottom: 2rem;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
.demo-section h2 {
|
|
73
|
-
margin: 0 0 0.5rem;
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
.theme-picker {
|
|
77
|
-
padding: 0.35em 0.75em;
|
|
78
|
-
font-family: inherit;
|
|
79
|
-
font-size: 0.8rem;
|
|
80
|
-
cursor: pointer;
|
|
81
|
-
border: 1px solid ButtonBorder;
|
|
82
|
-
border-radius: 4px;
|
|
83
|
-
background: Canvas;
|
|
84
|
-
color: CanvasText;
|
|
85
|
-
}
|
package/dist/demo.js
DELETED
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
const THEMES = ['', 'light', 'dark', 'solarized-light', 'solarized-dark'];
|
|
2
|
-
const LABELS = ['Auto', 'Light', 'Dark', 'Solarized ☀', 'Solarized ☾'];
|
|
3
|
-
|
|
4
|
-
const stored = localStorage.getItem('rc-demo-theme') ?? '';
|
|
5
|
-
applyTheme(stored);
|
|
6
|
-
|
|
7
|
-
function applyTheme(theme) {
|
|
8
|
-
if (theme) {
|
|
9
|
-
document.documentElement.dataset.theme = theme;
|
|
10
|
-
} else {
|
|
11
|
-
delete document.documentElement.dataset.theme;
|
|
12
|
-
}
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
function currentIndex() {
|
|
16
|
-
const current = document.documentElement.dataset.theme ?? '';
|
|
17
|
-
const idx = THEMES.indexOf(current);
|
|
18
|
-
return idx === -1 ? 0 : idx;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
function updateButtons() {
|
|
22
|
-
const label = LABELS[currentIndex()];
|
|
23
|
-
document.querySelectorAll('.theme-picker').forEach((btn) => {
|
|
24
|
-
btn.textContent = label;
|
|
25
|
-
});
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
window.cycleTheme = function () {
|
|
29
|
-
const next = THEMES[(currentIndex() + 1) % THEMES.length];
|
|
30
|
-
applyTheme(next);
|
|
31
|
-
localStorage.setItem('rc-demo-theme', next);
|
|
32
|
-
updateButtons();
|
|
33
|
-
};
|
|
34
|
-
|
|
35
|
-
document.addEventListener('DOMContentLoaded', () => {
|
|
36
|
-
updateButtons();
|
|
37
|
-
document.querySelectorAll('.theme-picker').forEach((btn) => {
|
|
38
|
-
btn.addEventListener('click', window.cycleTheme);
|
|
39
|
-
});
|
|
40
|
-
});
|