@mdbase-dev/ui 0.1.0-beta.126 → 0.1.0-beta.148
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 +168 -0
- package/css/collections.css +978 -0
- package/css/controls.css +33 -1
- package/css/screens.css +44 -2
- package/dist/AppSwitcher.js +187 -33
- package/dist/Checkbox.d.ts +14 -0
- package/dist/Checkbox.js +32 -0
- package/dist/CollectionModeControl.d.ts +11 -0
- package/dist/CollectionModeControl.js +290 -0
- package/dist/CommandPalette.d.ts +4 -2
- package/dist/CommandPalette.js +275 -103
- package/dist/Dialog.js +72 -25
- package/dist/Feedback.js +763 -229
- package/dist/FeedbackMarkup.d.ts +1 -1
- package/dist/FeedbackMarkup.js +326 -125
- package/dist/FeedbackVerification.js +115 -68
- package/dist/SaveNotice.js +71 -28
- package/dist/Select.js +183 -135
- package/dist/SelectList.js +59 -14
- package/dist/ThemeSelect.js +55 -4
- package/dist/apps.d.ts +1 -0
- package/dist/apps.js +50 -29
- package/dist/brand.js +251 -68
- package/dist/codemirror.js +116 -87
- package/dist/collections/CollectionGate.d.ts +52 -0
- package/dist/collections/CollectionGate.js +456 -0
- package/dist/collections/CollectionSaveWarning.d.ts +10 -0
- package/dist/collections/CollectionSaveWarning.js +28 -0
- package/dist/collections/CollectionSwitcher.d.ts +51 -0
- package/dist/collections/CollectionSwitcher.js +598 -0
- package/dist/collections/SetupReview.d.ts +9 -0
- package/dist/collections/SetupReview.js +216 -0
- package/dist/collections/copy.d.ts +100 -0
- package/dist/collections/copy.js +937 -0
- package/dist/collections/dialogs.d.ts +27 -0
- package/dist/collections/dialogs.js +169 -0
- package/dist/collections/model.d.ts +146 -0
- package/dist/collections/model.js +337 -0
- package/dist/collections/mount.d.ts +19 -0
- package/dist/collections/mount.js +56 -0
- package/dist/collections/parts.d.ts +86 -0
- package/dist/collections/parts.js +439 -0
- package/dist/collections/testing.d.ts +19 -0
- package/dist/collections/testing.js +39 -0
- package/dist/collections/use-collection-ui.d.ts +140 -0
- package/dist/collections/use-collection-ui.js +236 -0
- package/dist/collections.d.ts +19 -0
- package/dist/collections.js +13 -0
- package/dist/feedback-data.d.ts +13 -12
- package/dist/feedback-data.js +365 -168
- package/dist/flock.d.ts +63 -0
- package/dist/flock.js +140 -0
- package/dist/i18n.d.ts +6 -0
- package/dist/i18n.js +71 -0
- package/dist/locales/de/messages.json +1 -0
- package/dist/locales/en/messages.json +1 -0
- package/dist/locales/es/messages.json +1 -0
- package/dist/locales/fr/messages.json +1 -0
- package/dist/locales/it/messages.json +1 -0
- package/dist/locales/ja/messages.json +1 -0
- package/dist/locales/ko/messages.json +1 -0
- package/dist/locales/pt/messages.json +1 -0
- package/dist/locales/ru/messages.json +1 -0
- package/dist/locales/zh/messages.json +1 -0
- package/dist/mark-activity.js +82 -75
- package/dist/permission-selection.js +27 -17
- package/dist/popover.js +138 -130
- package/dist/screens.d.ts +20 -3
- package/dist/screens.js +275 -6
- package/dist/select-model.js +34 -28
- package/dist/text-diff.d.ts +1 -0
- package/dist/text-diff.js +106 -74
- package/dist/theme.js +34 -40
- package/package.json +56 -7
|
@@ -0,0 +1,598 @@
|
|
|
1
|
+
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useId, useLayoutEffect, useEffect, useRef, useState, useSyncExternalStore } from "react";
|
|
3
|
+
import { anchoredPlacement } from "../popover.js";
|
|
4
|
+
import { RowDialogs, SignOutDialog } from "./dialogs.js";
|
|
5
|
+
import { AccountLine, ActionButton, CollectionMark, ItemCopy, ItemMore, NoticeBlock, PrivateMark, RowCopy, RowMore } from "./parts.js";
|
|
6
|
+
import { useCollectionUi } from "./use-collection-ui.js";
|
|
7
|
+
/** Collections worth a filter box; fewer fit at a glance. */
|
|
8
|
+
const FILTER_THRESHOLD = 7;
|
|
9
|
+
const NARROW = "(max-width: 639.98px)";
|
|
10
|
+
/**
|
|
11
|
+
* The in-app collection switcher: a trigger that opens the approved collections
|
|
12
|
+
* (one click switches, no Connect window), "Add a collection…", "Manage access"
|
|
13
|
+
* and the account. A popover on wide screens, a bottom sheet on narrow ones.
|
|
14
|
+
*
|
|
15
|
+
* Pass `open` and `onOpenChange` to control it. With `variant="none"` it has no
|
|
16
|
+
* trigger, so an app can open it from its own control and pass that control's
|
|
17
|
+
* element (or the menu button that held it) as `anchorRef`.
|
|
18
|
+
*/
|
|
19
|
+
export function CollectionSwitcher({
|
|
20
|
+
variant = "full",
|
|
21
|
+
className,
|
|
22
|
+
icon,
|
|
23
|
+
open: openProp,
|
|
24
|
+
onOpenChange,
|
|
25
|
+
anchorRef,
|
|
26
|
+
...options
|
|
27
|
+
}) {
|
|
28
|
+
const ui = useCollectionUi(options);
|
|
29
|
+
const [ownOpen, setOwnOpen] = useState(false);
|
|
30
|
+
const open = openProp ?? ownOpen;
|
|
31
|
+
const setOpen = next => {
|
|
32
|
+
if (next === open) return;
|
|
33
|
+
if (openProp === undefined) setOwnOpen(next);
|
|
34
|
+
onOpenChange?.(next);
|
|
35
|
+
};
|
|
36
|
+
const [dialog, setDialog] = useState(null);
|
|
37
|
+
const trigger = useRef(null);
|
|
38
|
+
// The element the panel is placed against and hands focus back to.
|
|
39
|
+
const anchor = anchorRef ?? trigger;
|
|
40
|
+
const panelId = useId();
|
|
41
|
+
const narrow = useNarrow();
|
|
42
|
+
const {
|
|
43
|
+
view,
|
|
44
|
+
copy,
|
|
45
|
+
say,
|
|
46
|
+
busy
|
|
47
|
+
} = ui;
|
|
48
|
+
const app = ui.appOpen?.item ?? null;
|
|
49
|
+
const current = app ? null : view.current;
|
|
50
|
+
const currentName = app?.name ?? current?.name ?? null;
|
|
51
|
+
const name = currentName ?? copy.switchCollectionNone;
|
|
52
|
+
const markOf = app ?? current;
|
|
53
|
+
const leading = icon === "mark" ? markOf ? _jsx(CollectionMark, {
|
|
54
|
+
row: markOf
|
|
55
|
+
}) : _jsx(CollectionsGlyph, {}) : icon;
|
|
56
|
+
const close = refocus => {
|
|
57
|
+
setOpen(false);
|
|
58
|
+
if (refocus) anchor.current?.focus();
|
|
59
|
+
};
|
|
60
|
+
const toDialog = next => {
|
|
61
|
+
setOpen(false);
|
|
62
|
+
setDialog(next);
|
|
63
|
+
};
|
|
64
|
+
// The popover that held the row is gone: a closed row dialog hands focus to the anchor.
|
|
65
|
+
const returnFocus = useRef(false);
|
|
66
|
+
useEffect(() => {
|
|
67
|
+
if (dialog || !returnFocus.current) return;
|
|
68
|
+
returnFocus.current = false;
|
|
69
|
+
const active = document.activeElement;
|
|
70
|
+
if (!active || active === document.body) anchor.current?.focus();
|
|
71
|
+
}, [dialog, anchor]);
|
|
72
|
+
const content = _jsx(SwitcherContent, {
|
|
73
|
+
ui: ui,
|
|
74
|
+
onDone: () => close(false),
|
|
75
|
+
onDialog: toDialog
|
|
76
|
+
});
|
|
77
|
+
return _jsxs("span", {
|
|
78
|
+
className: ["mdbase-collections", "mdbase-collections-switcher", className].filter(Boolean).join(" "),
|
|
79
|
+
"data-variant": variant,
|
|
80
|
+
children: [variant !== "none" && _jsxs("button", {
|
|
81
|
+
ref: trigger,
|
|
82
|
+
type: "button",
|
|
83
|
+
className: "mdbase-collections-trigger",
|
|
84
|
+
"aria-haspopup": "dialog",
|
|
85
|
+
"aria-expanded": open,
|
|
86
|
+
"aria-controls": open ? panelId : undefined,
|
|
87
|
+
"aria-label": currentName ? say("switchCollection", {
|
|
88
|
+
collection: currentName
|
|
89
|
+
}) : copy.switchCollectionNone,
|
|
90
|
+
title: name,
|
|
91
|
+
onClick: () => setOpen(!open),
|
|
92
|
+
children: [busy === "select" || busy === "add" || view.kind === "opening" ? _jsx("span", {
|
|
93
|
+
className: "mdbase-collections-spinner",
|
|
94
|
+
"aria-hidden": "true"
|
|
95
|
+
}) : leading !== undefined && leading !== null ? _jsx("span", {
|
|
96
|
+
className: "mdbase-collections-trigger-icon",
|
|
97
|
+
"aria-hidden": "true",
|
|
98
|
+
children: leading
|
|
99
|
+
}) : variant === "icon" ? _jsx(CollectionsGlyph, {}) : null, variant !== "icon" && _jsx("span", {
|
|
100
|
+
className: "mdbase-collections-trigger-name",
|
|
101
|
+
children: name
|
|
102
|
+
}), variant === "full" && current && _jsx("span", {
|
|
103
|
+
className: "mdbase-collections-trigger-role",
|
|
104
|
+
children: current.roleLabel
|
|
105
|
+
}), _jsx("svg", {
|
|
106
|
+
className: "mdbase-collections-chevron",
|
|
107
|
+
viewBox: "0 0 16 16",
|
|
108
|
+
"aria-hidden": "true",
|
|
109
|
+
children: _jsx("path", {
|
|
110
|
+
d: "m4 6 4 4 4-4"
|
|
111
|
+
})
|
|
112
|
+
})]
|
|
113
|
+
}), open && (narrow ? _jsx(SwitcherSheet, {
|
|
114
|
+
id: panelId,
|
|
115
|
+
ui: ui,
|
|
116
|
+
onClose: close,
|
|
117
|
+
children: content
|
|
118
|
+
}) : _jsx(SwitcherPopover, {
|
|
119
|
+
id: panelId,
|
|
120
|
+
ui: ui,
|
|
121
|
+
anchorRef: anchor,
|
|
122
|
+
onClose: close,
|
|
123
|
+
children: content
|
|
124
|
+
})), _jsx(RowDialogs, {
|
|
125
|
+
ui: ui,
|
|
126
|
+
dialog: dialog,
|
|
127
|
+
onClose: () => {
|
|
128
|
+
returnFocus.current = true;
|
|
129
|
+
setDialog(null);
|
|
130
|
+
}
|
|
131
|
+
}), _jsx(SignOutDialog, {
|
|
132
|
+
ui: ui
|
|
133
|
+
})]
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
/** The switcher's list and actions, also shown inline by `CollectionPanel`. */
|
|
137
|
+
export function SwitcherContent({
|
|
138
|
+
ui,
|
|
139
|
+
onDone,
|
|
140
|
+
onDialog
|
|
141
|
+
}) {
|
|
142
|
+
const {
|
|
143
|
+
view,
|
|
144
|
+
copy,
|
|
145
|
+
say,
|
|
146
|
+
busy,
|
|
147
|
+
actions
|
|
148
|
+
} = ui;
|
|
149
|
+
const [query, setQuery] = useState("");
|
|
150
|
+
const [focused, setFocused] = useState(null);
|
|
151
|
+
const lists = useRef(null);
|
|
152
|
+
const listLabel = useId();
|
|
153
|
+
const app = ui.appOpen;
|
|
154
|
+
const sections = (ui.options.sections ?? []).filter(section => section.items.length > 0 || section.empty);
|
|
155
|
+
const needle = query.trim().toLocaleLowerCase();
|
|
156
|
+
const matches = name => !needle || name.toLocaleLowerCase().includes(needle);
|
|
157
|
+
const total = view.collections.length + sections.reduce((sum, section) => sum + section.items.length, 0);
|
|
158
|
+
const signIn = view.signedIn ? null : _jsxs(_Fragment, {
|
|
159
|
+
children: [_jsx("p", {
|
|
160
|
+
className: "mdbase-collections-lead",
|
|
161
|
+
children: say("signedOutLead")
|
|
162
|
+
}), _jsx("div", {
|
|
163
|
+
className: "mdbase-collections-actions",
|
|
164
|
+
children: _jsx(ActionButton, {
|
|
165
|
+
tone: "primary",
|
|
166
|
+
progress: busy === "sign_in" ? copy.addingCollection : null,
|
|
167
|
+
inert: busy !== null,
|
|
168
|
+
onClick: () => {
|
|
169
|
+
actions.signIn();
|
|
170
|
+
onDone();
|
|
171
|
+
},
|
|
172
|
+
children: copy.signIn
|
|
173
|
+
})
|
|
174
|
+
})]
|
|
175
|
+
});
|
|
176
|
+
const appSections = sections.map(section => _jsx(SwitcherSection, {
|
|
177
|
+
ui: ui,
|
|
178
|
+
section: section,
|
|
179
|
+
matches: matches,
|
|
180
|
+
focused: focused,
|
|
181
|
+
onFocusItem: setFocused,
|
|
182
|
+
onDone: onDone,
|
|
183
|
+
onDialog: onDialog
|
|
184
|
+
}, section.id));
|
|
185
|
+
if (!view.signedIn) {
|
|
186
|
+
return _jsxs("div", {
|
|
187
|
+
className: "mdbase-collections-switcher-content",
|
|
188
|
+
children: [signIn, appSections.length > 0 && _jsx("div", {
|
|
189
|
+
ref: lists,
|
|
190
|
+
className: "mdbase-collections-switcher-lists",
|
|
191
|
+
onKeyDown: moveAmongRadios,
|
|
192
|
+
children: appSections
|
|
193
|
+
})]
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
const rows = view.collections.filter(row => matches(row.name));
|
|
197
|
+
const isCurrent = row => row.current && !app;
|
|
198
|
+
const sdkKey = row => `sdk:${row.id}`;
|
|
199
|
+
const rovingId = rows.some(row => sdkKey(row) === focused) ? focused : rows.find(isCurrent) ?? rows[0] ? sdkKey(rows.find(isCurrent) ?? rows[0]) : null;
|
|
200
|
+
const shown = rows.length + sections.reduce((sum, section) => sum + section.items.filter(item => matches(item.name)).length, 0);
|
|
201
|
+
return _jsxs("div", {
|
|
202
|
+
className: "mdbase-collections-switcher-content",
|
|
203
|
+
children: [_jsxs("div", {
|
|
204
|
+
className: "mdbase-collections-switcher-header",
|
|
205
|
+
children: [_jsx("span", {
|
|
206
|
+
className: "mdbase-collections-eyebrow",
|
|
207
|
+
children: copy.collectionLabel
|
|
208
|
+
}), app ? _jsxs("span", {
|
|
209
|
+
className: "mdbase-collections-switcher-current",
|
|
210
|
+
children: [_jsx("strong", {
|
|
211
|
+
children: app.item.name
|
|
212
|
+
}), app.item.detail && _jsx("span", {
|
|
213
|
+
className: "mdbase-collections-row-meta",
|
|
214
|
+
children: app.item.detail
|
|
215
|
+
})]
|
|
216
|
+
}) : view.current ? _jsxs("span", {
|
|
217
|
+
className: "mdbase-collections-switcher-current",
|
|
218
|
+
children: [_jsx("strong", {
|
|
219
|
+
children: view.current.name
|
|
220
|
+
}), view.current.private && _jsx(PrivateMark, {
|
|
221
|
+
ui: ui
|
|
222
|
+
}), _jsx("span", {
|
|
223
|
+
className: "mdbase-collections-chip",
|
|
224
|
+
children: view.current.roleLabel
|
|
225
|
+
}), view.current.status && _jsx("span", {
|
|
226
|
+
className: "mdbase-collections-row-meta",
|
|
227
|
+
children: view.current.status
|
|
228
|
+
})]
|
|
229
|
+
}) : _jsx("span", {
|
|
230
|
+
className: "mdbase-collections-row-meta",
|
|
231
|
+
children: view.heading
|
|
232
|
+
})]
|
|
233
|
+
}), total >= FILTER_THRESHOLD && _jsx("input", {
|
|
234
|
+
className: "mdbase-collections-field mdbase-collections-filter",
|
|
235
|
+
type: "search",
|
|
236
|
+
inputMode: "search",
|
|
237
|
+
"aria-label": copy.findCollection,
|
|
238
|
+
placeholder: copy.findCollection,
|
|
239
|
+
value: query,
|
|
240
|
+
onChange: event => setQuery(event.target.value),
|
|
241
|
+
onKeyDown: event => {
|
|
242
|
+
if (event.key === "ArrowDown") {
|
|
243
|
+
event.preventDefault();
|
|
244
|
+
lists.current?.querySelector('[role="radio"][tabindex="0"]')?.focus();
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
}), _jsxs("div", {
|
|
248
|
+
ref: lists,
|
|
249
|
+
className: "mdbase-collections-switcher-lists",
|
|
250
|
+
onKeyDown: moveAmongRadios,
|
|
251
|
+
children: [view.collections.length > 0 && _jsxs(_Fragment, {
|
|
252
|
+
children: [_jsx("span", {
|
|
253
|
+
id: listLabel,
|
|
254
|
+
className: "mdbase-collections-visually-hidden",
|
|
255
|
+
children: copy.yourCollections
|
|
256
|
+
}), _jsx("div", {
|
|
257
|
+
role: "radiogroup",
|
|
258
|
+
"aria-labelledby": listLabel,
|
|
259
|
+
className: "mdbase-collections-switcher-list",
|
|
260
|
+
children: rows.map(row => {
|
|
261
|
+
const detailId = row.statusDetail ? `${listLabel}-${row.id}` : undefined;
|
|
262
|
+
const checked = isCurrent(row);
|
|
263
|
+
return _jsxs("div", {
|
|
264
|
+
className: "mdbase-collections-item",
|
|
265
|
+
children: [_jsxs("button", {
|
|
266
|
+
type: "button",
|
|
267
|
+
role: "radio",
|
|
268
|
+
"aria-checked": checked,
|
|
269
|
+
"aria-disabled": !row.available || undefined,
|
|
270
|
+
"aria-describedby": detailId,
|
|
271
|
+
tabIndex: sdkKey(row) === rovingId ? 0 : -1,
|
|
272
|
+
className: "mdbase-collections-row",
|
|
273
|
+
onFocus: () => setFocused(sdkKey(row)),
|
|
274
|
+
onClick: () => {
|
|
275
|
+
if (!row.available || busy !== null) return;
|
|
276
|
+
actions.select(row.id);
|
|
277
|
+
onDone();
|
|
278
|
+
},
|
|
279
|
+
children: [_jsx(CollectionMark, {
|
|
280
|
+
row: row
|
|
281
|
+
}), _jsx(RowCopy, {
|
|
282
|
+
row: row,
|
|
283
|
+
ui: ui,
|
|
284
|
+
current: checked,
|
|
285
|
+
...(detailId ? {
|
|
286
|
+
detailId
|
|
287
|
+
} : {})
|
|
288
|
+
}), checked && _jsx(CheckGlyph, {})]
|
|
289
|
+
}), _jsx(RowMore, {
|
|
290
|
+
row: row,
|
|
291
|
+
ui: ui,
|
|
292
|
+
onDialog: onDialog,
|
|
293
|
+
tabbable: sdkKey(row) === rovingId
|
|
294
|
+
})]
|
|
295
|
+
}, row.id);
|
|
296
|
+
})
|
|
297
|
+
})]
|
|
298
|
+
}), appSections, needle && shown === 0 && _jsx("p", {
|
|
299
|
+
className: "mdbase-collections-empty",
|
|
300
|
+
children: say("noMatches", {
|
|
301
|
+
query: query.trim()
|
|
302
|
+
})
|
|
303
|
+
})]
|
|
304
|
+
}), ui.error && _jsx(NoticeBlock, {
|
|
305
|
+
notice: ui.error,
|
|
306
|
+
ui: ui,
|
|
307
|
+
variant: "inline"
|
|
308
|
+
}), _jsxs("div", {
|
|
309
|
+
className: "mdbase-collections-switcher-actions",
|
|
310
|
+
children: [app && _jsx(ActionButton, {
|
|
311
|
+
tone: "quiet",
|
|
312
|
+
inert: busy !== null,
|
|
313
|
+
onClick: () => {
|
|
314
|
+
actions.chooseAnother();
|
|
315
|
+
onDone();
|
|
316
|
+
},
|
|
317
|
+
children: copy.chooseAnother
|
|
318
|
+
}), _jsxs(ActionButton, {
|
|
319
|
+
tone: "quiet",
|
|
320
|
+
className: "is-add",
|
|
321
|
+
inert: busy !== null && busy !== "add",
|
|
322
|
+
progress: busy === "add" ? copy.addingCollection : null,
|
|
323
|
+
onClick: () => {
|
|
324
|
+
actions.addCollection();
|
|
325
|
+
onDone();
|
|
326
|
+
},
|
|
327
|
+
children: [_jsx("svg", {
|
|
328
|
+
viewBox: "0 0 16 16",
|
|
329
|
+
"aria-hidden": "true",
|
|
330
|
+
children: _jsx("path", {
|
|
331
|
+
d: "M8 3v10M3 8h10"
|
|
332
|
+
})
|
|
333
|
+
}), copy.addCollection]
|
|
334
|
+
}), view.links && _jsx("a", {
|
|
335
|
+
className: "mdbase-collections-link",
|
|
336
|
+
href: view.links.manageAccess,
|
|
337
|
+
target: "_blank",
|
|
338
|
+
rel: "noopener noreferrer",
|
|
339
|
+
children: copy.manageAccess
|
|
340
|
+
})]
|
|
341
|
+
}), _jsx("div", {
|
|
342
|
+
className: "mdbase-collections-switcher-footer",
|
|
343
|
+
children: _jsx(AccountLine, {
|
|
344
|
+
ui: ui
|
|
345
|
+
})
|
|
346
|
+
})]
|
|
347
|
+
});
|
|
348
|
+
}
|
|
349
|
+
/** One app section as its own labelled radio group. */
|
|
350
|
+
function SwitcherSection({
|
|
351
|
+
ui,
|
|
352
|
+
section,
|
|
353
|
+
matches,
|
|
354
|
+
focused,
|
|
355
|
+
onFocusItem,
|
|
356
|
+
onDone,
|
|
357
|
+
onDialog
|
|
358
|
+
}) {
|
|
359
|
+
const label = useId();
|
|
360
|
+
const items = section.items.filter(item => matches(item.name));
|
|
361
|
+
if (section.items.length > 0 && items.length === 0) return null;
|
|
362
|
+
const key = id => `${section.id}:${id}`;
|
|
363
|
+
const roving = items.some(item => key(item.id) === focused) ? focused : key((items.find(item => item.current) ?? items[0])?.id ?? "");
|
|
364
|
+
return _jsxs("div", {
|
|
365
|
+
className: "mdbase-collections-switcher-section",
|
|
366
|
+
children: [_jsx("span", {
|
|
367
|
+
id: label,
|
|
368
|
+
className: "mdbase-collections-section-title",
|
|
369
|
+
children: section.title
|
|
370
|
+
}), items.length === 0 ? _jsx("p", {
|
|
371
|
+
className: "mdbase-collections-empty",
|
|
372
|
+
children: section.empty
|
|
373
|
+
}) : _jsx("div", {
|
|
374
|
+
role: "radiogroup",
|
|
375
|
+
"aria-labelledby": label,
|
|
376
|
+
className: "mdbase-collections-switcher-list",
|
|
377
|
+
children: items.map(item => _jsxs("div", {
|
|
378
|
+
className: "mdbase-collections-item",
|
|
379
|
+
children: [_jsxs("button", {
|
|
380
|
+
type: "button",
|
|
381
|
+
role: "radio",
|
|
382
|
+
"aria-checked": item.current === true,
|
|
383
|
+
"aria-disabled": item.disabled || undefined,
|
|
384
|
+
tabIndex: key(item.id) === roving ? 0 : -1,
|
|
385
|
+
className: "mdbase-collections-row",
|
|
386
|
+
onFocus: () => onFocusItem(key(item.id)),
|
|
387
|
+
onClick: () => {
|
|
388
|
+
if (item.disabled || ui.busy !== null) return;
|
|
389
|
+
ui.actions.selectItem(section, item);
|
|
390
|
+
onDone();
|
|
391
|
+
},
|
|
392
|
+
children: [_jsx(CollectionMark, {
|
|
393
|
+
row: item
|
|
394
|
+
}), _jsx(ItemCopy, {
|
|
395
|
+
item: item,
|
|
396
|
+
ui: ui
|
|
397
|
+
}), item.current && _jsx(CheckGlyph, {})]
|
|
398
|
+
}), _jsx(ItemMore, {
|
|
399
|
+
item: item,
|
|
400
|
+
ui: ui,
|
|
401
|
+
onDialog: onDialog,
|
|
402
|
+
tabbable: key(item.id) === roving
|
|
403
|
+
})]
|
|
404
|
+
}, item.id))
|
|
405
|
+
})]
|
|
406
|
+
});
|
|
407
|
+
}
|
|
408
|
+
/** Arrow keys within the focused row's radio group; Right and Left between a row and its menu button. */
|
|
409
|
+
function moveAmongRadios(event) {
|
|
410
|
+
const target = event.target;
|
|
411
|
+
if (event.key === "ArrowRight" && target.getAttribute("role") === "radio") {
|
|
412
|
+
const more = target.parentElement?.querySelector(".mdbase-collections-more");
|
|
413
|
+
if (more) {
|
|
414
|
+
more.focus();
|
|
415
|
+
event.preventDefault();
|
|
416
|
+
}
|
|
417
|
+
return;
|
|
418
|
+
}
|
|
419
|
+
if (event.key === "ArrowLeft" && target.classList.contains("mdbase-collections-more")) {
|
|
420
|
+
target.parentElement?.querySelector('[role="radio"]')?.focus();
|
|
421
|
+
event.preventDefault();
|
|
422
|
+
return;
|
|
423
|
+
}
|
|
424
|
+
const group = target.closest('[role="radiogroup"]');
|
|
425
|
+
if (!group || !["ArrowDown", "ArrowUp", "Home", "End"].includes(event.key)) return;
|
|
426
|
+
const radios = [...group.querySelectorAll('[role="radio"]')];
|
|
427
|
+
if (radios.length === 0) return;
|
|
428
|
+
event.preventDefault();
|
|
429
|
+
const index = radios.indexOf(target);
|
|
430
|
+
const from = index >= 0 ? index : radios.findIndex(radio => radio.parentElement?.contains(target));
|
|
431
|
+
const next = event.key === "Home" ? 0 : event.key === "End" ? radios.length - 1 : (from + (event.key === "ArrowDown" ? 1 : -1) + radios.length) % radios.length;
|
|
432
|
+
radios[next]?.focus();
|
|
433
|
+
}
|
|
434
|
+
/** Where focus starts in the list: the open collection, else the first group's row. */
|
|
435
|
+
function firstRadio(root) {
|
|
436
|
+
return root.querySelector('[role="radio"][aria-checked="true"]') ?? root.querySelector('[role="radio"][tabindex="0"]');
|
|
437
|
+
}
|
|
438
|
+
function CheckGlyph() {
|
|
439
|
+
return _jsx("svg", {
|
|
440
|
+
className: "mdbase-collections-check",
|
|
441
|
+
viewBox: "0 0 16 16",
|
|
442
|
+
"aria-hidden": "true",
|
|
443
|
+
children: _jsx("path", {
|
|
444
|
+
d: "m3.5 8.5 3 3 6-7"
|
|
445
|
+
})
|
|
446
|
+
});
|
|
447
|
+
}
|
|
448
|
+
/**
|
|
449
|
+
* Wide screens: a non-modal dialog anchored to the trigger (or the app's
|
|
450
|
+
* anchor), in the top layer where supported. Pointer presses and focus on the
|
|
451
|
+
* anchor count as inside, so the trigger can toggle it.
|
|
452
|
+
*/
|
|
453
|
+
function SwitcherPopover({
|
|
454
|
+
id,
|
|
455
|
+
ui,
|
|
456
|
+
anchorRef,
|
|
457
|
+
onClose,
|
|
458
|
+
children
|
|
459
|
+
}) {
|
|
460
|
+
const ref = useRef(null);
|
|
461
|
+
const closeRef = useRef(onClose);
|
|
462
|
+
closeRef.current = onClose;
|
|
463
|
+
useLayoutEffect(() => {
|
|
464
|
+
const panel = ref.current;
|
|
465
|
+
const anchor = anchorRef.current;
|
|
466
|
+
if (!panel || !anchor) return undefined;
|
|
467
|
+
if (typeof panel.showPopover === "function") panel.showPopover();
|
|
468
|
+
const box = anchor.getBoundingClientRect();
|
|
469
|
+
const width = Math.min(340, window.innerWidth - 16);
|
|
470
|
+
panel.style.width = `${width}px`;
|
|
471
|
+
const place = anchoredPlacement(box, {
|
|
472
|
+
width,
|
|
473
|
+
height: panel.scrollHeight
|
|
474
|
+
}, {
|
|
475
|
+
width: window.innerWidth,
|
|
476
|
+
height: window.innerHeight
|
|
477
|
+
}, {
|
|
478
|
+
gap: 6
|
|
479
|
+
});
|
|
480
|
+
panel.style.top = `${place.top}px`;
|
|
481
|
+
panel.style.left = `${place.left}px`;
|
|
482
|
+
panel.style.maxHeight = `${place.maxHeight}px`;
|
|
483
|
+
(firstRadio(panel) ?? panel.querySelector("button") ?? panel).focus({
|
|
484
|
+
preventScroll: true
|
|
485
|
+
});
|
|
486
|
+
return () => {
|
|
487
|
+
if (typeof panel.hidePopover === "function" && panel.matches(":popover-open")) panel.hidePopover();
|
|
488
|
+
};
|
|
489
|
+
}, [anchorRef]);
|
|
490
|
+
useEffect(() => {
|
|
491
|
+
const outside = event => {
|
|
492
|
+
const target = event.target;
|
|
493
|
+
if (target && !ref.current?.contains(target) && !anchorRef.current?.contains(target)) closeRef.current(false);
|
|
494
|
+
};
|
|
495
|
+
const resize = () => closeRef.current(false);
|
|
496
|
+
document.addEventListener("pointerdown", outside, true);
|
|
497
|
+
window.addEventListener("resize", resize);
|
|
498
|
+
return () => {
|
|
499
|
+
document.removeEventListener("pointerdown", outside, true);
|
|
500
|
+
window.removeEventListener("resize", resize);
|
|
501
|
+
};
|
|
502
|
+
}, [anchorRef]);
|
|
503
|
+
return _jsx("div", {
|
|
504
|
+
ref: ref,
|
|
505
|
+
id: id,
|
|
506
|
+
className: "mdbase-collections-popover",
|
|
507
|
+
popover: "manual",
|
|
508
|
+
role: "dialog",
|
|
509
|
+
"aria-label": ui.copy.collectionsLabel,
|
|
510
|
+
tabIndex: -1,
|
|
511
|
+
onKeyDown: event => {
|
|
512
|
+
if (event.key !== "Escape") return;
|
|
513
|
+
event.preventDefault();
|
|
514
|
+
event.stopPropagation();
|
|
515
|
+
onClose(true);
|
|
516
|
+
},
|
|
517
|
+
onBlur: event => {
|
|
518
|
+
const next = event.relatedTarget;
|
|
519
|
+
if (next && !ref.current?.contains(next) && !anchorRef.current?.contains(next)) onClose(false);
|
|
520
|
+
},
|
|
521
|
+
children: children
|
|
522
|
+
});
|
|
523
|
+
}
|
|
524
|
+
/** Narrow screens: a modal bottom sheet on the native <dialog>. */
|
|
525
|
+
function SwitcherSheet({
|
|
526
|
+
id,
|
|
527
|
+
ui,
|
|
528
|
+
onClose,
|
|
529
|
+
children
|
|
530
|
+
}) {
|
|
531
|
+
const ref = useRef(null);
|
|
532
|
+
useLayoutEffect(() => {
|
|
533
|
+
const sheet = ref.current;
|
|
534
|
+
if (!sheet) return undefined;
|
|
535
|
+
if (!sheet.open) {
|
|
536
|
+
if (typeof sheet.showModal === "function") sheet.showModal();else sheet.setAttribute("open", "");
|
|
537
|
+
}
|
|
538
|
+
firstRadio(sheet)?.focus({
|
|
539
|
+
preventScroll: true
|
|
540
|
+
});
|
|
541
|
+
return () => {
|
|
542
|
+
if (sheet.open && typeof sheet.close === "function") sheet.close();
|
|
543
|
+
};
|
|
544
|
+
}, []);
|
|
545
|
+
return _jsx("dialog", {
|
|
546
|
+
ref: ref,
|
|
547
|
+
id: id,
|
|
548
|
+
className: "mdbase-collections mdbase-collections-sheet",
|
|
549
|
+
"aria-label": ui.copy.collectionsLabel,
|
|
550
|
+
onCancel: event => {
|
|
551
|
+
event.preventDefault();
|
|
552
|
+
onClose(true);
|
|
553
|
+
},
|
|
554
|
+
onClick: event => {
|
|
555
|
+
if (event.target === event.currentTarget) onClose(true);
|
|
556
|
+
},
|
|
557
|
+
children: _jsxs("div", {
|
|
558
|
+
className: "mdbase-collections-sheet-body",
|
|
559
|
+
children: [_jsxs("div", {
|
|
560
|
+
className: "mdbase-collections-sheet-header",
|
|
561
|
+
children: [_jsx("span", {
|
|
562
|
+
className: "mdbase-collections-sheet-title",
|
|
563
|
+
children: ui.copy.collectionsLabel
|
|
564
|
+
}), _jsx("button", {
|
|
565
|
+
type: "button",
|
|
566
|
+
className: "mdbase-collections-more",
|
|
567
|
+
"aria-label": ui.copy.close,
|
|
568
|
+
onClick: () => onClose(true),
|
|
569
|
+
children: _jsx("svg", {
|
|
570
|
+
viewBox: "0 0 16 16",
|
|
571
|
+
"aria-hidden": "true",
|
|
572
|
+
children: _jsx("path", {
|
|
573
|
+
d: "m4 4 8 8M12 4l-8 8"
|
|
574
|
+
})
|
|
575
|
+
})
|
|
576
|
+
})]
|
|
577
|
+
}), children]
|
|
578
|
+
})
|
|
579
|
+
});
|
|
580
|
+
}
|
|
581
|
+
function CollectionsGlyph() {
|
|
582
|
+
return _jsx("svg", {
|
|
583
|
+
className: "mdbase-collections-glyph",
|
|
584
|
+
viewBox: "0 0 16 16",
|
|
585
|
+
"aria-hidden": "true",
|
|
586
|
+
children: _jsx("path", {
|
|
587
|
+
d: "M2.5 4.5h4l1.2 1.5h5.8v6.5h-11z"
|
|
588
|
+
})
|
|
589
|
+
});
|
|
590
|
+
}
|
|
591
|
+
function subscribeNarrow(listener) {
|
|
592
|
+
const query = typeof window.matchMedia === "function" ? window.matchMedia(NARROW) : null;
|
|
593
|
+
query?.addEventListener("change", listener);
|
|
594
|
+
return () => query?.removeEventListener("change", listener);
|
|
595
|
+
}
|
|
596
|
+
function useNarrow() {
|
|
597
|
+
return useSyncExternalStore(subscribeNarrow, () => typeof window.matchMedia === "function" && window.matchMedia(NARROW).matches, () => false);
|
|
598
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { type JSX } from "react";
|
|
2
|
+
import type { CollectionUi } from "./use-collection-ui.js";
|
|
3
|
+
/**
|
|
4
|
+
* Reviewable setup: `setup.plan()` (read-only, so it starts at once), then
|
|
5
|
+
* `setup.apply(plan)` only from the person's click. Never configures silently.
|
|
6
|
+
*/
|
|
7
|
+
export declare function SetupReview({ ui }: {
|
|
8
|
+
readonly ui: CollectionUi;
|
|
9
|
+
}): JSX.Element;
|