@adia-ai/web-components 0.4.5 → 0.4.7
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 +63 -24
- package/USAGE.md +604 -0
- package/components/accordion/accordion.d.ts +17 -0
- package/components/accordion/accordion.js +10 -117
- package/components/accordion/class.js +132 -0
- package/components/action-list/action-list.d.ts +15 -0
- package/components/action-list/action-list.js +9 -140
- package/components/action-list/class.js +156 -0
- package/components/agent-artifact/agent-artifact.d.ts +25 -0
- package/components/agent-artifact/agent-artifact.js +8 -181
- package/components/agent-artifact/class.js +200 -0
- package/components/agent-feedback-bar/agent-feedback-bar.d.ts +21 -0
- package/components/agent-feedback-bar/agent-feedback-bar.js +8 -143
- package/components/agent-feedback-bar/class.js +162 -0
- package/components/agent-questions/agent-questions.d.ts +23 -0
- package/components/agent-questions/agent-questions.js +8 -180
- package/components/agent-questions/class.js +199 -0
- package/components/agent-reasoning/agent-reasoning.d.ts +23 -0
- package/components/agent-reasoning/agent-reasoning.js +8 -494
- package/components/agent-reasoning/class.js +513 -0
- package/components/agent-suggestions/agent-suggestions.d.ts +21 -0
- package/components/agent-suggestions/agent-suggestions.js +8 -78
- package/components/agent-suggestions/class.js +97 -0
- package/components/agent-trace/agent-trace.d.ts +19 -0
- package/components/alert/alert.d.ts +29 -0
- package/components/alert/alert.js +8 -175
- package/components/alert/class.js +194 -0
- package/components/avatar/avatar.d.ts +27 -0
- package/components/avatar/avatar.js +9 -159
- package/components/avatar/class.js +173 -0
- package/components/badge/badge.d.ts +27 -0
- package/components/badge/badge.js +9 -75
- package/components/badge/class.js +93 -0
- package/components/block/block.d.ts +19 -0
- package/components/block/block.js +9 -15
- package/components/block/class.js +33 -0
- package/components/breadcrumb/breadcrumb.d.ts +23 -0
- package/components/breadcrumb/breadcrumb.js +8 -113
- package/components/breadcrumb/class.js +132 -0
- package/components/button/button.d.ts +34 -0
- package/components/button/button.js +15 -66
- package/components/button/class.js +80 -0
- package/components/calendar-picker/calendar-picker.a2ui.json +6 -1
- package/components/calendar-picker/calendar-picker.d.ts +27 -0
- package/components/calendar-picker/calendar-picker.js +8 -332
- package/components/calendar-picker/calendar-picker.yaml +51 -177
- package/components/calendar-picker/class.js +351 -0
- package/components/canvas/canvas.a2ui.json +6 -1
- package/components/canvas/canvas.d.ts +17 -0
- package/components/canvas/canvas.yaml +19 -36
- package/components/card/card.a2ui.json +3 -0
- package/components/card/card.d.ts +27 -0
- package/components/card/card.js +9 -50
- package/components/card/card.yaml +171 -433
- package/components/card/class.js +68 -0
- package/components/chart/chart.d.ts +41 -0
- package/components/chart/chart.js +8 -2131
- package/components/chart/class.js +2150 -0
- package/components/chart-legend/chart-legend.d.ts +27 -0
- package/components/chart-legend/chart-legend.js +8 -197
- package/components/chart-legend/class.js +215 -0
- package/components/chat-thread/chat-thread.d.ts +17 -0
- package/components/chat-thread/chat-thread.js +8 -157
- package/components/chat-thread/class.js +176 -0
- package/components/check/check.d.ts +30 -0
- package/components/check/check.js +11 -52
- package/components/check/class.js +68 -0
- package/components/code/class.js +501 -0
- package/components/code/code.d.ts +39 -0
- package/components/code/code.js +8 -482
- package/components/col/class.js +30 -0
- package/components/col/col.d.ts +23 -0
- package/components/col/col.js +10 -13
- package/components/color-picker/class.js +550 -0
- package/components/color-picker/color-picker.d.ts +37 -0
- package/components/color-picker/color-picker.js +8 -531
- package/components/command/class.js +364 -0
- package/components/command/command.a2ui.json +3 -0
- package/components/command/command.d.ts +19 -0
- package/components/command/command.js +8 -345
- package/components/command/command.yaml +105 -124
- package/components/demo-toggle/class.js +153 -0
- package/components/demo-toggle/demo-toggle.d.ts +23 -0
- package/components/demo-toggle/demo-toggle.js +8 -135
- package/components/description-list/class.js +86 -0
- package/components/description-list/description-list.d.ts +21 -0
- package/components/description-list/description-list.js +8 -67
- package/components/divider/class.js +57 -0
- package/components/divider/divider.d.ts +19 -0
- package/components/divider/divider.js +10 -40
- package/components/drawer/class.js +306 -0
- package/components/drawer/drawer.d.ts +25 -0
- package/components/drawer/drawer.js +8 -287
- package/components/embed/class.js +73 -0
- package/components/embed/embed.d.ts +23 -0
- package/components/embed/embed.js +9 -55
- package/components/empty-state/class.js +108 -0
- package/components/empty-state/empty-state.d.ts +21 -0
- package/components/empty-state/empty-state.js +9 -90
- package/components/feed/class.js +381 -0
- package/components/feed/feed.d.ts +19 -0
- package/components/feed/feed.js +9 -367
- package/components/field/class.js +266 -0
- package/components/field/field.d.ts +23 -0
- package/components/field/field.js +8 -247
- package/components/fields/class.js +106 -0
- package/components/fields/fields.d.ts +19 -0
- package/components/fields/fields.js +8 -87
- package/components/grid/class.js +31 -0
- package/components/grid/grid.d.ts +23 -0
- package/components/grid/grid.js +10 -14
- package/components/heatmap/class.js +305 -0
- package/components/heatmap/heatmap.d.ts +31 -0
- package/components/heatmap/heatmap.js +8 -286
- package/components/icon/class.js +54 -0
- package/components/icon/icon.d.ts +23 -0
- package/components/icon/icon.js +13 -40
- package/components/image/class.js +112 -0
- package/components/image/image.d.ts +33 -0
- package/components/image/image.js +9 -94
- package/components/index.js +1 -0
- package/components/input/class.js +773 -0
- package/components/input/input.a2ui.json +3 -0
- package/components/input/input.d.ts +61 -0
- package/components/input/input.js +8 -755
- package/components/input/input.yaml +171 -442
- package/components/inspector/class.js +142 -0
- package/components/inspector/inspector.a2ui.json +8 -1
- package/components/inspector/inspector.d.ts +17 -0
- package/components/inspector/inspector.js +8 -124
- package/components/inspector/inspector.yaml +15 -30
- package/components/kbd/class.js +34 -0
- package/components/kbd/kbd.a2ui.json +3 -0
- package/components/kbd/kbd.d.ts +17 -0
- package/components/kbd/kbd.js +10 -17
- package/components/kbd/kbd.yaml +54 -185
- package/components/link/class.js +187 -0
- package/components/link/link.d.ts +55 -0
- package/components/link/link.js +8 -168
- package/components/list/class.js +249 -0
- package/components/list/list.d.ts +23 -0
- package/components/list/list.js +9 -231
- package/components/menu/class.js +332 -0
- package/components/menu/menu.d.ts +21 -0
- package/components/menu/menu.js +11 -316
- package/components/modal/class.js +231 -0
- package/components/modal/modal.a2ui.json +5 -1
- package/components/modal/modal.d.ts +23 -0
- package/components/modal/modal.js +8 -212
- package/components/modal/modal.yaml +19 -39
- package/components/nav/class.js +150 -0
- package/components/nav/nav.d.ts +31 -0
- package/components/nav/nav.js +8 -131
- package/components/nav-group/class.js +152 -0
- package/components/nav-group/nav-group.d.ts +35 -0
- package/components/nav-group/nav-group.js +9 -134
- package/components/nav-item/class.js +86 -0
- package/components/nav-item/nav-item.d.ts +37 -0
- package/components/nav-item/nav-item.js +10 -69
- package/components/noodles/class.js +510 -0
- package/components/noodles/noodles.d.ts +33 -0
- package/components/noodles/noodles.js +9 -493
- package/components/option-card/class.js +167 -0
- package/components/option-card/option-card.d.ts +30 -0
- package/components/option-card/option-card.js +8 -149
- package/components/otp-input/class.js +180 -0
- package/components/otp-input/otp-input.a2ui.json +5 -1
- package/components/otp-input/otp-input.d.ts +25 -0
- package/components/otp-input/otp-input.js +9 -162
- package/components/otp-input/otp-input.yaml +45 -174
- package/components/page/class.js +97 -0
- package/components/page/page.d.ts +46 -0
- package/components/page/page.js +8 -79
- package/components/pagination/class.js +195 -0
- package/components/pagination/pagination.d.ts +23 -0
- package/components/pagination/pagination.js +9 -177
- package/components/pane/class.js +186 -0
- package/components/pane/pane.a2ui.json +12 -1
- package/components/pane/pane.css +10 -0
- package/components/pane/pane.d.ts +31 -0
- package/components/pane/pane.js +8 -143
- package/components/pane/pane.yaml +57 -157
- package/components/pipeline-status/class.js +189 -0
- package/components/pipeline-status/pipeline-status.a2ui.json +7 -1
- package/components/pipeline-status/pipeline-status.d.ts +21 -0
- package/components/pipeline-status/pipeline-status.js +9 -172
- package/components/pipeline-status/pipeline-status.yaml +34 -72
- package/components/popover/class.js +194 -0
- package/components/popover/popover.d.ts +23 -0
- package/components/popover/popover.js +9 -176
- package/components/progress/class.js +74 -0
- package/components/progress/progress.a2ui.json +3 -0
- package/components/progress/progress.d.ts +19 -0
- package/components/progress/progress.js +10 -57
- package/components/progress/progress.yaml +124 -287
- package/components/progress-row/class.js +110 -0
- package/components/progress-row/progress-row.d.ts +23 -0
- package/components/progress-row/progress-row.js +8 -92
- package/components/radio/class.js +83 -0
- package/components/radio/radio.d.ts +28 -0
- package/components/radio/radio.js +11 -67
- package/components/range/class.js +194 -0
- package/components/range/range.d.ts +31 -0
- package/components/range/range.js +9 -176
- package/components/rating/class.js +148 -0
- package/components/rating/rating.d.ts +33 -0
- package/components/rating/rating.js +9 -130
- package/components/richtext/class.js +87 -0
- package/components/richtext/richtext.a2ui.json +7 -1
- package/components/richtext/richtext.d.ts +19 -0
- package/components/richtext/richtext.js +8 -68
- package/components/richtext/richtext.yaml +30 -65
- package/components/row/class.js +50 -0
- package/components/row/row.d.ts +27 -0
- package/components/row/row.js +10 -33
- package/components/search/class.js +134 -0
- package/components/search/search.d.ts +35 -0
- package/components/search/search.js +10 -117
- package/components/segment/class.js +62 -0
- package/components/segment/segment.d.ts +25 -0
- package/components/segment/segment.js +10 -45
- package/components/segmented/class.js +165 -0
- package/components/segmented/segmented.a2ui.json +4 -0
- package/components/segmented/segmented.d.ts +24 -0
- package/components/segmented/segmented.js +10 -148
- package/components/segmented/segmented.yaml +41 -59
- package/components/select/class.js +408 -0
- package/components/select/select.d.ts +57 -0
- package/components/select/select.js +15 -396
- package/components/skeleton/class.js +52 -0
- package/components/skeleton/skeleton.d.ts +23 -0
- package/components/skeleton/skeleton.js +8 -34
- package/components/slider/class.js +184 -0
- package/components/slider/slider.d.ts +31 -0
- package/components/slider/slider.js +9 -166
- package/components/stack/class.js +28 -0
- package/components/stack/stack.d.ts +17 -0
- package/components/stack/stack.js +10 -11
- package/components/step-progress/class.js +98 -0
- package/components/step-progress/step-progress.d.ts +27 -0
- package/components/step-progress/step-progress.js +8 -79
- package/components/stepper/class.js +126 -0
- package/components/stepper/stepper.d.ts +19 -0
- package/components/stepper/stepper.js +9 -112
- package/components/stream/class.js +109 -0
- package/components/stream/stream.d.ts +19 -0
- package/components/stream/stream.js +8 -90
- package/components/swatch/class.js +131 -0
- package/components/swatch/swatch.d.ts +28 -0
- package/components/swatch/swatch.js +8 -112
- package/components/swiper/class.js +373 -0
- package/components/swiper/swiper.a2ui.json +4 -0
- package/components/swiper/swiper.d.ts +31 -0
- package/components/swiper/swiper.js +8 -354
- package/components/swiper/swiper.yaml +68 -212
- package/components/switch/class.js +63 -0
- package/components/switch/switch.a2ui.json +6 -1
- package/components/switch/switch.d.ts +30 -0
- package/components/switch/switch.js +11 -47
- package/components/switch/switch.yaml +70 -265
- package/components/table/class.js +1453 -0
- package/components/table/table.d.ts +37 -0
- package/components/table/table.js +8 -1435
- package/components/table-toolbar/class.js +680 -0
- package/components/table-toolbar/table-toolbar.d.ts +33 -0
- package/components/table-toolbar/table-toolbar.js +8 -689
- package/components/tabs/class.js +242 -0
- package/components/tabs/tabs.d.ts +21 -0
- package/components/tabs/tabs.js +8 -223
- package/components/tag/class.js +99 -0
- package/components/tag/tag.d.ts +27 -0
- package/components/tag/tag.js +8 -80
- package/components/text/class.js +46 -0
- package/components/text/text.d.ts +25 -0
- package/components/text/text.js +9 -28
- package/components/textarea/class.js +134 -0
- package/components/textarea/textarea.d.ts +31 -0
- package/components/textarea/textarea.js +11 -118
- package/components/timeline/class.js +176 -0
- package/components/timeline/timeline.d.ts +19 -0
- package/components/timeline/timeline.js +9 -162
- package/components/toast/class.js +92 -0
- package/components/toast/toast.d.ts +23 -0
- package/components/toast/toast.js +9 -76
- package/components/toggle-group/class.js +154 -0
- package/components/toggle-group/toggle-group.d.ts +19 -0
- package/components/toggle-group/toggle-group.js +11 -140
- package/components/toggle-scheme/class.js +286 -0
- package/components/toggle-scheme/toggle-scheme.a2ui.json +197 -0
- package/components/toggle-scheme/toggle-scheme.css +20 -0
- package/components/toggle-scheme/toggle-scheme.d.ts +41 -0
- package/components/toggle-scheme/toggle-scheme.js +17 -0
- package/components/toggle-scheme/toggle-scheme.yaml +173 -0
- package/components/toolbar/class.js +388 -0
- package/components/toolbar/toolbar.d.ts +23 -0
- package/components/toolbar/toolbar.js +10 -376
- package/components/tooltip/class.js +299 -0
- package/components/tooltip/tooltip.d.ts +27 -0
- package/components/tooltip/tooltip.js +8 -280
- package/components/tree/class.js +245 -0
- package/components/tree/tree.d.ts +15 -0
- package/components/tree/tree.js +9 -244
- package/components/upload/class.js +199 -0
- package/components/upload/upload.d.ts +27 -0
- package/components/upload/upload.js +11 -183
- package/core/element.d.ts +174 -0
- package/core/form.d.ts +108 -0
- package/core/index.d.ts +11 -0
- package/core/index.js +1 -0
- package/core/register.d.ts +25 -0
- package/core/register.js +58 -0
- package/core/signals.d.ts +94 -0
- package/core/template.d.ts +70 -0
- package/index.d.ts +315 -0
- package/package.json +25 -6
- package/traits/CATEGORIES.md +1 -1
|
@@ -0,0 +1,306 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Non-side-effect class export for `<drawer-ui>`.
|
|
3
|
+
*
|
|
4
|
+
* Importing this file gives you the class(es) without auto-registering the tag.
|
|
5
|
+
* Useful for test isolation, subclassing with tag-name override, or selective
|
|
6
|
+
* composition.
|
|
7
|
+
*
|
|
8
|
+
* The auto-register path stays at `@adia-ai/web-components/components/drawer`
|
|
9
|
+
* (which imports this file + calls `defineIfFree()`).
|
|
10
|
+
*
|
|
11
|
+
* @see ../../USAGE.md#registration--auto-vs-explicit
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* <drawer-ui> — Slide-out panel anchored to a viewport edge.
|
|
16
|
+
*
|
|
17
|
+
* Uses native <dialog> for focus trapping, Escape key, and ::backdrop.
|
|
18
|
+
* Content panel slides in/out with CSS transitions.
|
|
19
|
+
* Exit animation plays before dialog.close() via [data-closing].
|
|
20
|
+
*
|
|
21
|
+
* Authoring — card-ui-style tags (recommended):
|
|
22
|
+
* <drawer-ui side="right" size="md">
|
|
23
|
+
* <header>Title block</header>
|
|
24
|
+
* <section>Body content</section>
|
|
25
|
+
* <footer>Action buttons</footer>
|
|
26
|
+
* </drawer-ui>
|
|
27
|
+
*
|
|
28
|
+
* Authoring — explicit slots (also supported):
|
|
29
|
+
* <drawer-ui>
|
|
30
|
+
* <header slot="header">…</header>
|
|
31
|
+
* <section slot="body">…</section>
|
|
32
|
+
* <footer slot="footer">…</footer>
|
|
33
|
+
* </drawer-ui>
|
|
34
|
+
*
|
|
35
|
+
* Title-only (stamps an empty header with a close button):
|
|
36
|
+
* <drawer-ui text="Settings" side="right"></drawer-ui>
|
|
37
|
+
*
|
|
38
|
+
* API:
|
|
39
|
+
* drawer.show() · drawer.hide() · drawer.open = true|false
|
|
40
|
+
*
|
|
41
|
+
* Events:
|
|
42
|
+
* close — fired after the drawer finishes closing
|
|
43
|
+
*/
|
|
44
|
+
|
|
45
|
+
import { UIElement } from '../../core/element.js';
|
|
46
|
+
|
|
47
|
+
export class UIDrawer extends UIElement {
|
|
48
|
+
#bound = false;
|
|
49
|
+
#closing = false;
|
|
50
|
+
#previousFocus = null;
|
|
51
|
+
#closeTimer = null;
|
|
52
|
+
#dialogRef = null;
|
|
53
|
+
|
|
54
|
+
static properties = {
|
|
55
|
+
text: { type: String, default: '', reflect: true },
|
|
56
|
+
side: { type: String, default: 'right', reflect: true },
|
|
57
|
+
size: { type: String, default: 'md', reflect: true },
|
|
58
|
+
permanent: { type: Boolean, default: false, reflect: true },
|
|
59
|
+
open: { type: Boolean, default: false, reflect: true },
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
static parts = {
|
|
63
|
+
dialog: '<dialog slot="dialog"></dialog>',
|
|
64
|
+
panel: '<div slot="panel"></div>',
|
|
65
|
+
header: '<header slot="header"></header>',
|
|
66
|
+
close: '<button-ui slot="close" icon="x" variant="ghost" size="sm" aria-label="Close"></button-ui>',
|
|
67
|
+
body: '<section slot="body"></section>',
|
|
68
|
+
footer: '<footer slot="footer"></footer>',
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
// No template — drawer composes from authored light-DOM children. An empty
|
|
72
|
+
// html`` result would trigger stamp() → replaceChildren(), wiping authored
|
|
73
|
+
// [slot=header|body|footer] before render() can migrate them into the panel.
|
|
74
|
+
|
|
75
|
+
constructor() {
|
|
76
|
+
super();
|
|
77
|
+
// Safari requires <dialog>.showModal() to be invoked synchronously inside
|
|
78
|
+
// the click handler. The reactive system schedules render() in a microtask
|
|
79
|
+
// after the property change, which Safari treats as outside the user-gesture
|
|
80
|
+
// window and silently no-ops the showModal. Wrap the auto-installed `open`
|
|
81
|
+
// setter so dialog state syncs in the same synchronous frame as the
|
|
82
|
+
// assignment. See docs/BROWSER-COMPAT.md §3a (Flavor C).
|
|
83
|
+
const desc = Object.getOwnPropertyDescriptor(this, 'open');
|
|
84
|
+
if (desc?.set) {
|
|
85
|
+
const origSet = desc.set;
|
|
86
|
+
Object.defineProperty(this, 'open', {
|
|
87
|
+
get: desc.get,
|
|
88
|
+
set: (v) => {
|
|
89
|
+
origSet.call(this, v);
|
|
90
|
+
this.#syncDialog();
|
|
91
|
+
},
|
|
92
|
+
configurable: true,
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
#onPress = (e) => {
|
|
98
|
+
if (e.target.closest('[slot="close"]')) this.open = false;
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
#onDialogCancel = (e) => {
|
|
102
|
+
e.preventDefault();
|
|
103
|
+
if (!this.permanent) this.open = false;
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
#onDialogClose = () => {
|
|
107
|
+
this.open = false;
|
|
108
|
+
this.#previousFocus?.focus();
|
|
109
|
+
this.#previousFocus = null;
|
|
110
|
+
this.dispatchEvent(new Event('close', { bubbles: true }));
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
#onDialogClick = (e) => {
|
|
114
|
+
if (e.target === this.#dialogRef && !this.permanent) this.open = false;
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
connected() {
|
|
118
|
+
this.addEventListener('press', this.#onPress);
|
|
119
|
+
|
|
120
|
+
// Bind dialog listeners as soon as the dialog part is available.
|
|
121
|
+
const dialog = this.ensure('dialog');
|
|
122
|
+
if (dialog && !this.#bound) {
|
|
123
|
+
this.#bound = true;
|
|
124
|
+
this.#dialogRef = dialog;
|
|
125
|
+
dialog.addEventListener('cancel', this.#onDialogCancel);
|
|
126
|
+
dialog.addEventListener('close', this.#onDialogClose);
|
|
127
|
+
dialog.addEventListener('click', this.#onDialogClick);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
disconnected() {
|
|
132
|
+
this.removeEventListener('press', this.#onPress);
|
|
133
|
+
if (this.#dialogRef) {
|
|
134
|
+
this.#dialogRef.removeEventListener('cancel', this.#onDialogCancel);
|
|
135
|
+
this.#dialogRef.removeEventListener('close', this.#onDialogClose);
|
|
136
|
+
this.#dialogRef.removeEventListener('click', this.#onDialogClick);
|
|
137
|
+
}
|
|
138
|
+
if (this.#closeTimer != null) {
|
|
139
|
+
clearTimeout(this.#closeTimer);
|
|
140
|
+
this.#closeTimer = null;
|
|
141
|
+
}
|
|
142
|
+
this.#bound = false;
|
|
143
|
+
this.#dialogRef = null;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
#getDuration() {
|
|
147
|
+
const raw = getComputedStyle(this).getPropertyValue('--drawer-duration').trim();
|
|
148
|
+
return parseFloat(raw) || 200;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Adopt a slotted part into the panel. Accepts either an explicit
|
|
153
|
+
* [slot="X"] element OR a bare tag matching the card-ui convention
|
|
154
|
+
* (<header> → header, <section> → body, <footer> → footer). The tag is only
|
|
155
|
+
* adopted when no slot attribute is set, so authors can still opt into
|
|
156
|
+
* explicit slotting. The slot attribute is normalized onto the adopted
|
|
157
|
+
* element so downstream CSS selectors (`[slot="header"]`) match in both
|
|
158
|
+
* cases.
|
|
159
|
+
*/
|
|
160
|
+
#adoptSlot(slot, panel, mode = 'append') {
|
|
161
|
+
const fallbackTag = { header: 'header', body: 'section', footer: 'footer' }[slot];
|
|
162
|
+
const isMatch = (c) => c.getAttribute('slot') === slot
|
|
163
|
+
|| (fallbackTag && !c.getAttribute('slot') && c.localName === fallbackTag);
|
|
164
|
+
|
|
165
|
+
const inDirect = [...this.children].find(isMatch);
|
|
166
|
+
const inPanel = [...panel.children].find(c => c.getAttribute('slot') === slot);
|
|
167
|
+
|
|
168
|
+
if (inDirect && inPanel && inDirect !== inPanel) {
|
|
169
|
+
// User provided their own after we stamped a default — drop the stamp.
|
|
170
|
+
inPanel.remove();
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
const el = inDirect || inPanel || (() => {
|
|
174
|
+
const blueprint = this.constructor._pp?.[slot];
|
|
175
|
+
if (!blueprint) return null;
|
|
176
|
+
const clone = blueprint.cloneNode(true);
|
|
177
|
+
clone.setAttribute('data-stamped', '');
|
|
178
|
+
return clone;
|
|
179
|
+
})();
|
|
180
|
+
if (!el) return null;
|
|
181
|
+
|
|
182
|
+
if (el.getAttribute('slot') !== slot) el.setAttribute('slot', slot);
|
|
183
|
+
|
|
184
|
+
if (el.parentElement !== panel) {
|
|
185
|
+
if (mode === 'prepend') panel.prepend(el);
|
|
186
|
+
else panel.appendChild(el);
|
|
187
|
+
}
|
|
188
|
+
return el;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
render() {
|
|
192
|
+
const dialog = this.ensure('dialog');
|
|
193
|
+
|
|
194
|
+
if (this.text) {
|
|
195
|
+
dialog.setAttribute('aria-label', this.text);
|
|
196
|
+
this.setAttribute('aria-label', this.text);
|
|
197
|
+
} else {
|
|
198
|
+
dialog.removeAttribute('aria-label');
|
|
199
|
+
this.removeAttribute('aria-label');
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
const panel = this.ensure('panel');
|
|
203
|
+
if (panel.parentElement !== dialog) dialog.appendChild(panel);
|
|
204
|
+
|
|
205
|
+
const header = this.#adoptSlot('header', panel, 'prepend');
|
|
206
|
+
|
|
207
|
+
// text="…" shorthand: stamp a [slot="heading"] into the header so the
|
|
208
|
+
// card-ui grid picks it up. Any author-supplied heading wins.
|
|
209
|
+
if (header) {
|
|
210
|
+
let stampedHeading = header.querySelector(':scope > [slot="heading"][data-stamped]');
|
|
211
|
+
const hasAuthorHeading = [...header.children].some(c =>
|
|
212
|
+
!c.hasAttribute('data-stamped') &&
|
|
213
|
+
(c.getAttribute('slot') === 'heading' || /^h[1-6]$/.test(c.localName))
|
|
214
|
+
);
|
|
215
|
+
if (this.text && !hasAuthorHeading) {
|
|
216
|
+
if (!stampedHeading) {
|
|
217
|
+
stampedHeading = document.createElement('span');
|
|
218
|
+
stampedHeading.setAttribute('slot', 'heading');
|
|
219
|
+
stampedHeading.setAttribute('data-stamped', '');
|
|
220
|
+
header.prepend(stampedHeading);
|
|
221
|
+
}
|
|
222
|
+
if (stampedHeading.textContent !== this.text) stampedHeading.textContent = this.text;
|
|
223
|
+
} else if (stampedHeading) {
|
|
224
|
+
stampedHeading.remove();
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
if (!this.permanent) {
|
|
229
|
+
const close = this.ensure('close');
|
|
230
|
+
if (header && close.parentElement !== header) header.appendChild(close);
|
|
231
|
+
} else {
|
|
232
|
+
this.drop('close');
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// Body — multiple sections allowed (card-ui parity). Accepts either
|
|
236
|
+
// bare <section> tags or explicit [slot="body"]. If the author provides
|
|
237
|
+
// none, stamp an empty default so title-only drawers still lay out.
|
|
238
|
+
const authorSections = [...this.children].filter(c =>
|
|
239
|
+
c.getAttribute('slot') === 'body'
|
|
240
|
+
|| (c.localName === 'section' && !c.getAttribute('slot'))
|
|
241
|
+
);
|
|
242
|
+
if (authorSections.length === 0) {
|
|
243
|
+
this.#adoptSlot('body', panel, 'append');
|
|
244
|
+
} else {
|
|
245
|
+
for (const s of authorSections) {
|
|
246
|
+
if (s.getAttribute('slot') !== 'body') s.setAttribute('slot', 'body');
|
|
247
|
+
if (s.parentElement !== panel) panel.appendChild(s);
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// Footer is opt-in — only mounted if the author supplied one (either
|
|
252
|
+
// [slot="footer"] or a bare <footer> tag, card-ui style).
|
|
253
|
+
const userFooter = [...this.children].find(c =>
|
|
254
|
+
c.getAttribute('slot') === 'footer' || (!c.getAttribute('slot') && c.localName === 'footer')
|
|
255
|
+
);
|
|
256
|
+
if (userFooter) {
|
|
257
|
+
if (userFooter.getAttribute('slot') !== 'footer') userFooter.setAttribute('slot', 'footer');
|
|
258
|
+
if (userFooter.parentElement !== panel) panel.appendChild(userFooter);
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
// Sync open state — also syncs synchronously from the `open` setter
|
|
262
|
+
// (see constructor) so showModal() runs in the click handler's gesture
|
|
263
|
+
// frame on Safari.
|
|
264
|
+
this.#syncDialog();
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
#syncDialog() {
|
|
268
|
+
const dialog = this.#dialogRef;
|
|
269
|
+
if (!dialog) return;
|
|
270
|
+
if (this.open && !dialog.open) {
|
|
271
|
+
this.#closing = false;
|
|
272
|
+
this.#previousFocus = document.activeElement;
|
|
273
|
+
dialog.showModal();
|
|
274
|
+
// Synchronous reflow instead of rAF — Safari throttles
|
|
275
|
+
// requestAnimationFrame when a top-layer dialog is open, sometimes
|
|
276
|
+
// delaying [data-open] (and the slide-in transition) by tens of
|
|
277
|
+
// seconds. Forcing a reflow keeps the animation start in the same
|
|
278
|
+
// synchronous frame. See docs/BROWSER-COMPAT.md §3a (Flavor C).
|
|
279
|
+
void dialog.offsetHeight;
|
|
280
|
+
dialog.setAttribute('data-open', '');
|
|
281
|
+
} else if (!this.open && dialog.open && !this.#closing) {
|
|
282
|
+
this.#animateClose(dialog);
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
#animateClose(dialog) {
|
|
287
|
+
this.#closing = true;
|
|
288
|
+
// Set [data-closing] FIRST (carries the transition spec), force a
|
|
289
|
+
// reflow, THEN remove [data-open]. If both attribute changes batch
|
|
290
|
+
// into a single style update, Safari can skip the slide-out animation
|
|
291
|
+
// entirely.
|
|
292
|
+
dialog.setAttribute('data-closing', '');
|
|
293
|
+
void dialog.offsetHeight;
|
|
294
|
+
dialog.removeAttribute('data-open');
|
|
295
|
+
|
|
296
|
+
this.#closeTimer = setTimeout(() => {
|
|
297
|
+
this.#closeTimer = null;
|
|
298
|
+
this.#closing = false;
|
|
299
|
+
dialog.removeAttribute('data-closing');
|
|
300
|
+
if (dialog.open) dialog.close();
|
|
301
|
+
}, this.#getDuration());
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
show() { this.open = true; }
|
|
305
|
+
hide() { this.open = false; }
|
|
306
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `<drawer-ui>` — Slide-out panel anchored to a viewport edge. Uses native <dialog> for focus trap, Escape dismiss, and ::backdrop. Panel is a flex column that scrolls as a whole — the header and footer stick to the top/bottom of the panel when the body overflows. Authors can compose content with either card-ui-style bare tags (<header>, <section>, <footer>) or explicit [slot="header|body|footer"] attributes; multiple <section> siblings are allowed and stack in author order between the sticky header and footer. The header activates a card-ui-aligned 3-column grid (icon | heading+description | action+close) when any direct [slot="icon|heading|description|action"] child is present.
|
|
3
|
+
*
|
|
4
|
+
* @see https://ui-kit.exe.xyz/site/components/drawer
|
|
5
|
+
*
|
|
6
|
+
* Type declarations generated by scripts/build/dts-codegen.mjs from
|
|
7
|
+
* the component's `.a2ui.json` sidecar. Edit the source `.yaml`,
|
|
8
|
+
* run `npm run components`, then `npm run codegen:dts` to regenerate;
|
|
9
|
+
* or hand-author this file fully if rich event types are needed.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { UIElement } from '../../core/element.js';
|
|
13
|
+
|
|
14
|
+
export class UIDrawer extends UIElement {
|
|
15
|
+
/** Controls visibility. When false, backdrop and panel are removed from DOM. */
|
|
16
|
+
open: boolean;
|
|
17
|
+
/** When true, hide close button and suppress backdrop/Escape dismiss */
|
|
18
|
+
permanent: boolean;
|
|
19
|
+
/** Anchor side */
|
|
20
|
+
side: 'left' | 'right' | 'top' | 'bottom';
|
|
21
|
+
/** Panel width/height */
|
|
22
|
+
size: 'sm' | 'md' | 'lg';
|
|
23
|
+
/** Title text displayed in the drawer header */
|
|
24
|
+
text: string;
|
|
25
|
+
}
|
|
@@ -1,296 +1,17 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* `<drawer-ui>` — auto-registers the tag on import.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* Exit animation plays before dialog.close() via [data-closing].
|
|
4
|
+
* For non-side-effect class import (test isolation, tag override), use
|
|
5
|
+
* the `class` subpath:
|
|
7
6
|
*
|
|
8
|
-
*
|
|
9
|
-
* <drawer-ui side="right" size="md">
|
|
10
|
-
* <header>Title block</header>
|
|
11
|
-
* <section>Body content</section>
|
|
12
|
-
* <footer>Action buttons</footer>
|
|
13
|
-
* </drawer-ui>
|
|
7
|
+
* import { UIDrawer } from '@adia-ai/web-components/components/drawer/class';
|
|
14
8
|
*
|
|
15
|
-
*
|
|
16
|
-
* <drawer-ui>
|
|
17
|
-
* <header slot="header">…</header>
|
|
18
|
-
* <section slot="body">…</section>
|
|
19
|
-
* <footer slot="footer">…</footer>
|
|
20
|
-
* </drawer-ui>
|
|
21
|
-
*
|
|
22
|
-
* Title-only (stamps an empty header with a close button):
|
|
23
|
-
* <drawer-ui text="Settings" side="right"></drawer-ui>
|
|
24
|
-
*
|
|
25
|
-
* API:
|
|
26
|
-
* drawer.show() · drawer.hide() · drawer.open = true|false
|
|
27
|
-
*
|
|
28
|
-
* Events:
|
|
29
|
-
* close — fired after the drawer finishes closing
|
|
9
|
+
* @see ../../USAGE.md#registration--auto-vs-explicit
|
|
30
10
|
*/
|
|
31
11
|
|
|
32
|
-
import {
|
|
33
|
-
|
|
34
|
-
class UIDrawer extends UIElement {
|
|
35
|
-
#bound = false;
|
|
36
|
-
#closing = false;
|
|
37
|
-
#previousFocus = null;
|
|
38
|
-
#closeTimer = null;
|
|
39
|
-
#dialogRef = null;
|
|
40
|
-
|
|
41
|
-
static properties = {
|
|
42
|
-
text: { type: String, default: '', reflect: true },
|
|
43
|
-
side: { type: String, default: 'right', reflect: true },
|
|
44
|
-
size: { type: String, default: 'md', reflect: true },
|
|
45
|
-
permanent: { type: Boolean, default: false, reflect: true },
|
|
46
|
-
open: { type: Boolean, default: false, reflect: true },
|
|
47
|
-
};
|
|
48
|
-
|
|
49
|
-
static parts = {
|
|
50
|
-
dialog: '<dialog slot="dialog"></dialog>',
|
|
51
|
-
panel: '<div slot="panel"></div>',
|
|
52
|
-
header: '<header slot="header"></header>',
|
|
53
|
-
close: '<button-ui slot="close" icon="x" variant="ghost" size="sm" aria-label="Close"></button-ui>',
|
|
54
|
-
body: '<section slot="body"></section>',
|
|
55
|
-
footer: '<footer slot="footer"></footer>',
|
|
56
|
-
};
|
|
57
|
-
|
|
58
|
-
// No template — drawer composes from authored light-DOM children. An empty
|
|
59
|
-
// html`` result would trigger stamp() → replaceChildren(), wiping authored
|
|
60
|
-
// [slot=header|body|footer] before render() can migrate them into the panel.
|
|
61
|
-
|
|
62
|
-
constructor() {
|
|
63
|
-
super();
|
|
64
|
-
// Safari requires <dialog>.showModal() to be invoked synchronously inside
|
|
65
|
-
// the click handler. The reactive system schedules render() in a microtask
|
|
66
|
-
// after the property change, which Safari treats as outside the user-gesture
|
|
67
|
-
// window and silently no-ops the showModal. Wrap the auto-installed `open`
|
|
68
|
-
// setter so dialog state syncs in the same synchronous frame as the
|
|
69
|
-
// assignment. See docs/BROWSER-COMPAT.md §3a (Flavor C).
|
|
70
|
-
const desc = Object.getOwnPropertyDescriptor(this, 'open');
|
|
71
|
-
if (desc?.set) {
|
|
72
|
-
const origSet = desc.set;
|
|
73
|
-
Object.defineProperty(this, 'open', {
|
|
74
|
-
get: desc.get,
|
|
75
|
-
set: (v) => {
|
|
76
|
-
origSet.call(this, v);
|
|
77
|
-
this.#syncDialog();
|
|
78
|
-
},
|
|
79
|
-
configurable: true,
|
|
80
|
-
});
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
#onPress = (e) => {
|
|
85
|
-
if (e.target.closest('[slot="close"]')) this.open = false;
|
|
86
|
-
};
|
|
87
|
-
|
|
88
|
-
#onDialogCancel = (e) => {
|
|
89
|
-
e.preventDefault();
|
|
90
|
-
if (!this.permanent) this.open = false;
|
|
91
|
-
};
|
|
92
|
-
|
|
93
|
-
#onDialogClose = () => {
|
|
94
|
-
this.open = false;
|
|
95
|
-
this.#previousFocus?.focus();
|
|
96
|
-
this.#previousFocus = null;
|
|
97
|
-
this.dispatchEvent(new Event('close', { bubbles: true }));
|
|
98
|
-
};
|
|
99
|
-
|
|
100
|
-
#onDialogClick = (e) => {
|
|
101
|
-
if (e.target === this.#dialogRef && !this.permanent) this.open = false;
|
|
102
|
-
};
|
|
103
|
-
|
|
104
|
-
connected() {
|
|
105
|
-
this.addEventListener('press', this.#onPress);
|
|
106
|
-
|
|
107
|
-
// Bind dialog listeners as soon as the dialog part is available.
|
|
108
|
-
const dialog = this.ensure('dialog');
|
|
109
|
-
if (dialog && !this.#bound) {
|
|
110
|
-
this.#bound = true;
|
|
111
|
-
this.#dialogRef = dialog;
|
|
112
|
-
dialog.addEventListener('cancel', this.#onDialogCancel);
|
|
113
|
-
dialog.addEventListener('close', this.#onDialogClose);
|
|
114
|
-
dialog.addEventListener('click', this.#onDialogClick);
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
disconnected() {
|
|
119
|
-
this.removeEventListener('press', this.#onPress);
|
|
120
|
-
if (this.#dialogRef) {
|
|
121
|
-
this.#dialogRef.removeEventListener('cancel', this.#onDialogCancel);
|
|
122
|
-
this.#dialogRef.removeEventListener('close', this.#onDialogClose);
|
|
123
|
-
this.#dialogRef.removeEventListener('click', this.#onDialogClick);
|
|
124
|
-
}
|
|
125
|
-
if (this.#closeTimer != null) {
|
|
126
|
-
clearTimeout(this.#closeTimer);
|
|
127
|
-
this.#closeTimer = null;
|
|
128
|
-
}
|
|
129
|
-
this.#bound = false;
|
|
130
|
-
this.#dialogRef = null;
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
#getDuration() {
|
|
134
|
-
const raw = getComputedStyle(this).getPropertyValue('--drawer-duration').trim();
|
|
135
|
-
return parseFloat(raw) || 200;
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
/**
|
|
139
|
-
* Adopt a slotted part into the panel. Accepts either an explicit
|
|
140
|
-
* [slot="X"] element OR a bare tag matching the card-ui convention
|
|
141
|
-
* (<header> → header, <section> → body, <footer> → footer). The tag is only
|
|
142
|
-
* adopted when no slot attribute is set, so authors can still opt into
|
|
143
|
-
* explicit slotting. The slot attribute is normalized onto the adopted
|
|
144
|
-
* element so downstream CSS selectors (`[slot="header"]`) match in both
|
|
145
|
-
* cases.
|
|
146
|
-
*/
|
|
147
|
-
#adoptSlot(slot, panel, mode = 'append') {
|
|
148
|
-
const fallbackTag = { header: 'header', body: 'section', footer: 'footer' }[slot];
|
|
149
|
-
const isMatch = (c) => c.getAttribute('slot') === slot
|
|
150
|
-
|| (fallbackTag && !c.getAttribute('slot') && c.localName === fallbackTag);
|
|
151
|
-
|
|
152
|
-
const inDirect = [...this.children].find(isMatch);
|
|
153
|
-
const inPanel = [...panel.children].find(c => c.getAttribute('slot') === slot);
|
|
154
|
-
|
|
155
|
-
if (inDirect && inPanel && inDirect !== inPanel) {
|
|
156
|
-
// User provided their own after we stamped a default — drop the stamp.
|
|
157
|
-
inPanel.remove();
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
const el = inDirect || inPanel || (() => {
|
|
161
|
-
const blueprint = this.constructor._pp?.[slot];
|
|
162
|
-
if (!blueprint) return null;
|
|
163
|
-
const clone = blueprint.cloneNode(true);
|
|
164
|
-
clone.setAttribute('data-stamped', '');
|
|
165
|
-
return clone;
|
|
166
|
-
})();
|
|
167
|
-
if (!el) return null;
|
|
168
|
-
|
|
169
|
-
if (el.getAttribute('slot') !== slot) el.setAttribute('slot', slot);
|
|
170
|
-
|
|
171
|
-
if (el.parentElement !== panel) {
|
|
172
|
-
if (mode === 'prepend') panel.prepend(el);
|
|
173
|
-
else panel.appendChild(el);
|
|
174
|
-
}
|
|
175
|
-
return el;
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
render() {
|
|
179
|
-
const dialog = this.ensure('dialog');
|
|
180
|
-
|
|
181
|
-
if (this.text) {
|
|
182
|
-
dialog.setAttribute('aria-label', this.text);
|
|
183
|
-
this.setAttribute('aria-label', this.text);
|
|
184
|
-
} else {
|
|
185
|
-
dialog.removeAttribute('aria-label');
|
|
186
|
-
this.removeAttribute('aria-label');
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
const panel = this.ensure('panel');
|
|
190
|
-
if (panel.parentElement !== dialog) dialog.appendChild(panel);
|
|
191
|
-
|
|
192
|
-
const header = this.#adoptSlot('header', panel, 'prepend');
|
|
193
|
-
|
|
194
|
-
// text="…" shorthand: stamp a [slot="heading"] into the header so the
|
|
195
|
-
// card-ui grid picks it up. Any author-supplied heading wins.
|
|
196
|
-
if (header) {
|
|
197
|
-
let stampedHeading = header.querySelector(':scope > [slot="heading"][data-stamped]');
|
|
198
|
-
const hasAuthorHeading = [...header.children].some(c =>
|
|
199
|
-
!c.hasAttribute('data-stamped') &&
|
|
200
|
-
(c.getAttribute('slot') === 'heading' || /^h[1-6]$/.test(c.localName))
|
|
201
|
-
);
|
|
202
|
-
if (this.text && !hasAuthorHeading) {
|
|
203
|
-
if (!stampedHeading) {
|
|
204
|
-
stampedHeading = document.createElement('span');
|
|
205
|
-
stampedHeading.setAttribute('slot', 'heading');
|
|
206
|
-
stampedHeading.setAttribute('data-stamped', '');
|
|
207
|
-
header.prepend(stampedHeading);
|
|
208
|
-
}
|
|
209
|
-
if (stampedHeading.textContent !== this.text) stampedHeading.textContent = this.text;
|
|
210
|
-
} else if (stampedHeading) {
|
|
211
|
-
stampedHeading.remove();
|
|
212
|
-
}
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
if (!this.permanent) {
|
|
216
|
-
const close = this.ensure('close');
|
|
217
|
-
if (header && close.parentElement !== header) header.appendChild(close);
|
|
218
|
-
} else {
|
|
219
|
-
this.drop('close');
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
// Body — multiple sections allowed (card-ui parity). Accepts either
|
|
223
|
-
// bare <section> tags or explicit [slot="body"]. If the author provides
|
|
224
|
-
// none, stamp an empty default so title-only drawers still lay out.
|
|
225
|
-
const authorSections = [...this.children].filter(c =>
|
|
226
|
-
c.getAttribute('slot') === 'body'
|
|
227
|
-
|| (c.localName === 'section' && !c.getAttribute('slot'))
|
|
228
|
-
);
|
|
229
|
-
if (authorSections.length === 0) {
|
|
230
|
-
this.#adoptSlot('body', panel, 'append');
|
|
231
|
-
} else {
|
|
232
|
-
for (const s of authorSections) {
|
|
233
|
-
if (s.getAttribute('slot') !== 'body') s.setAttribute('slot', 'body');
|
|
234
|
-
if (s.parentElement !== panel) panel.appendChild(s);
|
|
235
|
-
}
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
// Footer is opt-in — only mounted if the author supplied one (either
|
|
239
|
-
// [slot="footer"] or a bare <footer> tag, card-ui style).
|
|
240
|
-
const userFooter = [...this.children].find(c =>
|
|
241
|
-
c.getAttribute('slot') === 'footer' || (!c.getAttribute('slot') && c.localName === 'footer')
|
|
242
|
-
);
|
|
243
|
-
if (userFooter) {
|
|
244
|
-
if (userFooter.getAttribute('slot') !== 'footer') userFooter.setAttribute('slot', 'footer');
|
|
245
|
-
if (userFooter.parentElement !== panel) panel.appendChild(userFooter);
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
// Sync open state — also syncs synchronously from the `open` setter
|
|
249
|
-
// (see constructor) so showModal() runs in the click handler's gesture
|
|
250
|
-
// frame on Safari.
|
|
251
|
-
this.#syncDialog();
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
#syncDialog() {
|
|
255
|
-
const dialog = this.#dialogRef;
|
|
256
|
-
if (!dialog) return;
|
|
257
|
-
if (this.open && !dialog.open) {
|
|
258
|
-
this.#closing = false;
|
|
259
|
-
this.#previousFocus = document.activeElement;
|
|
260
|
-
dialog.showModal();
|
|
261
|
-
// Synchronous reflow instead of rAF — Safari throttles
|
|
262
|
-
// requestAnimationFrame when a top-layer dialog is open, sometimes
|
|
263
|
-
// delaying [data-open] (and the slide-in transition) by tens of
|
|
264
|
-
// seconds. Forcing a reflow keeps the animation start in the same
|
|
265
|
-
// synchronous frame. See docs/BROWSER-COMPAT.md §3a (Flavor C).
|
|
266
|
-
void dialog.offsetHeight;
|
|
267
|
-
dialog.setAttribute('data-open', '');
|
|
268
|
-
} else if (!this.open && dialog.open && !this.#closing) {
|
|
269
|
-
this.#animateClose(dialog);
|
|
270
|
-
}
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
#animateClose(dialog) {
|
|
274
|
-
this.#closing = true;
|
|
275
|
-
// Set [data-closing] FIRST (carries the transition spec), force a
|
|
276
|
-
// reflow, THEN remove [data-open]. If both attribute changes batch
|
|
277
|
-
// into a single style update, Safari can skip the slide-out animation
|
|
278
|
-
// entirely.
|
|
279
|
-
dialog.setAttribute('data-closing', '');
|
|
280
|
-
void dialog.offsetHeight;
|
|
281
|
-
dialog.removeAttribute('data-open');
|
|
282
|
-
|
|
283
|
-
this.#closeTimer = setTimeout(() => {
|
|
284
|
-
this.#closeTimer = null;
|
|
285
|
-
this.#closing = false;
|
|
286
|
-
dialog.removeAttribute('data-closing');
|
|
287
|
-
if (dialog.open) dialog.close();
|
|
288
|
-
}, this.#getDuration());
|
|
289
|
-
}
|
|
12
|
+
import { defineIfFree } from '../../core/register.js';
|
|
13
|
+
import { UIDrawer } from './class.js';
|
|
290
14
|
|
|
291
|
-
|
|
292
|
-
hide() { this.open = false; }
|
|
293
|
-
}
|
|
294
|
-
customElements.define('drawer-ui', UIDrawer);
|
|
15
|
+
defineIfFree('drawer-ui', UIDrawer);
|
|
295
16
|
|
|
296
17
|
export { UIDrawer };
|