@torpor/ui 0.0.17 → 0.1.1
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/CHANGELOG.md +41 -0
- package/dist/Accordion/Accordion.torp +53 -54
- package/dist/Accordion/AccordionContent.torp +5 -5
- package/dist/Accordion/AccordionHeader.torp +5 -4
- package/dist/Accordion/AccordionItem.torp +13 -8
- package/dist/Accordion/AccordionTrigger.torp +5 -5
- package/dist/Accordion/AccordionTypes.ts +1 -0
- package/dist/Dialog/Dialog.torp +156 -0
- package/dist/Dialog/DialogBody.torp +34 -0
- package/dist/Dialog/DialogButton.torp +48 -0
- package/dist/Dialog/DialogFooter.torp +13 -0
- package/dist/Dialog/DialogHeader.torp +34 -0
- package/dist/Dialog/DialogTypes.ts +16 -0
- package/dist/Dialog/index.cjs +53 -0
- package/dist/Dialog/index.cjs.map +1 -0
- package/dist/Dialog/index.js +22 -0
- package/dist/Dialog/index.js.map +1 -0
- package/dist/NavMenu/NavMenu.torp +178 -0
- package/dist/NavMenu/NavMenuButton.torp +46 -0
- package/dist/NavMenu/NavMenuElement.torp +43 -0
- package/dist/NavMenu/NavMenuGroup.torp +30 -0
- package/dist/NavMenu/NavMenuGroupHeader.torp +30 -0
- package/dist/NavMenu/NavMenuLink.torp +52 -0
- package/dist/NavMenu/NavMenuPopout.torp +69 -0
- package/dist/NavMenu/NavMenuPopoutButton.torp +120 -0
- package/dist/NavMenu/NavMenuPopoutContent.torp +79 -0
- package/dist/NavMenu/NavMenuSeparator.torp +14 -0
- package/dist/NavMenu/NavMenuTypes.ts +32 -0
- package/dist/NavMenu/index.cjs +78 -0
- package/dist/NavMenu/index.cjs.map +1 -0
- package/dist/NavMenu/index.js +42 -0
- package/dist/NavMenu/index.js.map +1 -0
- package/dist/Offscreen/Offscreen.torp +93 -0
- package/dist/Offscreen/index.cjs +33 -0
- package/dist/Offscreen/index.cjs.map +1 -0
- package/dist/Offscreen/index.js +6 -0
- package/dist/Offscreen/index.js.map +1 -0
- package/dist/Popover/Popover.torp +105 -0
- package/dist/Popover/PopoverContent.torp +78 -0
- package/dist/Popover/PopoverContentAnchored.torp +107 -0
- package/dist/Popover/PopoverContentContextual.torp +99 -0
- package/dist/Popover/PopoverOverlay.torp +32 -0
- package/dist/Popover/PopoverTrigger.torp +29 -0
- package/dist/Popover/PopoverTriggerContextual.torp +37 -0
- package/dist/Popover/PopoverTriggerHover.torp +38 -0
- package/dist/Popover/PopoverTypes.ts +30 -0
- package/dist/Popover/index.cjs +222 -0
- package/dist/Popover/index.cjs.map +1 -0
- package/dist/Popover/index.js +183 -0
- package/dist/Popover/index.js.map +1 -0
- package/dist/Popover/mountClickPopover.ts +50 -0
- package/dist/Popover/mountContextPopover.ts +50 -0
- package/dist/Popover/mountHoverPopover.ts +57 -0
- package/dist/Popover/showPopover.ts +24 -0
- package/dist/Popover/showPopoverPrompt.ts +23 -0
- package/dist/TabGroup/TabContent.torp +4 -4
- package/dist/TabGroup/TabGroup.torp +64 -62
- package/dist/TabGroup/TabGroupTypes.ts +1 -0
- package/dist/TabGroup/TabHeader.torp +10 -8
- package/dist/TabGroup/TabList.torp +4 -3
- package/dist/TabGroup/TabTrigger.torp +6 -6
- package/dist/metafile-cjs.json +1 -1
- package/dist/metafile-esm.json +1 -1
- package/dist/mount/disableScroll.ts +28 -0
- package/dist/mount/hoverTrigger.ts +59 -0
- package/dist/mount/trapFocus.ts +58 -0
- package/dist/utils/documentEvents.ts +17 -0
- package/dist/utils/getScrollParent.ts +22 -0
- package/dist/utils/setContextPosition.ts +31 -0
- package/dist/utils/setPopoverPosition.ts +138 -0
- package/package.json +26 -6
- package/scripts/postbuild.ts +17 -5
- package/src/Accordion/Accordion.torp +53 -54
- package/src/Accordion/AccordionContent.torp +5 -5
- package/src/Accordion/AccordionHeader.torp +5 -4
- package/src/Accordion/AccordionItem.torp +13 -8
- package/src/Accordion/AccordionTrigger.torp +5 -5
- package/src/Accordion/AccordionTypes.ts +1 -0
- package/src/Dialog/Dialog.torp +156 -0
- package/src/Dialog/DialogBody.torp +34 -0
- package/src/Dialog/DialogButton.torp +48 -0
- package/src/Dialog/DialogFooter.torp +13 -0
- package/src/Dialog/DialogHeader.torp +34 -0
- package/src/Dialog/DialogTypes.ts +16 -0
- package/src/Dialog/index.ts +7 -0
- package/src/Dialog.ts +7 -0
- package/src/NavMenu/NavMenu.torp +178 -0
- package/src/NavMenu/NavMenuButton.torp +46 -0
- package/src/NavMenu/NavMenuElement.torp +43 -0
- package/src/NavMenu/NavMenuGroup.torp +30 -0
- package/src/NavMenu/NavMenuGroupHeader.torp +30 -0
- package/src/NavMenu/NavMenuLink.torp +52 -0
- package/src/NavMenu/NavMenuPopout.torp +69 -0
- package/src/NavMenu/NavMenuPopoutButton.torp +120 -0
- package/src/NavMenu/NavMenuPopoutContent.torp +79 -0
- package/src/NavMenu/NavMenuSeparator.torp +14 -0
- package/src/NavMenu/NavMenuTypes.ts +32 -0
- package/src/NavMenu/index.ts +23 -0
- package/src/NavMenu.ts +23 -0
- package/src/Offscreen/Offscreen.torp +93 -0
- package/src/Offscreen/index.ts +3 -0
- package/src/Popover/Popover.torp +105 -0
- package/src/Popover/PopoverContent.torp +78 -0
- package/src/Popover/PopoverContentAnchored.torp +107 -0
- package/src/Popover/PopoverContentContextual.torp +99 -0
- package/src/Popover/PopoverOverlay.torp +32 -0
- package/src/Popover/PopoverTrigger.torp +29 -0
- package/src/Popover/PopoverTriggerContextual.torp +37 -0
- package/src/Popover/PopoverTriggerHover.torp +38 -0
- package/src/Popover/PopoverTypes.ts +30 -0
- package/src/Popover/index.ts +29 -0
- package/src/Popover/mountClickPopover.ts +50 -0
- package/src/Popover/mountContextPopover.ts +50 -0
- package/src/Popover/mountHoverPopover.ts +57 -0
- package/src/Popover/showPopover.ts +24 -0
- package/src/Popover/showPopoverPrompt.ts +23 -0
- package/src/Popover.ts +29 -0
- package/src/TabGroup/TabContent.torp +4 -4
- package/src/TabGroup/TabGroup.torp +64 -62
- package/src/TabGroup/TabGroupTypes.ts +1 -0
- package/src/TabGroup/TabHeader.torp +10 -8
- package/src/TabGroup/TabList.torp +4 -3
- package/src/TabGroup/TabTrigger.torp +6 -6
- package/src/mount/disableScroll.ts +28 -0
- package/src/mount/hoverTrigger.ts +59 -0
- package/src/mount/trapFocus.ts +58 -0
- package/src/utils/documentEvents.ts +17 -0
- package/src/utils/getScrollParent.ts +22 -0
- package/src/utils/setContextPosition.ts +31 -0
- package/src/utils/setPopoverPosition.ts +138 -0
- package/test/Accordion/accessibility.test.ts +0 -3
- package/test/Accordion/for-items.test.ts +2 -4
- package/test/Accordion/keyboard.test.ts +0 -3
- package/test/Accordion/multiple-item-toggle.test.ts +3 -5
- package/test/Accordion/single-item-toggle.test.ts +3 -5
- package/test/Dialog/accessibility-alert.test.ts +40 -0
- package/test/Dialog/accessibility-modal.test.ts +52 -0
- package/test/Dialog/components/CustomDialog.torp +20 -0
- package/test/Dialog/components/DialogAccessibility.torp +31 -0
- package/test/Dialog/components/DialogFunction.torp +22 -0
- package/test/Dialog/components/DialogVariable.torp +35 -0
- package/test/Dialog/function.test.ts +26 -0
- package/test/Dialog/variable.test.ts +27 -0
- package/test/NavMenu/accessibility.test.ts +97 -0
- package/test/NavMenu/basic.test.ts +22 -0
- package/test/NavMenu/components/NavMenu.torp +28 -0
- package/test/NavMenu/components/NavMenuAccessibility.torp +35 -0
- package/test/TabGroup/for-items.test.ts +2 -4
- package/test/TabGroup/item-trigger.test.ts +10 -11
- package/tsup.config.ts +9 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,46 @@
|
|
|
1
1
|
# @torpor/ui
|
|
2
2
|
|
|
3
|
+
## 0.1.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 4e6a5aa: Break: rename PopoverContentStatic to PopoverContent
|
|
8
|
+
- 2d7fae6: Fix: show the first tab content if no value set
|
|
9
|
+
- a3a40f6: Fix: Popover issues
|
|
10
|
+
- Updated dependencies [f9fd76d]
|
|
11
|
+
- Updated dependencies [1ddc240]
|
|
12
|
+
- Updated dependencies [7b9dc31]
|
|
13
|
+
- @torpor/view@0.1.1
|
|
14
|
+
- @torpor/unplugin@0.1.12
|
|
15
|
+
|
|
16
|
+
## 0.1.0
|
|
17
|
+
|
|
18
|
+
### Minor Changes
|
|
19
|
+
|
|
20
|
+
- 995513a: Break: specify binding with `&` rather than `:`
|
|
21
|
+
|
|
22
|
+
### Patch Changes
|
|
23
|
+
|
|
24
|
+
- cda6804: Fix: move itemStates out of $state
|
|
25
|
+
- 80c5461: Feat: id and class props for the Accordion
|
|
26
|
+
- cbd07ca: Feat: bind component props with `&name`
|
|
27
|
+
- d4a7ecc: Feat: UI Popover component
|
|
28
|
+
- b6bf066: Fix: set data-state (open/closed/active/inactive)
|
|
29
|
+
- baec4c7: Feat: UI NavMenu component
|
|
30
|
+
- 8545f43: Feat: UI Dialog component
|
|
31
|
+
- 7cdeaa5: Feat: id and class props for the TabGroup
|
|
32
|
+
- Updated dependencies [28a993b]
|
|
33
|
+
- Updated dependencies [98602ec]
|
|
34
|
+
- Updated dependencies [a49d307]
|
|
35
|
+
- Updated dependencies [995513a]
|
|
36
|
+
- Updated dependencies [c9a9cf8]
|
|
37
|
+
- Updated dependencies [cbd07ca]
|
|
38
|
+
- Updated dependencies [f53b184]
|
|
39
|
+
- Updated dependencies [215db17]
|
|
40
|
+
- Updated dependencies [95e866b]
|
|
41
|
+
- @torpor/unplugin@0.1.11
|
|
42
|
+
- @torpor/view@0.1.0
|
|
43
|
+
|
|
3
44
|
## 0.0.17
|
|
4
45
|
|
|
5
46
|
### Patch Changes
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { AccordionContextName, type AccordionContext, type ItemState } from
|
|
1
|
+
import { AccordionContextName, type AccordionContext, type ItemState } from "./AccordionTypes";
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* A vertically stacked set of interactive headings that enable users to reveal
|
|
@@ -19,24 +19,16 @@ import { AccordionContextName, type AccordionContext, type ItemState } from './A
|
|
|
19
19
|
* </Accordion>
|
|
20
20
|
* ```
|
|
21
21
|
*
|
|
22
|
-
* @prop {
|
|
23
|
-
* @prop {string | string[]} value -- The value of the expanded AccordionItem, or an array of values if the type is
|
|
22
|
+
* @prop {"single" | "multiple"} type -- Whether a single AccordionItem or multiple AccordionItems can be expanded at the same time
|
|
23
|
+
* @prop {string | string[]} value -- The value of the expanded AccordionItem, or an array of values if the type is "multiple"
|
|
24
24
|
* @prop {boolean} disabled -- If set to true, the user cannot interact with any items in the Accordion
|
|
25
25
|
* @prop {(value: string | string[]) => void} [onchange] -- Raised when the expanded item(s) have changed
|
|
26
26
|
*/
|
|
27
27
|
export default function Accordion($props: any) {
|
|
28
|
-
$props.type ||=
|
|
29
|
-
$props.value ||= $props.type ===
|
|
28
|
+
$props.type ||= "single";
|
|
29
|
+
$props.value ||= $props.type === "single" ? "" : [];
|
|
30
30
|
|
|
31
|
-
|
|
32
|
-
value: string | string[];
|
|
33
|
-
itemStates: ItemState[];
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
let $state: State = $watch({
|
|
37
|
-
value: $props.value,
|
|
38
|
-
itemStates: []
|
|
39
|
-
})
|
|
31
|
+
let itemStates: ItemState[] = [];
|
|
40
32
|
|
|
41
33
|
// This is used to assign automatic indexes to items where needed
|
|
42
34
|
let nextIndex = 0;
|
|
@@ -52,24 +44,24 @@ export default function Accordion($props: any) {
|
|
|
52
44
|
|
|
53
45
|
$run(() => {
|
|
54
46
|
// HACK: This should probably be done with setters?
|
|
55
|
-
let newValue = $
|
|
47
|
+
let newValue = $props.value;
|
|
56
48
|
|
|
57
49
|
// Get the value
|
|
58
50
|
switch ($props.type) {
|
|
59
|
-
case
|
|
51
|
+
case "single": {
|
|
60
52
|
// Ensure the value passed in was a string (in case e.g. the user has set values to ints)
|
|
61
53
|
newValue = newValue?.toString();
|
|
62
|
-
for (let item of
|
|
54
|
+
for (let item of itemStates) {
|
|
63
55
|
item.expanded = item.value === newValue;
|
|
64
56
|
}
|
|
65
57
|
break;
|
|
66
58
|
}
|
|
67
|
-
case
|
|
59
|
+
case "multiple": {
|
|
68
60
|
// Ensure the value passed in is an array
|
|
69
61
|
if (!Array.isArray(newValue)) {
|
|
70
62
|
newValue = [newValue];
|
|
71
63
|
}
|
|
72
|
-
for (let item of
|
|
64
|
+
for (let item of itemStates) {
|
|
73
65
|
item.expanded = newValue.indexOf(item.value) !== -1;
|
|
74
66
|
}
|
|
75
67
|
break;
|
|
@@ -77,20 +69,24 @@ export default function Accordion($props: any) {
|
|
|
77
69
|
}
|
|
78
70
|
|
|
79
71
|
// Set the value
|
|
80
|
-
$
|
|
72
|
+
$props.value = newValue;
|
|
81
73
|
})
|
|
82
74
|
|
|
83
75
|
$run(() => {
|
|
84
|
-
|
|
76
|
+
itemStates.forEach((s) => s.parentDisabled = $props.disabled);
|
|
85
77
|
});
|
|
86
78
|
|
|
87
79
|
// Dispatch the change event when the value changes
|
|
88
|
-
|
|
80
|
+
let firstChange = true;
|
|
89
81
|
$run(() => {
|
|
90
|
-
|
|
91
|
-
|
|
82
|
+
const ev = $props.onchange;
|
|
83
|
+
const value = $props.value;
|
|
84
|
+
if (firstChange) {
|
|
85
|
+
firstChange = false;
|
|
86
|
+
} else if (ev) {
|
|
87
|
+
ev(value);
|
|
92
88
|
}
|
|
93
|
-
})
|
|
89
|
+
});
|
|
94
90
|
|
|
95
91
|
function registerItem(itemState: ItemState) {
|
|
96
92
|
// If an index wasn't set for the item, set it automatically
|
|
@@ -104,52 +100,52 @@ export default function Accordion($props: any) {
|
|
|
104
100
|
}
|
|
105
101
|
|
|
106
102
|
// If we have a value, set whether the item should be expanded
|
|
107
|
-
if ($
|
|
103
|
+
if ($props.value) {
|
|
108
104
|
switch ($props.type) {
|
|
109
|
-
case
|
|
110
|
-
itemState.expanded = $
|
|
105
|
+
case "single": {
|
|
106
|
+
itemState.expanded = $props.value === itemState.value;
|
|
111
107
|
break;
|
|
112
108
|
}
|
|
113
|
-
case
|
|
114
|
-
itemState.expanded = $
|
|
109
|
+
case "multiple": {
|
|
110
|
+
itemState.expanded = $props.value.includes(itemState.value);
|
|
115
111
|
break;
|
|
116
112
|
}
|
|
117
113
|
}
|
|
118
114
|
}
|
|
119
115
|
|
|
120
116
|
// Set whether the Accordion itself is disabled
|
|
121
|
-
|
|
117
|
+
itemState.parentDisabled = $props.disabled
|
|
122
118
|
|
|
123
119
|
// Add the item's state to the array
|
|
124
|
-
|
|
120
|
+
itemStates.splice(itemState.index, 0, itemState);
|
|
125
121
|
}
|
|
126
122
|
|
|
127
123
|
// TODO: I think this should be done by value
|
|
128
124
|
function removeItem(itemIndex: number) {
|
|
129
125
|
// Remove the item and re-index
|
|
130
|
-
let index =
|
|
126
|
+
let index = itemStates.findIndex((item) => item.index === itemIndex);
|
|
131
127
|
if (index !== -1) {
|
|
132
|
-
|
|
128
|
+
itemStates.splice(itemIndex, 1);
|
|
133
129
|
}
|
|
134
|
-
|
|
130
|
+
itemStates.forEach((item, i) => (item.index = i));
|
|
135
131
|
}
|
|
136
132
|
|
|
137
133
|
function toggleItem(toggleValue: string) {
|
|
138
134
|
switch ($props.type) {
|
|
139
|
-
case
|
|
140
|
-
for (let item of
|
|
135
|
+
case "single": {
|
|
136
|
+
for (let item of itemStates) {
|
|
141
137
|
// The item is expanded if it has the supplied value (i.e. if it was clicked) and if it
|
|
142
138
|
// wasn't already expanded (i.e. we collapse it if it was expanded and clicked)
|
|
143
139
|
if (item.value === toggleValue) {
|
|
144
|
-
$
|
|
140
|
+
$props.value = item.expanded ? "" : item.value;
|
|
145
141
|
break;
|
|
146
142
|
}
|
|
147
143
|
}
|
|
148
144
|
break;
|
|
149
145
|
}
|
|
150
|
-
case
|
|
146
|
+
case "multiple": {
|
|
151
147
|
let newValue = [];
|
|
152
|
-
for (let item of
|
|
148
|
+
for (let item of itemStates) {
|
|
153
149
|
// The item is expanded if it has the supplied value (i.e. if it was clicked) or if it
|
|
154
150
|
// was already expanded
|
|
155
151
|
if (
|
|
@@ -159,7 +155,7 @@ export default function Accordion($props: any) {
|
|
|
159
155
|
newValue.push(item.value);
|
|
160
156
|
}
|
|
161
157
|
}
|
|
162
|
-
$
|
|
158
|
+
$props.value = newValue.sort();
|
|
163
159
|
break;
|
|
164
160
|
}
|
|
165
161
|
}
|
|
@@ -167,33 +163,33 @@ export default function Accordion($props: any) {
|
|
|
167
163
|
|
|
168
164
|
function handleHeaderKey(headerIndex: number, e: KeyboardEvent) {
|
|
169
165
|
switch (e.key) {
|
|
170
|
-
case
|
|
171
|
-
case
|
|
166
|
+
case "Up":
|
|
167
|
+
case "ArrowUp": {
|
|
172
168
|
if (headerIndex > 0) {
|
|
173
169
|
e.preventDefault();
|
|
174
|
-
|
|
170
|
+
itemStates[headerIndex - 1].setFocused();
|
|
175
171
|
}
|
|
176
172
|
break;
|
|
177
173
|
}
|
|
178
|
-
case
|
|
179
|
-
case
|
|
180
|
-
if (headerIndex <
|
|
174
|
+
case "Down":
|
|
175
|
+
case "ArrowDown": {
|
|
176
|
+
if (headerIndex < itemStates.length - 1) {
|
|
181
177
|
e.preventDefault();
|
|
182
|
-
|
|
178
|
+
itemStates[headerIndex + 1].setFocused();
|
|
183
179
|
}
|
|
184
180
|
break;
|
|
185
181
|
}
|
|
186
|
-
case
|
|
182
|
+
case "Home": {
|
|
187
183
|
if (headerIndex > 0) {
|
|
188
184
|
e.preventDefault();
|
|
189
|
-
|
|
185
|
+
itemStates[0].setFocused();
|
|
190
186
|
}
|
|
191
187
|
break;
|
|
192
188
|
}
|
|
193
|
-
case
|
|
194
|
-
if (headerIndex <
|
|
189
|
+
case "End": {
|
|
190
|
+
if (headerIndex < itemStates.length - 1) {
|
|
195
191
|
e.preventDefault();
|
|
196
|
-
|
|
192
|
+
itemStates[itemStates.length - 1].setFocused();
|
|
197
193
|
}
|
|
198
194
|
break;
|
|
199
195
|
}
|
|
@@ -201,7 +197,10 @@ export default function Accordion($props: any) {
|
|
|
201
197
|
}
|
|
202
198
|
|
|
203
199
|
@render {
|
|
204
|
-
<div
|
|
200
|
+
<div
|
|
201
|
+
id={$props.id}
|
|
202
|
+
class={`torp-accordion ${$props.class ?? ""}`.trim()}
|
|
203
|
+
>
|
|
205
204
|
<:slot />
|
|
206
205
|
</div>
|
|
207
206
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import getId from
|
|
2
|
-
import { AccordionItemContextName, type AccordionItemContext } from
|
|
1
|
+
import getId from "../utils/getId";
|
|
2
|
+
import { AccordionItemContextName, type AccordionItemContext } from "./AccordionTypes";
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Content for an AccordionItem, which can be revealed or hidden by an associated AccordionTrigger.
|
|
@@ -12,7 +12,7 @@ export default function AccordionContent($props: any) {
|
|
|
12
12
|
|
|
13
13
|
const context = $context[AccordionItemContextName] as AccordionItemContext;
|
|
14
14
|
if (!context) {
|
|
15
|
-
throw new Error(
|
|
15
|
+
throw new Error("AccordionContent must be contained within an AccordionItem");
|
|
16
16
|
}
|
|
17
17
|
let $state = $context[AccordionItemContextName].state
|
|
18
18
|
|
|
@@ -22,8 +22,8 @@ export default function AccordionContent($props: any) {
|
|
|
22
22
|
@render {
|
|
23
23
|
<div
|
|
24
24
|
id={$props.id}
|
|
25
|
-
class=
|
|
26
|
-
role={$props.ariaRegion ?
|
|
25
|
+
class={`torp-accordion-content ${$props.class ?? ""}`.trim()}
|
|
26
|
+
role={$props.ariaRegion ? "region" : undefined}
|
|
27
27
|
aria-labelledby={$state.headerId}
|
|
28
28
|
data-state={$state.dataState}
|
|
29
29
|
data-disabled={$state.dataDisabled}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { AccordionItemContextName, type AccordionItemContext } from
|
|
1
|
+
import { AccordionItemContextName, type AccordionItemContext } from "./AccordionTypes";
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* The header for an AccordionItem, which contains the AccordionTrigger.
|
|
@@ -11,19 +11,20 @@ import { AccordionItemContextName, type AccordionItemContext } from './Accordion
|
|
|
11
11
|
* </AccordionHeader>
|
|
12
12
|
* ```
|
|
13
13
|
*
|
|
14
|
-
* @prop {
|
|
14
|
+
* @prop {"h1" | "h2" | "h3" | "h4" | "h5" | "h6"} as -- The type of HTML header to use
|
|
15
15
|
*/
|
|
16
16
|
export default function AccordionHeader($props: any) {
|
|
17
17
|
const context = $context[AccordionItemContextName] as AccordionItemContext;
|
|
18
18
|
if (!context) {
|
|
19
|
-
throw new Error(
|
|
19
|
+
throw new Error("AccordionHeader must be contained within an AccordionItem");
|
|
20
20
|
}
|
|
21
21
|
let $state = $context[AccordionItemContextName].state
|
|
22
22
|
|
|
23
23
|
@render {
|
|
24
24
|
@// TODO: $props.as
|
|
25
25
|
<h4
|
|
26
|
-
|
|
26
|
+
id={$props.id}
|
|
27
|
+
class={`torp-accordion-header ${$props.class ?? ""}`.trim()}
|
|
27
28
|
data-state={$state.dataState}
|
|
28
29
|
data-disabled={$state.dataDisabled}
|
|
29
30
|
>
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { AccordionContextName, AccordionItemContextName, type AccordionContext, type AccordionItemContext } from
|
|
1
|
+
import { AccordionContextName, AccordionItemContextName, type AccordionContext, type AccordionItemContext } from "./AccordionTypes";
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* An item in an Accordion, which must consist of an AccordionTrigger and an AccordionContent.
|
|
@@ -27,14 +27,14 @@ export default function AccordionItem($props: any) {
|
|
|
27
27
|
disabled: $props.disabled,
|
|
28
28
|
parentDisabled: false,
|
|
29
29
|
get dataState() {
|
|
30
|
-
return this.expanded ?
|
|
30
|
+
return this.expanded ? "open" : "closed";
|
|
31
31
|
},
|
|
32
32
|
})
|
|
33
33
|
|
|
34
34
|
// Get stuff out of the AccordionContext
|
|
35
35
|
const context = $context[AccordionContextName] as AccordionContext;
|
|
36
36
|
if (!context) {
|
|
37
|
-
throw new Error(
|
|
37
|
+
throw new Error("AccordionItem must be contained within an Accordion");
|
|
38
38
|
}
|
|
39
39
|
|
|
40
40
|
// Put stuff into an AccordionItemContext
|
|
@@ -56,16 +56,21 @@ export default function AccordionItem($props: any) {
|
|
|
56
56
|
});
|
|
57
57
|
|
|
58
58
|
// Dispatch the toggle event when the value of expanded changes
|
|
59
|
-
|
|
59
|
+
let firstToggle = true;
|
|
60
60
|
$run(() => {
|
|
61
|
-
|
|
62
|
-
|
|
61
|
+
const ev = $props.ontoggle;
|
|
62
|
+
const expanded = $state.expanded;
|
|
63
|
+
if (firstToggle) {
|
|
64
|
+
firstToggle = false;
|
|
65
|
+
} else if (ev) {
|
|
66
|
+
ev(expanded);
|
|
63
67
|
}
|
|
64
|
-
})
|
|
68
|
+
});
|
|
65
69
|
|
|
66
70
|
@render {
|
|
67
71
|
<div
|
|
68
|
-
|
|
72
|
+
id={$props.id}
|
|
73
|
+
class={`torp-accordion-item ${$props.class ?? ""}`.trim()}
|
|
69
74
|
data-state={$state.dataState}
|
|
70
75
|
data-disabled={$state.dataDisabled}
|
|
71
76
|
>
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import getId from
|
|
2
|
-
import { AccordionItemContextName, type AccordionItemContext } from
|
|
1
|
+
import getId from "../utils/getId";
|
|
2
|
+
import { AccordionItemContextName, type AccordionItemContext } from "./AccordionTypes";
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* A header button for an AccordionItem, which can be used to reveal or hide an associated AccordionContent.
|
|
@@ -12,7 +12,7 @@ export default function AccordionTrigger() {
|
|
|
12
12
|
// Get stuff out of the AccordionItemContext
|
|
13
13
|
const context = $context[AccordionItemContextName] as AccordionItemContext;
|
|
14
14
|
if (!context) {
|
|
15
|
-
throw new Error(
|
|
15
|
+
throw new Error("AccordionTrigger must be contained within an AccordionItem");
|
|
16
16
|
}
|
|
17
17
|
let $state = $context[AccordionItemContextName].state;
|
|
18
18
|
|
|
@@ -42,14 +42,14 @@ export default function AccordionTrigger() {
|
|
|
42
42
|
<button
|
|
43
43
|
type="button"
|
|
44
44
|
id={$props.id}
|
|
45
|
-
class=
|
|
45
|
+
class={`torp-accordion-trigger ${$props.class ?? ""}`.trim()}
|
|
46
46
|
disabled={$state.disabled || $state.parentDisabled}
|
|
47
47
|
aria-expanded={$state.expanded.toString()}
|
|
48
48
|
aria-disabled={$state.disabled}
|
|
49
49
|
aria-controls={$state.contentId}
|
|
50
50
|
data-state={$state.dataState}
|
|
51
51
|
data-disabled={$state.disabled}
|
|
52
|
-
|
|
52
|
+
&ref={button}
|
|
53
53
|
onclick={handleClick}
|
|
54
54
|
onkeydown={handleKey}
|
|
55
55
|
>
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import getId from "../utils/getId";
|
|
2
|
+
import { addDocumentEvent, removeDocumentEvent } from "../utils/documentEvents";
|
|
3
|
+
import { trapFocus, type TrapFocusOptions } from "../mount/trapFocus";
|
|
4
|
+
import { PopoverContextName, type PopoverContext } from "../Popover/PopoverTypes";
|
|
5
|
+
import { DialogContextName, type DialogContext, type DialogState } from "./DialogTypes";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Content for a window displayed in a Popover.
|
|
9
|
+
*
|
|
10
|
+
* See [the WAI ARIA guidelines for Dialogs](https://www.w3.org/WAI/ARIA/apg/patterns/dialogmodal/).
|
|
11
|
+
*
|
|
12
|
+
* The structure of a Dialog is:
|
|
13
|
+
*
|
|
14
|
+
* ```
|
|
15
|
+
* <Dialog>
|
|
16
|
+
* <DialogHeader /> (optional)
|
|
17
|
+
* <DialogBody /> (optional)
|
|
18
|
+
* <DialogFooter> (optional)
|
|
19
|
+
* <DialogButton /> (optional)
|
|
20
|
+
* </DialogFooter>
|
|
21
|
+
* </Dialog>
|
|
22
|
+
* ```
|
|
23
|
+
*
|
|
24
|
+
* @prop {string | undefined} ariaLabel -- An ARIA label for describing the popover to screen readers
|
|
25
|
+
* @prop {boolean} alert -- Whether the dialog should alert the user via the screen reader
|
|
26
|
+
*/
|
|
27
|
+
export default function Dialog($props: any) {
|
|
28
|
+
$props.id ??= getId();
|
|
29
|
+
$props.dialog ??= false;
|
|
30
|
+
|
|
31
|
+
let div: HTMLDivElement;
|
|
32
|
+
|
|
33
|
+
let trapFocusOptions: TrapFocusOptions = {
|
|
34
|
+
focusableElements: []
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
// Get stuff out of the PopoverContext
|
|
38
|
+
const popoverContext = $context[PopoverContextName] as PopoverContext;
|
|
39
|
+
if (!popoverContext) {
|
|
40
|
+
throw new Error("Dialog must be contained within a Popover");
|
|
41
|
+
}
|
|
42
|
+
const { handleButton, modal } = popoverContext;
|
|
43
|
+
const visible = popoverContext.state.visible;
|
|
44
|
+
|
|
45
|
+
popoverContext.handleClickOutside = handleClickOutside;
|
|
46
|
+
// Set the focusFirstElement method in the PopoverContext so that it can be
|
|
47
|
+
// called after everything has been shown
|
|
48
|
+
popoverContext.focusFirstElement = focusFirstElement;
|
|
49
|
+
|
|
50
|
+
// Set the IDs for using in the Dialog's ARIA attributes
|
|
51
|
+
let $state: DialogState = $watch({
|
|
52
|
+
dialogId: $props.id,
|
|
53
|
+
headerId: "",
|
|
54
|
+
bodyId: "",
|
|
55
|
+
visible
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
// Set the context to pass down to items
|
|
59
|
+
let context: DialogContext = {
|
|
60
|
+
handleButton,
|
|
61
|
+
state: $state,
|
|
62
|
+
anchorElement: popoverContext.anchorElement
|
|
63
|
+
};
|
|
64
|
+
$context[DialogContextName] = context;
|
|
65
|
+
|
|
66
|
+
let closeOnClick = false;
|
|
67
|
+
|
|
68
|
+
$mount(() => {
|
|
69
|
+
if ($state.visible) {
|
|
70
|
+
// HACK: Because this Dialog could be shown with a click, we need to
|
|
71
|
+
// wait for another mousedown before we close on click. Otherwise
|
|
72
|
+
// the click immediately bubbles to the document. I guess we could
|
|
73
|
+
// require the user to preventDefault after calling showPrompt etc,
|
|
74
|
+
// but that seems very error-prone?
|
|
75
|
+
closeOnClick = false;
|
|
76
|
+
addDocumentEvent("mousedown", handleDocumentMouseDown);
|
|
77
|
+
addDocumentEvent("click", handleDocumentClick);
|
|
78
|
+
} else {
|
|
79
|
+
removeDocumentEvent("mousedown", handleDocumentMouseDown);
|
|
80
|
+
removeDocumentEvent("click", handleDocumentClick);
|
|
81
|
+
// Focus the anchor element per WAI guidelines
|
|
82
|
+
if (context.anchorElement?.focus) {
|
|
83
|
+
context.anchorElement.focus();
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
function focusFirstElement() {
|
|
89
|
+
if (trapFocusOptions.focusableElements.length) {
|
|
90
|
+
trapFocusOptions.focusableElements[0].focus();
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function handleDocumentMouseDown() {
|
|
95
|
+
closeOnClick = true;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function handleDocumentClick(e: MouseEvent) {
|
|
99
|
+
if (
|
|
100
|
+
closeOnClick &&
|
|
101
|
+
div &&
|
|
102
|
+
!div.contains(e.target as HTMLElement) &&
|
|
103
|
+
(!context.anchorElement || !context.anchorElement.contains(e.target as HTMLElement))
|
|
104
|
+
) {
|
|
105
|
+
handleClickOutside(e);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function handleClickOutside(e: MouseEvent) {
|
|
110
|
+
// Don't allow the user to click outside -- they must press a button or
|
|
111
|
+
// the Esc key
|
|
112
|
+
e.preventDefault();
|
|
113
|
+
if (!modal) {
|
|
114
|
+
popoverContext.state.visible = false;
|
|
115
|
+
context.state.visible = false;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function handleKey(e: KeyboardEvent) {
|
|
120
|
+
switch (e.key) {
|
|
121
|
+
case "Esc":
|
|
122
|
+
case "Escape": {
|
|
123
|
+
if (context.cancelButton) {
|
|
124
|
+
context.cancelButton.click();
|
|
125
|
+
e.preventDefault();
|
|
126
|
+
}
|
|
127
|
+
break;
|
|
128
|
+
}
|
|
129
|
+
case "Enter": {
|
|
130
|
+
if (context.confirmButton) {
|
|
131
|
+
context.confirmButton.click();
|
|
132
|
+
e.preventDefault();
|
|
133
|
+
}
|
|
134
|
+
break;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
@render {
|
|
140
|
+
<div
|
|
141
|
+
id={$props.id}
|
|
142
|
+
class={`torp-dialog ${$props.class ?? ""}`.trim()}
|
|
143
|
+
tabindex="0"
|
|
144
|
+
role={$props.alert ? "alertdialog" : "dialog"}
|
|
145
|
+
aria-modal={modal}
|
|
146
|
+
aria-label={$props.ariaLabel}
|
|
147
|
+
aria-labelledby={!$props.ariaLabel && $state.headerId ? $state.headerId : undefined}
|
|
148
|
+
aria-describedby={$state.bodyId ? $state.bodyId : undefined}
|
|
149
|
+
onkeydown={handleKey}
|
|
150
|
+
:onmount={(el) => trapFocus(el, trapFocusOptions)}
|
|
151
|
+
&ref={div}
|
|
152
|
+
>
|
|
153
|
+
<:slot />
|
|
154
|
+
</div>
|
|
155
|
+
}
|
|
156
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import getId from "../utils/getId";
|
|
2
|
+
import { DialogContextName, type DialogContext } from "./DialogTypes";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* The body information for a Dialog. Content in the body will be announced by
|
|
6
|
+
* screen readers when the Dialog is opened, unless ariaAnnounce is false.
|
|
7
|
+
*
|
|
8
|
+
* @prop {boolean} ariaAnnounce -- Whether the body content will be announced by screen readers when the Dialog is opened
|
|
9
|
+
*/
|
|
10
|
+
export default function DialogBody($props: any) {
|
|
11
|
+
$props.id ??= getId();
|
|
12
|
+
$props.ariaAnnounce ??= true;
|
|
13
|
+
|
|
14
|
+
// Get stuff out of the DialogContext
|
|
15
|
+
const context = $context[DialogContextName] as DialogContext;
|
|
16
|
+
if (!context) {
|
|
17
|
+
throw new Error("DialogBody must be contained within a Dialog");
|
|
18
|
+
}
|
|
19
|
+
let $state = context.state;
|
|
20
|
+
|
|
21
|
+
// Set the body id for using in the Dialog's ARIA attributes
|
|
22
|
+
if ($props.ariaAnnounce) {
|
|
23
|
+
$state.bodyId = $props.id;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
@render {
|
|
27
|
+
<div
|
|
28
|
+
id={$props.id}
|
|
29
|
+
class={`torp-dialog-body ${$props.class ?? ""}`.trim()}
|
|
30
|
+
>
|
|
31
|
+
<:slot />
|
|
32
|
+
</div>
|
|
33
|
+
}
|
|
34
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { DialogContextName, type DialogContext } from "./DialogTypes";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* A button that is placed in a DialogFooter and signals a response by a user.
|
|
5
|
+
*
|
|
6
|
+
* @prop {any | undefined} value -- The value that should be returned when this button is pressed and the Dialog was invoked by calling showContextual, showPrompt or showPopover
|
|
7
|
+
* @prop {"confirm" | "cancel" | undefined} type -- "confirm" if the button should respond to the Enter key or "cancel" if the button should respond to the Escape key
|
|
8
|
+
* @prop {boolean} disabled -- Whether this button is disabled
|
|
9
|
+
*/
|
|
10
|
+
export default function DialogButton($props: any) {
|
|
11
|
+
let button: HTMLButtonElement;
|
|
12
|
+
|
|
13
|
+
// Get stuff out of the DialogContext
|
|
14
|
+
const context = $context[DialogContextName] as DialogContext;
|
|
15
|
+
if (!context) {
|
|
16
|
+
throw new Error("DialogButton must be contained within a Dialog");
|
|
17
|
+
}
|
|
18
|
+
const handleButton = context.handleButton;
|
|
19
|
+
|
|
20
|
+
$mount(() => {
|
|
21
|
+
// Store the confirm or cancel button in the DialogContext
|
|
22
|
+
if ($props.type === "confirm") {
|
|
23
|
+
context.confirmButton = button;
|
|
24
|
+
} else if ($props.type === "cancel") {
|
|
25
|
+
context.cancelButton = button;
|
|
26
|
+
}
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
function handleClick(e) {
|
|
30
|
+
if ($props.onclick) {
|
|
31
|
+
$props.onclick(e)
|
|
32
|
+
}
|
|
33
|
+
handleButton($props.type, $props.value);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
@render {
|
|
37
|
+
<button
|
|
38
|
+
type="button"
|
|
39
|
+
id={$props.id}
|
|
40
|
+
class={`torp-dialog-button ${$props.class ?? ""}`.trim()}
|
|
41
|
+
disabled={$props.disabled}
|
|
42
|
+
onclick={handleClick}
|
|
43
|
+
&ref={button}
|
|
44
|
+
>
|
|
45
|
+
<:slot />
|
|
46
|
+
</button>
|
|
47
|
+
}
|
|
48
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The footer for a Dialog, which may contain one or more DialogButtons.
|
|
3
|
+
*/
|
|
4
|
+
export default function DialogFooter($props: any) {
|
|
5
|
+
@render {
|
|
6
|
+
<div
|
|
7
|
+
id={$props.id}
|
|
8
|
+
class={`torp-dialog-footer ${$props.class ?? ""}`.trim()}
|
|
9
|
+
>
|
|
10
|
+
<:slot />
|
|
11
|
+
</div>
|
|
12
|
+
}
|
|
13
|
+
}
|