@tokis/core 1.0.1 → 1.1.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 +5 -5
- package/dist/__tests__/accordion.test.d.ts +2 -0
- package/dist/__tests__/accordion.test.d.ts.map +1 -0
- package/dist/__tests__/accordion.test.js +127 -0
- package/dist/__tests__/accordion.test.js.map +1 -0
- package/dist/__tests__/create-machine.test.d.ts +2 -0
- package/dist/__tests__/create-machine.test.d.ts.map +1 -0
- package/dist/__tests__/create-machine.test.js +79 -0
- package/dist/__tests__/create-machine.test.js.map +1 -0
- package/dist/__tests__/dialog.test.d.ts +2 -0
- package/dist/__tests__/dialog.test.d.ts.map +1 -0
- package/dist/__tests__/dialog.test.js +74 -0
- package/dist/__tests__/dialog.test.js.map +1 -0
- package/dist/__tests__/menu.test.d.ts +2 -0
- package/dist/__tests__/menu.test.d.ts.map +1 -0
- package/dist/__tests__/menu.test.js +177 -0
- package/dist/__tests__/menu.test.js.map +1 -0
- package/dist/__tests__/tabs.test.d.ts +2 -0
- package/dist/__tests__/tabs.test.d.ts.map +1 -0
- package/dist/__tests__/tabs.test.js +147 -0
- package/dist/__tests__/tabs.test.js.map +1 -0
- package/dist/cjs/__tests__/accordion.test.js +128 -0
- package/dist/cjs/__tests__/create-machine.test.js +80 -0
- package/dist/cjs/__tests__/dialog.test.js +75 -0
- package/dist/cjs/__tests__/menu.test.js +178 -0
- package/dist/cjs/__tests__/tabs.test.js +148 -0
- package/dist/cjs/index.js +9 -0
- package/dist/cjs/primitives/accordion/accordion.machine.js +109 -0
- package/dist/cjs/primitives/accordion/accordion.types.js +6 -0
- package/dist/cjs/primitives/accordion/index.js +18 -0
- package/dist/cjs/primitives/dialog/dialog.machine.js +62 -0
- package/dist/cjs/primitives/dialog/dialog.types.js +6 -0
- package/dist/cjs/primitives/dialog/index.js +18 -0
- package/dist/cjs/primitives/menu/index.js +18 -0
- package/dist/cjs/primitives/menu/menu.machine.js +135 -0
- package/dist/cjs/primitives/menu/menu.types.js +6 -0
- package/dist/cjs/primitives/popover/index.js +18 -0
- package/dist/cjs/primitives/popover/popover.machine.js +87 -0
- package/dist/cjs/primitives/popover/popover.types.js +7 -0
- package/dist/cjs/primitives/tabs/index.js +18 -0
- package/dist/cjs/primitives/tabs/tabs.machine.js +124 -0
- package/dist/cjs/primitives/tabs/tabs.types.js +6 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +9 -0
- package/dist/index.js.map +1 -1
- package/dist/primitives/accordion/accordion.machine.d.ts +22 -0
- package/dist/primitives/accordion/accordion.machine.d.ts.map +1 -0
- package/dist/primitives/accordion/accordion.machine.js +103 -0
- package/dist/primitives/accordion/accordion.machine.js.map +1 -0
- package/dist/primitives/accordion/accordion.types.d.ts +28 -0
- package/dist/primitives/accordion/accordion.types.d.ts.map +1 -0
- package/dist/primitives/accordion/accordion.types.js +6 -0
- package/dist/primitives/accordion/accordion.types.js.map +1 -0
- package/dist/primitives/accordion/index.d.ts +3 -0
- package/dist/primitives/accordion/index.d.ts.map +1 -0
- package/dist/primitives/accordion/index.js +3 -0
- package/dist/primitives/accordion/index.js.map +1 -0
- package/dist/primitives/dialog/dialog.machine.d.ts +23 -0
- package/dist/primitives/dialog/dialog.machine.d.ts.map +1 -0
- package/dist/primitives/dialog/dialog.machine.js +58 -0
- package/dist/primitives/dialog/dialog.machine.js.map +1 -0
- package/dist/primitives/dialog/dialog.types.d.ts +25 -0
- package/dist/primitives/dialog/dialog.types.d.ts.map +1 -0
- package/dist/primitives/dialog/dialog.types.js +6 -0
- package/dist/primitives/dialog/dialog.types.js.map +1 -0
- package/dist/primitives/dialog/index.d.ts +3 -0
- package/dist/primitives/dialog/index.d.ts.map +1 -0
- package/dist/primitives/dialog/index.js +3 -0
- package/dist/primitives/dialog/index.js.map +1 -0
- package/dist/primitives/menu/index.d.ts +3 -0
- package/dist/primitives/menu/index.d.ts.map +1 -0
- package/dist/primitives/menu/index.js +3 -0
- package/dist/primitives/menu/index.js.map +1 -0
- package/dist/primitives/menu/menu.machine.d.ts +25 -0
- package/dist/primitives/menu/menu.machine.d.ts.map +1 -0
- package/dist/primitives/menu/menu.machine.js +129 -0
- package/dist/primitives/menu/menu.machine.js.map +1 -0
- package/dist/primitives/menu/menu.types.d.ts +30 -0
- package/dist/primitives/menu/menu.types.d.ts.map +1 -0
- package/dist/primitives/menu/menu.types.js +6 -0
- package/dist/primitives/menu/menu.types.js.map +1 -0
- package/dist/primitives/popover/index.d.ts +3 -0
- package/dist/primitives/popover/index.d.ts.map +1 -0
- package/dist/primitives/popover/index.js +3 -0
- package/dist/primitives/popover/index.js.map +1 -0
- package/dist/primitives/popover/popover.machine.d.ts +30 -0
- package/dist/primitives/popover/popover.machine.d.ts.map +1 -0
- package/dist/primitives/popover/popover.machine.js +80 -0
- package/dist/primitives/popover/popover.machine.js.map +1 -0
- package/dist/primitives/popover/popover.types.d.ts +34 -0
- package/dist/primitives/popover/popover.types.d.ts.map +1 -0
- package/dist/primitives/popover/popover.types.js +7 -0
- package/dist/primitives/popover/popover.types.js.map +1 -0
- package/dist/primitives/tabs/index.d.ts +3 -0
- package/dist/primitives/tabs/index.d.ts.map +1 -0
- package/dist/primitives/tabs/index.js +3 -0
- package/dist/primitives/tabs/index.js.map +1 -0
- package/dist/primitives/tabs/tabs.machine.d.ts +25 -0
- package/dist/primitives/tabs/tabs.machine.d.ts.map +1 -0
- package/dist/primitives/tabs/tabs.machine.js +117 -0
- package/dist/primitives/tabs/tabs.machine.js.map +1 -0
- package/dist/primitives/tabs/tabs.types.d.ts +34 -0
- package/dist/primitives/tabs/tabs.types.d.ts.map +1 -0
- package/dist/primitives/tabs/tabs.types.js +6 -0
- package/dist/primitives/tabs/tabs.types.js.map +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const vitest_1 = require("vitest");
|
|
4
|
+
const tabs_machine_js_1 = require("../primitives/tabs/tabs.machine");
|
|
5
|
+
// ─── Fixtures ──────────────────────────────────────────────────────────────
|
|
6
|
+
function makeCtx(overrides = {}) {
|
|
7
|
+
return {
|
|
8
|
+
tabIds: ['tab-1', 'tab-2', 'tab-3'],
|
|
9
|
+
activeTabId: 'tab-1',
|
|
10
|
+
focusedTabId: 'tab-1',
|
|
11
|
+
activationMode: 'automatic',
|
|
12
|
+
orientation: 'horizontal',
|
|
13
|
+
loop: true,
|
|
14
|
+
...overrides,
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
// ─── Machine tests ─────────────────────────────────────────────────────────
|
|
18
|
+
(0, vitest_1.describe)('tabsMachine', () => {
|
|
19
|
+
(0, vitest_1.it)('starts in idle state', () => {
|
|
20
|
+
(0, vitest_1.expect)(tabs_machine_js_1.tabsMachine.initialState).toBe('idle');
|
|
21
|
+
});
|
|
22
|
+
(0, vitest_1.it)('stays in idle on any event (all context work is done by helpers)', () => {
|
|
23
|
+
(0, vitest_1.expect)(tabs_machine_js_1.tabsMachine.transition('idle', 'SELECT_TAB')).toBe('idle');
|
|
24
|
+
(0, vitest_1.expect)(tabs_machine_js_1.tabsMachine.transition('idle', 'NEXT_TAB')).toBe('idle');
|
|
25
|
+
(0, vitest_1.expect)(tabs_machine_js_1.tabsMachine.transition('idle', 'PREV_TAB')).toBe('idle');
|
|
26
|
+
});
|
|
27
|
+
});
|
|
28
|
+
// ─── Navigation helpers ────────────────────────────────────────────────────
|
|
29
|
+
(0, vitest_1.describe)('getNextTabId', () => {
|
|
30
|
+
(0, vitest_1.it)('moves from first → second tab', () => {
|
|
31
|
+
const ctx = makeCtx({ focusedTabId: 'tab-1' });
|
|
32
|
+
(0, vitest_1.expect)((0, tabs_machine_js_1.getNextTabId)(ctx)).toBe('tab-2');
|
|
33
|
+
});
|
|
34
|
+
(0, vitest_1.it)('moves from second → third tab', () => {
|
|
35
|
+
const ctx = makeCtx({ focusedTabId: 'tab-2' });
|
|
36
|
+
(0, vitest_1.expect)((0, tabs_machine_js_1.getNextTabId)(ctx)).toBe('tab-3');
|
|
37
|
+
});
|
|
38
|
+
(0, vitest_1.it)('wraps from last → first when loop=true', () => {
|
|
39
|
+
const ctx = makeCtx({ focusedTabId: 'tab-3', loop: true });
|
|
40
|
+
(0, vitest_1.expect)((0, tabs_machine_js_1.getNextTabId)(ctx)).toBe('tab-1');
|
|
41
|
+
});
|
|
42
|
+
(0, vitest_1.it)('stays at last tab when loop=false', () => {
|
|
43
|
+
const ctx = makeCtx({ focusedTabId: 'tab-3', loop: false });
|
|
44
|
+
(0, vitest_1.expect)((0, tabs_machine_js_1.getNextTabId)(ctx)).toBe('tab-3');
|
|
45
|
+
});
|
|
46
|
+
(0, vitest_1.it)('returns first tab when current focus is not in tabIds', () => {
|
|
47
|
+
const ctx = makeCtx({ focusedTabId: 'unknown' });
|
|
48
|
+
(0, vitest_1.expect)((0, tabs_machine_js_1.getNextTabId)(ctx)).toBe('tab-1');
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
(0, vitest_1.describe)('getPrevTabId', () => {
|
|
52
|
+
(0, vitest_1.it)('moves from third → second tab', () => {
|
|
53
|
+
const ctx = makeCtx({ focusedTabId: 'tab-3' });
|
|
54
|
+
(0, vitest_1.expect)((0, tabs_machine_js_1.getPrevTabId)(ctx)).toBe('tab-2');
|
|
55
|
+
});
|
|
56
|
+
(0, vitest_1.it)('wraps from first → last when loop=true', () => {
|
|
57
|
+
const ctx = makeCtx({ focusedTabId: 'tab-1', loop: true });
|
|
58
|
+
(0, vitest_1.expect)((0, tabs_machine_js_1.getPrevTabId)(ctx)).toBe('tab-3');
|
|
59
|
+
});
|
|
60
|
+
(0, vitest_1.it)('stays at first tab when loop=false', () => {
|
|
61
|
+
const ctx = makeCtx({ focusedTabId: 'tab-1', loop: false });
|
|
62
|
+
(0, vitest_1.expect)((0, tabs_machine_js_1.getPrevTabId)(ctx)).toBe('tab-1');
|
|
63
|
+
});
|
|
64
|
+
});
|
|
65
|
+
// ─── reduceTabsContext ─────────────────────────────────────────────────────
|
|
66
|
+
(0, vitest_1.describe)('reduceTabsContext', () => {
|
|
67
|
+
(0, vitest_1.it)('SELECT_TAB with payload sets activeTabId and focusedTabId', () => {
|
|
68
|
+
const ctx = makeCtx();
|
|
69
|
+
const patch = (0, tabs_machine_js_1.reduceTabsContext)(ctx, 'SELECT_TAB', { tabId: 'tab-2' });
|
|
70
|
+
(0, vitest_1.expect)(patch).toEqual({ activeTabId: 'tab-2', focusedTabId: 'tab-2' });
|
|
71
|
+
});
|
|
72
|
+
(0, vitest_1.it)('SELECT_TAB without payload uses current focusedTabId', () => {
|
|
73
|
+
const ctx = makeCtx({ focusedTabId: 'tab-3' });
|
|
74
|
+
const patch = (0, tabs_machine_js_1.reduceTabsContext)(ctx, 'SELECT_TAB');
|
|
75
|
+
(0, vitest_1.expect)(patch).toEqual({ activeTabId: 'tab-3', focusedTabId: 'tab-3' });
|
|
76
|
+
});
|
|
77
|
+
(0, vitest_1.it)('FOCUS_TAB in automatic mode also updates activeTabId', () => {
|
|
78
|
+
const ctx = makeCtx({ activationMode: 'automatic' });
|
|
79
|
+
const patch = (0, tabs_machine_js_1.reduceTabsContext)(ctx, 'FOCUS_TAB', { tabId: 'tab-2' });
|
|
80
|
+
(0, vitest_1.expect)(patch.focusedTabId).toBe('tab-2');
|
|
81
|
+
(0, vitest_1.expect)(patch.activeTabId).toBe('tab-2');
|
|
82
|
+
});
|
|
83
|
+
(0, vitest_1.it)('FOCUS_TAB in manual mode only updates focusedTabId', () => {
|
|
84
|
+
const ctx = makeCtx({ activationMode: 'manual' });
|
|
85
|
+
const patch = (0, tabs_machine_js_1.reduceTabsContext)(ctx, 'FOCUS_TAB', { tabId: 'tab-2' });
|
|
86
|
+
(0, vitest_1.expect)(patch.focusedTabId).toBe('tab-2');
|
|
87
|
+
(0, vitest_1.expect)(patch.activeTabId).toBeUndefined();
|
|
88
|
+
});
|
|
89
|
+
(0, vitest_1.it)('NEXT_TAB advances focus in automatic mode', () => {
|
|
90
|
+
const ctx = makeCtx({ focusedTabId: 'tab-1', activationMode: 'automatic' });
|
|
91
|
+
const patch = (0, tabs_machine_js_1.reduceTabsContext)(ctx, 'NEXT_TAB');
|
|
92
|
+
(0, vitest_1.expect)(patch.focusedTabId).toBe('tab-2');
|
|
93
|
+
(0, vitest_1.expect)(patch.activeTabId).toBe('tab-2');
|
|
94
|
+
});
|
|
95
|
+
(0, vitest_1.it)('PREV_TAB moves focus backward', () => {
|
|
96
|
+
const ctx = makeCtx({ focusedTabId: 'tab-2' });
|
|
97
|
+
const patch = (0, tabs_machine_js_1.reduceTabsContext)(ctx, 'PREV_TAB');
|
|
98
|
+
(0, vitest_1.expect)(patch.focusedTabId).toBe('tab-1');
|
|
99
|
+
});
|
|
100
|
+
(0, vitest_1.it)('FIRST_TAB jumps to first tab', () => {
|
|
101
|
+
const ctx = makeCtx({ focusedTabId: 'tab-3' });
|
|
102
|
+
const patch = (0, tabs_machine_js_1.reduceTabsContext)(ctx, 'FIRST_TAB');
|
|
103
|
+
(0, vitest_1.expect)(patch.focusedTabId).toBe('tab-1');
|
|
104
|
+
});
|
|
105
|
+
(0, vitest_1.it)('LAST_TAB jumps to last tab', () => {
|
|
106
|
+
const ctx = makeCtx({ focusedTabId: 'tab-1' });
|
|
107
|
+
const patch = (0, tabs_machine_js_1.reduceTabsContext)(ctx, 'LAST_TAB');
|
|
108
|
+
(0, vitest_1.expect)(patch.focusedTabId).toBe('tab-3');
|
|
109
|
+
});
|
|
110
|
+
(0, vitest_1.it)('unknown event returns empty patch (no-op)', () => {
|
|
111
|
+
const ctx = makeCtx();
|
|
112
|
+
// @ts-expect-error — intentional unknown event
|
|
113
|
+
const patch = (0, tabs_machine_js_1.reduceTabsContext)(ctx, 'UNKNOWN_EVENT');
|
|
114
|
+
(0, vitest_1.expect)(patch).toEqual({});
|
|
115
|
+
});
|
|
116
|
+
});
|
|
117
|
+
// ─── ARIA helpers ──────────────────────────────────────────────────────────
|
|
118
|
+
(0, vitest_1.describe)('getTabAriaProps', () => {
|
|
119
|
+
(0, vitest_1.it)('returns correct ARIA attributes for the active tab', () => {
|
|
120
|
+
const ctx = makeCtx({ activeTabId: 'tab-1' });
|
|
121
|
+
const props = (0, tabs_machine_js_1.getTabAriaProps)(ctx, 'tab-1', 'panel-1');
|
|
122
|
+
(0, vitest_1.expect)(props.role).toBe('tab');
|
|
123
|
+
(0, vitest_1.expect)(props['aria-selected']).toBe(true);
|
|
124
|
+
(0, vitest_1.expect)(props['aria-controls']).toBe('panel-1');
|
|
125
|
+
(0, vitest_1.expect)(props.tabIndex).toBe(0);
|
|
126
|
+
});
|
|
127
|
+
(0, vitest_1.it)('marks non-active tabs as not selected with tabIndex -1', () => {
|
|
128
|
+
const ctx = makeCtx({ activeTabId: 'tab-1' });
|
|
129
|
+
const props = (0, tabs_machine_js_1.getTabAriaProps)(ctx, 'tab-2', 'panel-2');
|
|
130
|
+
(0, vitest_1.expect)(props['aria-selected']).toBe(false);
|
|
131
|
+
(0, vitest_1.expect)(props.tabIndex).toBe(-1);
|
|
132
|
+
});
|
|
133
|
+
});
|
|
134
|
+
(0, vitest_1.describe)('getTabPanelAriaProps', () => {
|
|
135
|
+
(0, vitest_1.it)('returns correct ARIA attributes for the visible panel', () => {
|
|
136
|
+
const ctx = makeCtx({ activeTabId: 'tab-1' });
|
|
137
|
+
const props = (0, tabs_machine_js_1.getTabPanelAriaProps)(ctx, 'panel-1', 'tab-1');
|
|
138
|
+
(0, vitest_1.expect)(props.role).toBe('tabpanel');
|
|
139
|
+
(0, vitest_1.expect)(props.hidden).toBe(false);
|
|
140
|
+
(0, vitest_1.expect)(props['aria-labelledby']).toBe('tab-1');
|
|
141
|
+
(0, vitest_1.expect)(props.tabIndex).toBe(0);
|
|
142
|
+
});
|
|
143
|
+
(0, vitest_1.it)('marks non-visible panels as hidden', () => {
|
|
144
|
+
const ctx = makeCtx({ activeTabId: 'tab-1' });
|
|
145
|
+
const props = (0, tabs_machine_js_1.getTabPanelAriaProps)(ctx, 'panel-2', 'tab-2');
|
|
146
|
+
(0, vitest_1.expect)(props.hidden).toBe(true);
|
|
147
|
+
});
|
|
148
|
+
});
|
package/dist/cjs/index.js
CHANGED
|
@@ -14,11 +14,20 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
|
14
14
|
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
15
|
};
|
|
16
16
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
// Primitives — headless state machines per component
|
|
17
18
|
__exportStar(require("./primitives/button/index"), exports);
|
|
19
|
+
__exportStar(require("./primitives/dialog/index"), exports);
|
|
20
|
+
__exportStar(require("./primitives/tabs/index"), exports);
|
|
21
|
+
__exportStar(require("./primitives/accordion/index"), exports);
|
|
22
|
+
__exportStar(require("./primitives/menu/index"), exports);
|
|
23
|
+
__exportStar(require("./primitives/popover/index"), exports);
|
|
24
|
+
// Focus management
|
|
18
25
|
__exportStar(require("./focus/focus-trap"), exports);
|
|
19
26
|
__exportStar(require("./focus/roving-tabindex"), exports);
|
|
20
27
|
__exportStar(require("./focus/use-focus-visible"), exports);
|
|
28
|
+
// Accessibility helpers
|
|
21
29
|
__exportStar(require("./a11y/aria-helpers"), exports);
|
|
22
30
|
__exportStar(require("./a11y/id-generator"), exports);
|
|
31
|
+
// State utilities
|
|
23
32
|
__exportStar(require("./state/controllable-state"), exports);
|
|
24
33
|
__exportStar(require("./state/create-machine"), exports);
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.accordionMachine = void 0;
|
|
4
|
+
exports.reduceAccordionContext = reduceAccordionContext;
|
|
5
|
+
exports.isAccordionItemOpen = isAccordionItemOpen;
|
|
6
|
+
exports.getAccordionTriggerAriaProps = getAccordionTriggerAriaProps;
|
|
7
|
+
exports.getAccordionPanelAriaProps = getAccordionPanelAriaProps;
|
|
8
|
+
const create_machine_js_1 = require("../../state/create-machine");
|
|
9
|
+
/**
|
|
10
|
+
* Accordion state machine.
|
|
11
|
+
*
|
|
12
|
+
* Like tabs, the macro-state is always 'idle'. Open/closed state per item
|
|
13
|
+
* lives in context.openItemIds. Use `reduceAccordionContext` in adapters.
|
|
14
|
+
*/
|
|
15
|
+
exports.accordionMachine = (0, create_machine_js_1.createMachine)({
|
|
16
|
+
id: 'accordion',
|
|
17
|
+
initial: 'idle',
|
|
18
|
+
context: {
|
|
19
|
+
itemIds: [],
|
|
20
|
+
openItemIds: new Set(),
|
|
21
|
+
multiple: false,
|
|
22
|
+
collapsible: true,
|
|
23
|
+
},
|
|
24
|
+
states: {
|
|
25
|
+
idle: {},
|
|
26
|
+
},
|
|
27
|
+
});
|
|
28
|
+
// ─── Pure context helpers ──────────────────────────────────────────────────
|
|
29
|
+
/**
|
|
30
|
+
* Reduces an accordion event into a new openItemIds set.
|
|
31
|
+
* Returns the full updated context for easy spreading.
|
|
32
|
+
*/
|
|
33
|
+
function reduceAccordionContext(ctx, event, payload) {
|
|
34
|
+
const open = new Set(ctx.openItemIds);
|
|
35
|
+
switch (event) {
|
|
36
|
+
case 'TOGGLE_ITEM': {
|
|
37
|
+
const id = payload?.itemId;
|
|
38
|
+
if (!id)
|
|
39
|
+
return {};
|
|
40
|
+
if (open.has(id)) {
|
|
41
|
+
// Prevent closing the last item when collapsible=false
|
|
42
|
+
if (!ctx.collapsible && open.size === 1)
|
|
43
|
+
return {};
|
|
44
|
+
open.delete(id);
|
|
45
|
+
}
|
|
46
|
+
else {
|
|
47
|
+
if (!ctx.multiple)
|
|
48
|
+
open.clear(); // exclusive mode
|
|
49
|
+
open.add(id);
|
|
50
|
+
}
|
|
51
|
+
return { openItemIds: open };
|
|
52
|
+
}
|
|
53
|
+
case 'OPEN_ITEM': {
|
|
54
|
+
const id = payload?.itemId;
|
|
55
|
+
if (!id)
|
|
56
|
+
return {};
|
|
57
|
+
if (!ctx.multiple) {
|
|
58
|
+
const next = new Set();
|
|
59
|
+
next.add(id);
|
|
60
|
+
return { openItemIds: next };
|
|
61
|
+
}
|
|
62
|
+
open.add(id);
|
|
63
|
+
return { openItemIds: open };
|
|
64
|
+
}
|
|
65
|
+
case 'CLOSE_ITEM': {
|
|
66
|
+
const id = payload?.itemId;
|
|
67
|
+
if (!id)
|
|
68
|
+
return {};
|
|
69
|
+
if (!ctx.collapsible && open.size === 1 && open.has(id))
|
|
70
|
+
return {};
|
|
71
|
+
open.delete(id);
|
|
72
|
+
return { openItemIds: open };
|
|
73
|
+
}
|
|
74
|
+
case 'OPEN_ALL': {
|
|
75
|
+
if (!ctx.multiple)
|
|
76
|
+
return {}; // no-op for exclusive mode
|
|
77
|
+
return { openItemIds: new Set(ctx.itemIds) };
|
|
78
|
+
}
|
|
79
|
+
case 'CLOSE_ALL': {
|
|
80
|
+
if (!ctx.collapsible)
|
|
81
|
+
return {}; // no-op: cannot close when not collapsible
|
|
82
|
+
return { openItemIds: new Set() };
|
|
83
|
+
}
|
|
84
|
+
default:
|
|
85
|
+
return {};
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
/** Returns whether a specific item is currently open. */
|
|
89
|
+
function isAccordionItemOpen(ctx, itemId) {
|
|
90
|
+
return ctx.openItemIds.has(itemId);
|
|
91
|
+
}
|
|
92
|
+
/** Returns ARIA attributes for an accordion trigger button. */
|
|
93
|
+
function getAccordionTriggerAriaProps(ctx, itemId, panelId) {
|
|
94
|
+
const expanded = isAccordionItemOpen(ctx, itemId);
|
|
95
|
+
return {
|
|
96
|
+
role: 'button',
|
|
97
|
+
'aria-expanded': expanded,
|
|
98
|
+
'aria-controls': panelId,
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
/** Returns ARIA attributes for an accordion panel. */
|
|
102
|
+
function getAccordionPanelAriaProps(ctx, panelId, triggerId) {
|
|
103
|
+
return {
|
|
104
|
+
role: 'region',
|
|
105
|
+
id: panelId,
|
|
106
|
+
'aria-labelledby': triggerId,
|
|
107
|
+
hidden: !isAccordionItemOpen(ctx, triggerId.replace('-trigger', '')),
|
|
108
|
+
};
|
|
109
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
__exportStar(require("./accordion.machine"), exports);
|
|
18
|
+
__exportStar(require("./accordion.types"), exports);
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.dialogMachine = void 0;
|
|
4
|
+
exports.isDialogMounted = isDialogMounted;
|
|
5
|
+
exports.isDialogOpen = isDialogOpen;
|
|
6
|
+
const create_machine_js_1 = require("../../state/create-machine");
|
|
7
|
+
/**
|
|
8
|
+
* Dialog state machine.
|
|
9
|
+
*
|
|
10
|
+
* Transition table:
|
|
11
|
+
*
|
|
12
|
+
* closed + OPEN → opening (animated) | open (no animation)
|
|
13
|
+
* opening + ANIMATION_END → open
|
|
14
|
+
* open + CLOSE → closing (animated) | closed (no animation)
|
|
15
|
+
* open + TOGGLE → closing (animated) | closed (no animation)
|
|
16
|
+
* closing + ANIMATION_END → closed
|
|
17
|
+
* closed + TOGGLE → opening (animated) | open (no animation)
|
|
18
|
+
*
|
|
19
|
+
* When `animated` is false the intermediate opening/closing states are
|
|
20
|
+
* skipped — callers simply never fire ANIMATION_END and can treat
|
|
21
|
+
* open/closed as the only two live states.
|
|
22
|
+
*/
|
|
23
|
+
exports.dialogMachine = (0, create_machine_js_1.createMachine)({
|
|
24
|
+
id: 'dialog',
|
|
25
|
+
initial: 'closed',
|
|
26
|
+
context: {
|
|
27
|
+
id: 'dialog',
|
|
28
|
+
modal: true,
|
|
29
|
+
closeOnOverlayClick: true,
|
|
30
|
+
closeOnEscape: true,
|
|
31
|
+
animated: true,
|
|
32
|
+
},
|
|
33
|
+
states: {
|
|
34
|
+
closed: {
|
|
35
|
+
OPEN: 'opening',
|
|
36
|
+
TOGGLE: 'opening',
|
|
37
|
+
},
|
|
38
|
+
opening: {
|
|
39
|
+
ANIMATION_END: 'open',
|
|
40
|
+
// Immediately cancel if closed before animation completes
|
|
41
|
+
CLOSE: 'closed',
|
|
42
|
+
},
|
|
43
|
+
open: {
|
|
44
|
+
CLOSE: 'closing',
|
|
45
|
+
TOGGLE: 'closing',
|
|
46
|
+
},
|
|
47
|
+
closing: {
|
|
48
|
+
ANIMATION_END: 'closed',
|
|
49
|
+
// Re-open before close animation completes
|
|
50
|
+
OPEN: 'opening',
|
|
51
|
+
TOGGLE: 'opening',
|
|
52
|
+
},
|
|
53
|
+
},
|
|
54
|
+
});
|
|
55
|
+
/** Derive if the dialog DOM node should be mounted (present in the DOM) */
|
|
56
|
+
function isDialogMounted(state) {
|
|
57
|
+
return state === 'open' || state === 'opening' || state === 'closing';
|
|
58
|
+
}
|
|
59
|
+
/** Derive aria-expanded / open prop */
|
|
60
|
+
function isDialogOpen(state) {
|
|
61
|
+
return state === 'open' || state === 'opening';
|
|
62
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
__exportStar(require("./dialog.machine"), exports);
|
|
18
|
+
__exportStar(require("./dialog.types"), exports);
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
__exportStar(require("./menu.machine"), exports);
|
|
18
|
+
__exportStar(require("./menu.types"), exports);
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.menuMachine = void 0;
|
|
4
|
+
exports.reduceMenuContext = reduceMenuContext;
|
|
5
|
+
exports.getMenuTriggerAriaProps = getMenuTriggerAriaProps;
|
|
6
|
+
exports.getMenuAriaProps = getMenuAriaProps;
|
|
7
|
+
exports.getMenuItemAriaProps = getMenuItemAriaProps;
|
|
8
|
+
const create_machine_js_1 = require("../../state/create-machine");
|
|
9
|
+
/**
|
|
10
|
+
* Menu state machine.
|
|
11
|
+
*
|
|
12
|
+
* Transition table:
|
|
13
|
+
* closed + OPEN → open
|
|
14
|
+
* closed + TOGGLE → open
|
|
15
|
+
* open + CLOSE → closed
|
|
16
|
+
* open + TOGGLE → closed
|
|
17
|
+
* open + SELECT_ITEM → closed
|
|
18
|
+
* (all other events keep current state, context mutated via helpers)
|
|
19
|
+
*/
|
|
20
|
+
exports.menuMachine = (0, create_machine_js_1.createMachine)({
|
|
21
|
+
id: 'menu',
|
|
22
|
+
initial: 'closed',
|
|
23
|
+
context: {
|
|
24
|
+
triggerId: 'menu-trigger',
|
|
25
|
+
items: [],
|
|
26
|
+
activeItemId: null,
|
|
27
|
+
searchBuffer: '',
|
|
28
|
+
loop: true,
|
|
29
|
+
},
|
|
30
|
+
states: {
|
|
31
|
+
closed: {
|
|
32
|
+
OPEN: 'open',
|
|
33
|
+
TOGGLE: 'open',
|
|
34
|
+
},
|
|
35
|
+
open: {
|
|
36
|
+
CLOSE: 'closed',
|
|
37
|
+
TOGGLE: 'closed',
|
|
38
|
+
SELECT_ITEM: 'closed',
|
|
39
|
+
},
|
|
40
|
+
},
|
|
41
|
+
});
|
|
42
|
+
// ─── Pure context helpers ──────────────────────────────────────────────────
|
|
43
|
+
function enabledItems(items) {
|
|
44
|
+
return items.filter((i) => !i.disabled);
|
|
45
|
+
}
|
|
46
|
+
function getActiveIndex(ctx) {
|
|
47
|
+
if (!ctx.activeItemId)
|
|
48
|
+
return -1;
|
|
49
|
+
return enabledItems(ctx.items).findIndex((i) => i.id === ctx.activeItemId);
|
|
50
|
+
}
|
|
51
|
+
/** Handles navigation and search events, returning updated context fields. */
|
|
52
|
+
function reduceMenuContext(ctx, event, payload) {
|
|
53
|
+
const enabled = enabledItems(ctx.items);
|
|
54
|
+
switch (event) {
|
|
55
|
+
case 'OPEN': {
|
|
56
|
+
// Focus first item on open
|
|
57
|
+
const first = enabled[0];
|
|
58
|
+
return { activeItemId: first?.id ?? null, searchBuffer: '' };
|
|
59
|
+
}
|
|
60
|
+
case 'CLOSE':
|
|
61
|
+
case 'TOGGLE': {
|
|
62
|
+
return { activeItemId: null, searchBuffer: '' };
|
|
63
|
+
}
|
|
64
|
+
case 'FOCUS_ITEM': {
|
|
65
|
+
return { activeItemId: payload?.itemId ?? null };
|
|
66
|
+
}
|
|
67
|
+
case 'SELECT_ITEM': {
|
|
68
|
+
return { activeItemId: null, searchBuffer: '' };
|
|
69
|
+
}
|
|
70
|
+
case 'NEXT_ITEM': {
|
|
71
|
+
const idx = getActiveIndex(ctx);
|
|
72
|
+
let next = idx + 1;
|
|
73
|
+
if (next >= enabled.length)
|
|
74
|
+
next = ctx.loop ? 0 : enabled.length - 1;
|
|
75
|
+
return { activeItemId: enabled[next]?.id ?? null };
|
|
76
|
+
}
|
|
77
|
+
case 'PREV_ITEM': {
|
|
78
|
+
const idx = getActiveIndex(ctx);
|
|
79
|
+
let prev = idx - 1;
|
|
80
|
+
if (prev < 0)
|
|
81
|
+
prev = ctx.loop ? enabled.length - 1 : 0;
|
|
82
|
+
return { activeItemId: enabled[prev]?.id ?? null };
|
|
83
|
+
}
|
|
84
|
+
case 'FIRST_ITEM': {
|
|
85
|
+
return { activeItemId: enabled[0]?.id ?? null };
|
|
86
|
+
}
|
|
87
|
+
case 'LAST_ITEM': {
|
|
88
|
+
return { activeItemId: enabled[enabled.length - 1]?.id ?? null };
|
|
89
|
+
}
|
|
90
|
+
case 'SEARCH': {
|
|
91
|
+
const char = (payload?.char ?? '').toLowerCase();
|
|
92
|
+
if (!char)
|
|
93
|
+
return {};
|
|
94
|
+
// Accumulate typeahead buffer (cleared by timeout in adapter)
|
|
95
|
+
const buffer = ctx.searchBuffer + char;
|
|
96
|
+
const currentIdx = getActiveIndex(ctx);
|
|
97
|
+
// Find next item starting with buffer, searching from after current
|
|
98
|
+
const startIdx = currentIdx + 1;
|
|
99
|
+
const rotated = [...enabled.slice(startIdx), ...enabled.slice(0, startIdx)];
|
|
100
|
+
const match = rotated.find((i) => i.label.toLowerCase().startsWith(buffer));
|
|
101
|
+
return {
|
|
102
|
+
searchBuffer: buffer,
|
|
103
|
+
activeItemId: match?.id ?? ctx.activeItemId,
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
default:
|
|
107
|
+
return {};
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
/** ARIA attributes for the menu trigger button. */
|
|
111
|
+
function getMenuTriggerAriaProps(state, ctx, menuId) {
|
|
112
|
+
return {
|
|
113
|
+
'aria-haspopup': 'menu',
|
|
114
|
+
'aria-expanded': state === 'open',
|
|
115
|
+
'aria-controls': menuId,
|
|
116
|
+
id: ctx.triggerId,
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
/** ARIA attributes for the menu container (ul element). */
|
|
120
|
+
function getMenuAriaProps(ctx, menuId) {
|
|
121
|
+
return {
|
|
122
|
+
role: 'menu',
|
|
123
|
+
id: menuId,
|
|
124
|
+
'aria-labelledby': ctx.triggerId,
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
/** ARIA attributes for a single menu item. */
|
|
128
|
+
function getMenuItemAriaProps(ctx, item) {
|
|
129
|
+
return {
|
|
130
|
+
role: 'menuitem',
|
|
131
|
+
id: item.id,
|
|
132
|
+
'aria-disabled': item.disabled ?? false,
|
|
133
|
+
tabIndex: ctx.activeItemId === item.id ? 0 : -1,
|
|
134
|
+
};
|
|
135
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
__exportStar(require("./popover.machine"), exports);
|
|
18
|
+
__exportStar(require("./popover.types"), exports);
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.popoverMachine = void 0;
|
|
4
|
+
exports.isPopoverMounted = isPopoverMounted;
|
|
5
|
+
exports.isPopoverOpen = isPopoverOpen;
|
|
6
|
+
exports.getPopoverDataState = getPopoverDataState;
|
|
7
|
+
exports.getPopoverTriggerAriaProps = getPopoverTriggerAriaProps;
|
|
8
|
+
exports.getPopoverAriaProps = getPopoverAriaProps;
|
|
9
|
+
const create_machine_js_1 = require("../../state/create-machine");
|
|
10
|
+
/**
|
|
11
|
+
* Popover state machine.
|
|
12
|
+
*
|
|
13
|
+
* Mirrors the Dialog machine's animated open/close cycle but without
|
|
14
|
+
* modal focus trapping — the popover is non-modal by default.
|
|
15
|
+
*
|
|
16
|
+
* Transition table:
|
|
17
|
+
* closed + OPEN → opening (animated) | open (instant)
|
|
18
|
+
* opening + ANIMATION_END → open
|
|
19
|
+
* opening + CLOSE → closed (cancel open)
|
|
20
|
+
* open + CLOSE → closing (animated) | closed (instant)
|
|
21
|
+
* open + TOGGLE → closing (animated) | closed (instant)
|
|
22
|
+
* closing + ANIMATION_END → closed
|
|
23
|
+
* closing + OPEN → opening (re-open mid-close)
|
|
24
|
+
* closing + TOGGLE → opening (re-open mid-close)
|
|
25
|
+
* closed + TOGGLE → opening (animated) | open (instant)
|
|
26
|
+
*/
|
|
27
|
+
exports.popoverMachine = (0, create_machine_js_1.createMachine)({
|
|
28
|
+
id: 'popover',
|
|
29
|
+
initial: 'closed',
|
|
30
|
+
context: {
|
|
31
|
+
id: 'popover',
|
|
32
|
+
triggerId: 'popover-trigger',
|
|
33
|
+
placement: 'bottom',
|
|
34
|
+
closeOnClickOutside: true,
|
|
35
|
+
closeOnEscape: true,
|
|
36
|
+
animated: true,
|
|
37
|
+
},
|
|
38
|
+
states: {
|
|
39
|
+
closed: {
|
|
40
|
+
OPEN: 'opening',
|
|
41
|
+
TOGGLE: 'opening',
|
|
42
|
+
},
|
|
43
|
+
opening: {
|
|
44
|
+
ANIMATION_END: 'open',
|
|
45
|
+
CLOSE: 'closed',
|
|
46
|
+
},
|
|
47
|
+
open: {
|
|
48
|
+
CLOSE: 'closing',
|
|
49
|
+
TOGGLE: 'closing',
|
|
50
|
+
},
|
|
51
|
+
closing: {
|
|
52
|
+
ANIMATION_END: 'closed',
|
|
53
|
+
OPEN: 'opening',
|
|
54
|
+
TOGGLE: 'opening',
|
|
55
|
+
},
|
|
56
|
+
},
|
|
57
|
+
});
|
|
58
|
+
// ─── Derived state helpers ─────────────────────────────────────────────────
|
|
59
|
+
/** Whether the popover DOM node should be present (mounted). */
|
|
60
|
+
function isPopoverMounted(state) {
|
|
61
|
+
return state === 'open' || state === 'opening' || state === 'closing';
|
|
62
|
+
}
|
|
63
|
+
/** Whether the popover is visible (aria-expanded). */
|
|
64
|
+
function isPopoverOpen(state) {
|
|
65
|
+
return state === 'open' || state === 'opening';
|
|
66
|
+
}
|
|
67
|
+
/** `data-state` attribute value for CSS animation hooks. */
|
|
68
|
+
function getPopoverDataState(state) {
|
|
69
|
+
return isPopoverOpen(state) ? 'open' : 'closed';
|
|
70
|
+
}
|
|
71
|
+
/** ARIA attributes for the trigger element. */
|
|
72
|
+
function getPopoverTriggerAriaProps(state, ctx) {
|
|
73
|
+
return {
|
|
74
|
+
id: ctx.triggerId,
|
|
75
|
+
'aria-expanded': isPopoverOpen(state),
|
|
76
|
+
'aria-controls': ctx.id,
|
|
77
|
+
'aria-haspopup': 'dialog',
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
/** ARIA attributes for the popover container element. */
|
|
81
|
+
function getPopoverAriaProps(ctx) {
|
|
82
|
+
return {
|
|
83
|
+
id: ctx.id,
|
|
84
|
+
role: 'dialog',
|
|
85
|
+
'aria-labelledby': ctx.triggerId,
|
|
86
|
+
};
|
|
87
|
+
}
|