@0001-labs/design-system-1 0.3.0-alpha.15 → 0.3.0-alpha.17
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/DS1/0-face/{drawer.ts → attach-overlay.ts} +17 -17
- package/DS1/0-face/{dialogs.ts → feedback-overlays.ts} +8 -8
- package/DS1/0-face/feedback.ts +3 -3
- package/DS1/0-face/generated/toast-assets.ts +1 -1
- package/DS1/0-face/header.ts +3 -3
- package/DS1/0-face/{dialog-swipe.ts → overlay-swipe.ts} +47 -44
- package/DS1/0-face/toaster.ts +20 -2
- package/DS1/1-root/cjk.css +1 -1
- package/DS1/1-root/component-tokens.css +4 -0
- package/DS1/1-root/controls.css +33 -26
- package/DS1/1-root/empty.css +22 -0
- package/DS1/1-root/fonts.css +1 -1
- package/DS1/1-root/header.css +4 -4
- package/DS1/1-root/one.css +1 -1
- package/DS1/1-root/one.semantic.css +1 -1
- package/DS1/1-root/overlay.css +283 -0
- package/DS1/2-core/ds-empty.ts +9 -3
- package/DS1/2-core/ds-tag.ts +11 -0
- package/DS1/2-core/ds-toast.ts +27 -1
- package/DS1/2-core/generated/ds-icon-map.ts +6 -0
- package/DS1/2-core/generated/icons-extra.ts +3 -0
- package/DS1/2-core/generated/icons-immediate.ts +1 -1
- package/DS1/2-core/styles/ds-tag.css +28 -0
- package/DS1/2-core/styles/ds-toast.css +6 -0
- package/DS1/3-unit/context-trigger.ts +4 -1
- package/DS1/3-unit/ds-context.ts +102 -0
- package/DS1/3-unit/ds-menu.ts +56 -12
- package/DS1/3-unit/ds-select.ts +211 -0
- package/DS1/3-unit/ds-sidebar.ts +3 -1
- package/DS1/3-unit/styles/ds-context.css +107 -0
- package/DS1/{2-core → 3-unit}/styles/ds-select.css +70 -6
- package/DS1/4-page/ds-header.ts +5 -5
- package/DS1/4-page/{ds-dialog.ts → ds-overlay.ts} +77 -51
- package/DS1/4-page/ds-page.ts +5 -5
- package/DS1/4-page/styles/ds-header.css +2 -2
- package/DS1/4-page/styles/ds-overlay.css +88 -0
- package/DS1/4-page/styles/ds-page.css +1 -1
- package/DS1/components.ts +2 -1
- package/DS1/index.ts +4 -2
- package/DS1/react/choice.ts +2 -14
- package/DS1/react/{context-menu.ts → context.ts} +35 -35
- package/DS1/react/empty.ts +4 -2
- package/DS1/react/{menu.ts → menu-engine.ts} +10 -3
- package/DS1/react/{dialog.ts → overlay.ts} +40 -37
- package/DS1/react/select.ts +118 -0
- package/DS1/react/tag.ts +6 -0
- package/DS1/x-icon/dot-happy.svg +3 -0
- package/DS1/x-icon/dot-neutral.svg +3 -0
- package/DS1/x-icon/dot-sad.svg +3 -0
- package/README.md +29 -214
- package/dist/0-face/{drawer.d.ts → attach-overlay.d.ts} +8 -8
- package/dist/0-face/attach-overlay.d.ts.map +1 -0
- package/dist/0-face/{drawer.js → attach-overlay.js} +15 -15
- package/dist/0-face/{dialogs.d.ts → feedback-overlays.d.ts} +1 -1
- package/dist/0-face/feedback-overlays.d.ts.map +1 -0
- package/dist/0-face/{dialogs.js → feedback-overlays.js} +8 -8
- package/dist/0-face/feedback.d.ts +3 -3
- package/dist/0-face/feedback.d.ts.map +1 -1
- package/dist/0-face/feedback.js +2 -2
- package/dist/0-face/generated/toast-assets.d.ts.map +1 -1
- package/dist/0-face/generated/toast-assets.js +1 -1
- package/dist/0-face/header.d.ts +2 -2
- package/dist/0-face/header.d.ts.map +1 -1
- package/dist/0-face/header.js +3 -3
- package/dist/0-face/{dialog-swipe.d.ts → overlay-swipe.d.ts} +16 -14
- package/dist/0-face/overlay-swipe.d.ts.map +1 -0
- package/dist/0-face/{dialog-swipe.js → overlay-swipe.js} +44 -42
- package/dist/0-face/toaster.d.ts.map +1 -1
- package/dist/0-face/toaster.js +21 -2
- package/dist/2-core/ds-empty.d.ts +7 -0
- package/dist/2-core/ds-empty.d.ts.map +1 -1
- package/dist/2-core/ds-empty.js +8 -3
- package/dist/2-core/ds-tag.d.ts +12 -0
- package/dist/2-core/ds-tag.d.ts.map +1 -0
- package/dist/2-core/ds-tag.js +9 -0
- package/dist/2-core/ds-toast.d.ts +9 -0
- package/dist/2-core/ds-toast.d.ts.map +1 -1
- package/dist/2-core/ds-toast.js +29 -1
- package/dist/2-core/generated/ds-icon-map.d.ts +4 -1
- package/dist/2-core/generated/ds-icon-map.d.ts.map +1 -1
- package/dist/2-core/generated/ds-icon-map.js +6 -0
- package/dist/2-core/generated/icons-extra.d.ts.map +1 -1
- package/dist/2-core/generated/icons-extra.js +3 -0
- package/dist/2-core/generated/icons-immediate.d.ts +1 -1
- package/dist/2-core/generated/icons-immediate.d.ts.map +1 -1
- package/dist/2-core/generated/icons-immediate.js +1 -1
- package/dist/3-unit/context-trigger.d.ts +2 -0
- package/dist/3-unit/context-trigger.d.ts.map +1 -1
- package/dist/3-unit/context-trigger.js +3 -1
- package/dist/3-unit/ds-context.d.ts +104 -0
- package/dist/3-unit/ds-context.d.ts.map +1 -0
- package/dist/3-unit/ds-context.js +104 -0
- package/dist/3-unit/ds-menu.d.ts +19 -2
- package/dist/3-unit/ds-menu.d.ts.map +1 -1
- package/dist/3-unit/ds-menu.js +60 -9
- package/dist/3-unit/ds-select.d.ts +132 -0
- package/dist/3-unit/ds-select.d.ts.map +1 -0
- package/dist/3-unit/ds-select.js +221 -0
- package/dist/3-unit/ds-sidebar.d.ts +2 -0
- package/dist/3-unit/ds-sidebar.d.ts.map +1 -1
- package/dist/3-unit/ds-sidebar.js +1 -1
- package/dist/4-page/ds-header.js +5 -5
- package/dist/4-page/{ds-dialog.d.ts → ds-overlay.d.ts} +43 -21
- package/dist/4-page/ds-overlay.d.ts.map +1 -0
- package/dist/4-page/{ds-dialog.js → ds-overlay.js} +64 -46
- package/dist/4-page/ds-page.d.ts +1 -1
- package/dist/4-page/ds-page.js +5 -5
- package/dist/components.d.ts +2 -1
- package/dist/components.d.ts.map +1 -1
- package/dist/components.js +2 -1
- package/dist/design-system-1.bundle.js +7727 -7357
- package/dist/design-system-1.bundle.js.map +1 -1
- package/dist/design-system-1.bundle.min.js +4966 -4766
- package/dist/design-system-1.bundle.min.js.map +1 -1
- package/dist/design-system-1.css +1 -1
- package/dist/feedback.js +57 -44
- package/dist/feedback.js.map +1 -1
- package/dist/{icons-extra-CiDDs7_-.js → icons-extra-BbbpXocy.js} +10 -1
- package/dist/{icons-extra-CiDDs7_-.js.map → icons-extra-BbbpXocy.js.map} +1 -1
- package/dist/{icons-extra-hauwCx07.js → icons-extra-C0E3yQpX.js} +4 -1
- package/dist/{icons-extra-hauwCx07.js.map → icons-extra-C0E3yQpX.js.map} +1 -1
- package/dist/index.d.ts +4 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -2
- package/dist/react/choice.d.ts +2 -14
- package/dist/react/choice.d.ts.map +1 -1
- package/dist/react/choice.js +1 -4
- package/dist/react/{context-menu.d.ts → context.d.ts} +22 -22
- package/dist/react/context.d.ts.map +1 -0
- package/dist/react/{context-menu.js → context.js} +29 -29
- package/dist/react/empty.d.ts +4 -0
- package/dist/react/empty.d.ts.map +1 -1
- package/dist/react/empty.js +2 -2
- package/dist/react/{menu.d.ts → menu-engine.d.ts} +11 -2
- package/dist/react/menu-engine.d.ts.map +1 -0
- package/dist/react/{menu.js → menu-engine.js} +6 -2
- package/dist/react/{dialog.d.ts → overlay.d.ts} +25 -23
- package/dist/react/overlay.d.ts.map +1 -0
- package/dist/react/{dialog.js → overlay.js} +24 -23
- package/dist/react/select.d.ts +62 -0
- package/dist/react/select.d.ts.map +1 -0
- package/dist/react/select.js +80 -0
- package/dist/react/tag.d.ts +5 -0
- package/dist/react/tag.d.ts.map +1 -0
- package/dist/react/tag.js +4 -0
- package/package.json +1 -1
- package/DS1/1-root/dialog-placement.css +0 -191
- package/DS1/1-root/drawer.css +0 -83
- package/DS1/2-core/ds-select.ts +0 -44
- package/DS1/4-page/styles/ds-dialog.css +0 -94
- package/dist/0-face/dialog-swipe.d.ts.map +0 -1
- package/dist/0-face/dialogs.d.ts.map +0 -1
- package/dist/0-face/drawer.d.ts.map +0 -1
- package/dist/2-core/ds-select.d.ts +0 -30
- package/dist/2-core/ds-select.d.ts.map +0 -1
- package/dist/2-core/ds-select.js +0 -45
- package/dist/4-page/ds-dialog.d.ts.map +0 -1
- package/dist/react/context-menu.d.ts.map +0 -1
- package/dist/react/dialog.d.ts.map +0 -1
- package/dist/react/menu.d.ts.map +0 -1
|
@@ -3,10 +3,10 @@
|
|
|
3
3
|
* from a menu button. Plain TypeScript with no Lit and no framework, so a page
|
|
4
4
|
* of native HTML uses it as the elements do.
|
|
5
5
|
*
|
|
6
|
-
* `
|
|
6
|
+
* `attachOverlay()` ties a toggle button to the parts it folds away (a
|
|
7
7
|
* header's navigation and actions, a page's navigation). Opening moves them
|
|
8
|
-
* into a native `<dialog class="ds-
|
|
9
|
-
* (1-root/
|
|
8
|
+
* into a native `<dialog class="ds-overlay ds-overlay--screen">` laid over the screen
|
|
9
|
+
* (1-root/overlay.css draws it), shown modally so the rest of the page is
|
|
10
10
|
* inert, with a close button where the toggle stood. While it is open:
|
|
11
11
|
*
|
|
12
12
|
* - Tab and Shift+Tab go round the panel's controls and never leave it;
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
* stays the same (Menu) open or closed.
|
|
20
20
|
*
|
|
21
21
|
* @example
|
|
22
|
-
* const drawer =
|
|
22
|
+
* const drawer = attachOverlay({ toggle, content: [nav, actions], closeLabel: "Close" });
|
|
23
23
|
* drawer.open();
|
|
24
24
|
*/
|
|
25
25
|
|
|
@@ -29,8 +29,8 @@ export interface DrawerOptions {
|
|
|
29
29
|
/** What the drawer holds while it is open: moved into the panel in this order and back on closing. */
|
|
30
30
|
content: readonly Element[] | (() => readonly Element[]);
|
|
31
31
|
/**
|
|
32
|
-
* The panel to open, with its close button marked `.ds-
|
|
33
|
-
* `<dialog class="ds-
|
|
32
|
+
* The panel to open, with its close button marked `.ds-overlay__close`. A
|
|
33
|
+
* `<dialog class="ds-overlay ds-overlay--screen">` is made beside the toggle when none
|
|
34
34
|
* is given, its close button a copy of the toggle.
|
|
35
35
|
*/
|
|
36
36
|
panel?: HTMLDialogElement;
|
|
@@ -116,20 +116,20 @@ export function tabbables(root: Element): HTMLElement[] {
|
|
|
116
116
|
return found;
|
|
117
117
|
}
|
|
118
118
|
|
|
119
|
-
export function
|
|
119
|
+
export function attachOverlay(options: DrawerOptions): Drawer {
|
|
120
120
|
const { toggle } = options;
|
|
121
121
|
const document = toggle.ownerDocument;
|
|
122
122
|
const made = !options.panel;
|
|
123
123
|
const panel = options.panel ?? document.createElement("dialog");
|
|
124
124
|
let placeholders: [Element, Comment][] = [];
|
|
125
125
|
let open = false;
|
|
126
|
-
/** The button in the panel that closes it: made here, or the panel's own `.ds-
|
|
127
|
-
let closeButton: HTMLElement | null = options.panel?.querySelector<HTMLElement>(".ds-
|
|
126
|
+
/** The button in the panel that closes it: made here, or the panel's own `.ds-overlay__close`. */
|
|
127
|
+
let closeButton: HTMLElement | null = options.panel?.querySelector<HTMLElement>(".ds-overlay__close") ?? null;
|
|
128
128
|
|
|
129
129
|
if (made) {
|
|
130
|
-
panel.className = "ds-
|
|
130
|
+
panel.className = "ds-overlay ds-overlay--screen";
|
|
131
131
|
const bar = document.createElement("div");
|
|
132
|
-
bar.className = "ds-
|
|
132
|
+
bar.className = "ds-overlay__bar";
|
|
133
133
|
// The close button is a copy of the toggle, so it shows the same icons
|
|
134
134
|
// (the second one, close) in the same place.
|
|
135
135
|
closeButton = toggle.cloneNode(true) as HTMLElement;
|
|
@@ -137,8 +137,8 @@ export function attachDrawer(options: DrawerOptions): Drawer {
|
|
|
137
137
|
closeButton.removeAttribute("aria-controls");
|
|
138
138
|
closeButton.removeAttribute("aria-expanded");
|
|
139
139
|
closeButton.removeAttribute("aria-haspopup");
|
|
140
|
-
closeButton.classList.remove("ds-
|
|
141
|
-
closeButton.classList.add("ds-
|
|
140
|
+
closeButton.classList.remove("ds-overlay__toggle", "ds-header__toggle");
|
|
141
|
+
closeButton.classList.add("ds-overlay__close");
|
|
142
142
|
closeButton.setAttribute("aria-label", options.closeLabel || "Close");
|
|
143
143
|
if (closeButton instanceof HTMLButtonElement) closeButton.type = "button";
|
|
144
144
|
bar.append(closeButton);
|
|
@@ -146,14 +146,14 @@ export function attachDrawer(options: DrawerOptions): Drawer {
|
|
|
146
146
|
if (options.container) options.container.append(panel);
|
|
147
147
|
else toggle.after(panel);
|
|
148
148
|
}
|
|
149
|
-
panel.classList.add("ds-
|
|
149
|
+
panel.classList.add("ds-overlay--screen");
|
|
150
150
|
const label = () => options.label || toggle.getAttribute("aria-label") || toggle.textContent?.trim() || "";
|
|
151
151
|
if (!panel.hasAttribute("aria-label") && !panel.hasAttribute("aria-labelledby") && label()) panel.setAttribute("aria-label", label());
|
|
152
|
-
if (!panel.id) panel.id = `ds-
|
|
152
|
+
if (!panel.id) panel.id = `ds-overlay-${Math.random().toString(36).slice(2, 10)}`;
|
|
153
153
|
if (!toggle.hasAttribute("aria-controls")) toggle.setAttribute("aria-controls", panel.id);
|
|
154
154
|
toggle.setAttribute("aria-expanded", "false");
|
|
155
155
|
toggle.setAttribute("aria-haspopup", "dialog");
|
|
156
|
-
toggle.classList.add("ds-
|
|
156
|
+
toggle.classList.add("ds-overlay__toggle");
|
|
157
157
|
|
|
158
158
|
const parts = () => (typeof options.content === "function" ? options.content() : options.content).filter(Boolean);
|
|
159
159
|
|
|
@@ -162,7 +162,7 @@ export function attachDrawer(options: DrawerOptions): Drawer {
|
|
|
162
162
|
open = true;
|
|
163
163
|
const content = parts();
|
|
164
164
|
placeholders = content.map(element => {
|
|
165
|
-
const mark = document.createComment("ds-
|
|
165
|
+
const mark = document.createComment("ds-overlay");
|
|
166
166
|
element.before(mark);
|
|
167
167
|
return [element, mark];
|
|
168
168
|
});
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Dialogs that answer with a promise, in place of the browser's `alert()`,
|
|
3
3
|
* `confirm()` and `prompt()`, for pages without Lit or any other framework.
|
|
4
4
|
*
|
|
5
|
-
* Each is drawn on the native `dialog.ds-
|
|
5
|
+
* Each is drawn on the native `dialog.ds-overlay` frame from `1-root/controls.css`
|
|
6
6
|
* (which the page loads) with its native buttons, `.ds-button--primary`,
|
|
7
7
|
* `--secondary` and `--danger`, and opened with `showModal()`, so the browser
|
|
8
8
|
* keeps the rest of the page inert behind it. Their words default to the
|
|
@@ -100,32 +100,32 @@ function show(spec: Spec): Promise<boolean | string | null> {
|
|
|
100
100
|
const id = `ds-feedback-${++count}`;
|
|
101
101
|
const opener = focused();
|
|
102
102
|
const dialog = element("dialog", {
|
|
103
|
-
class: "ds-
|
|
103
|
+
class: "ds-overlay",
|
|
104
104
|
role: spec.kind === "prompt" ? "dialog" : "alertdialog",
|
|
105
105
|
"aria-labelledby": `${id}-title`,
|
|
106
106
|
"data-ds-feedback": spec.kind,
|
|
107
107
|
});
|
|
108
|
-
const header = element("div", { "data-ds-
|
|
109
|
-
header.appendChild(element("h2", { id: `${id}-title`, "data-ds-
|
|
108
|
+
const header = element("div", { "data-ds-overlay": "header" });
|
|
109
|
+
header.appendChild(element("h2", { id: `${id}-title`, "data-ds-overlay": "title" }, spec.title));
|
|
110
110
|
if (spec.text) {
|
|
111
|
-
header.appendChild(element("p", { id: `${id}-text`, "data-ds-
|
|
111
|
+
header.appendChild(element("p", { id: `${id}-text`, "data-ds-overlay": "description" }, spec.text));
|
|
112
112
|
dialog.setAttribute("aria-describedby", `${id}-text`);
|
|
113
113
|
}
|
|
114
114
|
dialog.appendChild(header);
|
|
115
115
|
|
|
116
116
|
let input: HTMLInputElement | undefined;
|
|
117
117
|
if (spec.field) {
|
|
118
|
-
const field = element("div", { "data-ds-
|
|
118
|
+
const field = element("div", { "data-ds-overlay": "field" });
|
|
119
119
|
input = element("input", { id: `${id}-field`, class: "ds-input", type: "text", autocomplete: "off" });
|
|
120
120
|
input.value = spec.field.value;
|
|
121
121
|
if (spec.field.placeholder) input.placeholder = spec.field.placeholder;
|
|
122
|
-
if (spec.field.label) field.appendChild(element("label", { for: input.id, "data-ds-
|
|
122
|
+
if (spec.field.label) field.appendChild(element("label", { for: input.id, "data-ds-overlay": "label" }, spec.field.label));
|
|
123
123
|
else input.setAttribute("aria-labelledby", `${id}-title`);
|
|
124
124
|
field.appendChild(input);
|
|
125
125
|
dialog.appendChild(field);
|
|
126
126
|
}
|
|
127
127
|
|
|
128
|
-
const footer = element("div", { "data-ds-
|
|
128
|
+
const footer = element("div", { "data-ds-overlay": "footer" });
|
|
129
129
|
const cancel = spec.cancel === undefined ? undefined : element("button", { type: "button", class: "ds-button ds-button--secondary", value: "cancel" }, spec.cancel);
|
|
130
130
|
const confirm = element("button", { type: "button", class: spec.danger ? "ds-button ds-button--secondary ds-button--danger" : "ds-button ds-button--primary", value: "confirm" }, spec.confirm);
|
|
131
131
|
if (cancel) footer.appendChild(cancel);
|
package/DS1/0-face/feedback.ts
CHANGED
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
* (`./toaster.ts`) on a page without Lit and by `<ds-toast>` where the page
|
|
17
17
|
* has one. `configureToaster()` takes Sonner's `<Toaster />` props.
|
|
18
18
|
* - `alert()`, `confirm()` and `prompt()` are drawn on the native
|
|
19
|
-
* `dialog.ds-
|
|
19
|
+
* `dialog.ds-overlay` frame from `1-root/controls.css`, which the page loads.
|
|
20
20
|
* - Default words (`./feedback-words.ts`) are English, with no language
|
|
21
21
|
* runtime. Where the page loads the language module (`./i18n.ts`, Sync 1's
|
|
22
22
|
* `languages` export), they are the shared words in the page's language
|
|
@@ -32,8 +32,8 @@
|
|
|
32
32
|
export { configureToaster, pageToaster, toast } from "./toaster";
|
|
33
33
|
export type { ToasterOptions } from "./toaster";
|
|
34
34
|
export type { ToastButton, ToastData, ToastId, ToastOptions, ToastPosition, ToastPromiseMessages, ToastType } from "./toast-queue";
|
|
35
|
-
export { alert, confirm, prompt } from "./
|
|
36
|
-
export type { AlertOptions, ConfirmOptions, PromptOptions } from "./
|
|
35
|
+
export { alert, confirm, prompt } from "./feedback-overlays";
|
|
36
|
+
export type { AlertOptions, ConfirmOptions, PromptOptions } from "./feedback-overlays";
|
|
37
37
|
export { copyText } from "./copy-text";
|
|
38
38
|
export type { CopyTextOptions } from "./copy-text";
|
|
39
39
|
export { setFeedbackWords } from "./feedback-words";
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
/** The toast's stylesheet, the same one `<ds-toast>` imports. */
|
|
8
|
-
export const toastStyles: string = "[data-ds-shape=\"square\"], :host([data-ds-shape=\"square\"]){--ds-corner-size: 0px;--ds-corner-shape: round;--ds-button-inset: 0.5px}[data-ds-shape=\"rounded\"], :host([data-ds-shape=\"rounded\"]){--ds-corner-size: 4px;--ds-corner-shape: round;--ds-button-inset: 2.5px}[data-ds-shape=\"circular\"], :host([data-ds-shape=\"circular\"]){--ds-corner-size: 999px;--ds-corner-shape: round;--ds-button-inset: 5px}@supports (corner-shape: squircle){[data-ds-shape=\"rounded\"], :host([data-ds-shape=\"rounded\"]){--ds-corner-size: 8px;--ds-corner-shape: squircle}}:root, :host, :where([data-ds-surface]){--ds-unit: calc(var(--ds-1, 20px) * var(--ds-sf, 1));--ds-control-height: calc(var(--ds-control-size, var(--ds-08, 16px)) * var(--ds-sf, 1));--ds-row-height: calc(var(--ds-row-size, var(--ds-1, 20px)) * var(--ds-sf, 1));--ds-type-size-default: calc(var(--ds-text-size, 14px) * var(--ds-sf, 1));--ds-type-size-small: calc(var(--ds-small-text-size, 10px) * var(--ds-sf, 1));--ds-type-size-heading: calc(var(--ds-heading-text-size, 13px) * var(--ds-sf, 1));--ds-type-size-display: calc(var(--ds-display-text-size, 20px) * var(--ds-sf, 1));--ds-type-lineheight-default: calc(var(--ds-text-leading, 16px) * var(--ds-sf, 1));--ds-type-letterspacing-default: calc(var(--ds-text-tracking, -0.3px) * var(--ds-sf, 1));--ds-radius: calc(var(--ds-corner-size, 4px) * var(--ds-sf, 1));--ds-radius-card: calc(var(--ds-card-corner-size, 4px) * var(--ds-sf, 1))}:host{--ds-toast-width: calc(16 * var(--ds-unit));--ds-toast-offset: var(--ds-unit);--ds-toast-gap: calc(var(--ds-unit) - var(--ds-control-height));--ds-toast-background: var(--ds-tooltip-background, light-dark(var(--ds-black, #2a2a2a), var(--ds-white, #ffffff)));--ds-toast-color: var(--ds-tooltip-color, light-dark(var(--ds-white, #ffffff), var(--ds-black, #2a2a2a)));display: contents}section{display: contents}ol{--lift: -1;position: fixed;z-index: 2147483000;box-sizing: border-box;width: min(var(--ds-toast-width), calc(100% - 2 * var(--ds-toast-offset)));height: calc(var(--height) + var(--gaps) * var(--ds-toast-gap));margin: 0;padding: 0;list-style: none;outline: none;pointer-events: none}ol[data-y=\"top\"]{--lift: 1;top: calc(var(--ds-toast-offset) + env(safe-area-inset-top, 0px))}ol[data-y=\"bottom\"]{bottom: calc(var(--ds-toast-offset) + env(safe-area-inset-bottom, 0px))}ol[data-x=\"left\"]{left: var(--ds-toast-offset)}ol[data-x=\"right\"]{right: var(--ds-toast-offset)}ol[data-x=\"center\"]{left: 50%;transform: translateX(-50%)}:host([contained]) ol{position: absolute;z-index: var(--ds-toast-z-index, 60)}:host([contained]) ol[data-y=\"top\"]{top: var(--ds-toast-offset)}:host([contained]) ol[data-y=\"bottom\"]{bottom: var(--ds-toast-offset)}.toast{--swipe: 0px;position: absolute;left: 0;right: 0;box-sizing: border-box;width: max-content;max-width: 100%;overflow: hidden;border-radius: var(--ds-radius);background: var(--ds-toast-background);color: var(--ds-toast-color);font-family: var(--ds-typeface-regular);font-feature-settings: var(--ds-font-features);font-size: var(--ds-type-size-default);line-height: var(--ds-control-height);letter-spacing: var(--ds-type-letterspacing-default);outline: none;touch-action: none;user-select: none;pointer-events: auto;opacity: 0;transition: transform 400ms ease, opacity 400ms ease}ol[data-y=\"bottom\"] .toast{bottom: 0}ol[data-y=\"top\"] .toast{top: 0}ol[data-x=\"center\"] .toast{margin-inline: auto}ol[data-x=\"left\"] .toast{margin-right: auto}ol[data-x=\"right\"] .toast{margin-left: auto}.toast:focus-visible{outline: 2px solid var(--ds-focus-ring);outline-offset: 1px}.toast[data-mounted=\"false\"]{transform: translateY(calc(var(--lift) * -100%))}.toast[data-mounted=\"true\"]{opacity: 1}ol[data-expanded=\"false\"] .toast[data-mounted=\"true\"]{transform: translateY(calc(var(--lift) * var(--index) * var(--ds-toast-gap) + var(--swipe))) scale(calc(1 - var(--index) * 0.05))}ol[data-expanded=\"false\"] .toast[data-front=\"false\"]{height: var(--front-height)}ol[data-expanded=\"false\"] .toast[data-front=\"false\"] .body{opacity: 0}ol[data-expanded=\"true\"] .toast[data-mounted=\"true\"]{transform: translateY(calc(var(--lift) * (var(--offset) + var(--index) * var(--ds-toast-gap)) + var(--swipe)))}.toast[data-visible=\"false\"]{opacity: 0;pointer-events: none}.toast[data-swiping=\"true\"]{transition: none}.toast[data-removed=\"true\"]{opacity: 0;pointer-events: none}.toast[data-removed=\"true\"][data-front=\"true\"]{transform: translateY(calc(var(--lift) * -100%))}.toast[data-swipe-out=\"true\"]{transform: translateY(calc(var(--swipe) + var(--lift) * -100%))}.body{display: flex;align-items: flex-start;gap: calc(4px * var(--ds-sf));box-sizing: border-box;min-height: var(--ds-control-height);padding-inline: calc(4px * var(--ds-sf));transition: opacity 400ms ease}.icon{display: inline-flex;flex: 0 0 auto;align-items: center;justify-content: center;width: var(--ds-control-height);height: var(--ds-control-height);--ds-icon-color: currentColor}.icon:first-child{margin-left: calc(-4px * var(--ds-sf))}.icon > svg, .close > svg{width: calc(14px * var(--ds-sf));height: calc(14px * var(--ds-sf))}.content{flex: 1 1 auto;min-width: 0}.title, .description{white-space: normal;overflow-wrap: anywhere}.description{opacity: 0.7}.button{flex: 0 0 auto;margin: 0 0 0 calc(4px * var(--ds-sf));padding: 0;border: 0;background: none;color: inherit;font: inherit;letter-spacing: inherit;text-decoration: underline;text-underline-offset: 2px;cursor: pointer}a.button{display: inline-block}.button.cancel{opacity: 0.7}.button:focus-visible, .close:focus-visible{outline: 1px solid currentColor;outline-offset: 1px;border-radius: 1px}.close{display: inline-flex;flex: 0 0 auto;align-items: center;justify-content: center;width: var(--ds-control-height);height: var(--ds-control-height);margin: 0 calc(-4px * var(--ds-sf)) 0 0;padding: 0;border: 0;background: none;color: inherit;cursor: pointer;--ds-icon-color: currentColor}.spinner{width: calc(10px * var(--ds-sf));height: calc(10px * var(--ds-sf));box-sizing: border-box;border: 1px solid color-mix(in srgb, currentColor 25%, transparent);border-top-color: currentColor;border-radius: 50%;animation: ds-toast-spin 720ms linear infinite}@keyframes ds-toast-spin{to{transform: rotate(360deg)}}:host([rich-colors]) .toast[data-type=\"success\"]{--ds-toast-background: var(--ds-apple-green)}:host([rich-colors]) .toast[data-type=\"info\"]{--ds-toast-background: var(--ds-zenith-blue)}:host([rich-colors]) .toast[data-type=\"warning\"]{--ds-toast-background: var(--ds-orange)}:host([rich-colors]) .toast[data-type=\"error\"]{--ds-toast-background: var(--ds-tuned-red)}:host([rich-colors]) .toast:is([data-type=\"success\"], [data-type=\"info\"], [data-type=\"warning\"], [data-type=\"error\"]){--ds-toast-color: var(--ds-black)}@media (prefers-reduced-motion: reduce){.toast, .body{transition: none}.spinner{animation: none}}";
|
|
8
|
+
export const toastStyles: string = "[data-ds-shape=\"square\"], :host([data-ds-shape=\"square\"]){--ds-corner-size: 0px;--ds-corner-shape: round;--ds-button-inset: 0.5px}[data-ds-shape=\"rounded\"], :host([data-ds-shape=\"rounded\"]){--ds-corner-size: 4px;--ds-corner-shape: round;--ds-button-inset: 2.5px}[data-ds-shape=\"circular\"], :host([data-ds-shape=\"circular\"]){--ds-corner-size: 999px;--ds-corner-shape: round;--ds-button-inset: 5px}@supports (corner-shape: squircle){[data-ds-shape=\"rounded\"], :host([data-ds-shape=\"rounded\"]){--ds-corner-size: 8px;--ds-corner-shape: squircle}}:root, :host, :where([data-ds-surface]){--ds-unit: calc(var(--ds-1, 20px) * var(--ds-sf, 1));--ds-control-height: calc(var(--ds-control-size, var(--ds-08, 16px)) * var(--ds-sf, 1));--ds-row-height: calc(var(--ds-row-size, var(--ds-1, 20px)) * var(--ds-sf, 1));--ds-type-size-default: calc(var(--ds-text-size, 14px) * var(--ds-sf, 1));--ds-type-size-small: calc(var(--ds-small-text-size, 10px) * var(--ds-sf, 1));--ds-type-size-heading: calc(var(--ds-heading-text-size, 13px) * var(--ds-sf, 1));--ds-type-size-display: calc(var(--ds-display-text-size, 20px) * var(--ds-sf, 1));--ds-type-lineheight-default: calc(var(--ds-text-leading, 16px) * var(--ds-sf, 1));--ds-type-letterspacing-default: calc(var(--ds-text-tracking, -0.3px) * var(--ds-sf, 1));--ds-radius: calc(var(--ds-corner-size, 4px) * var(--ds-sf, 1));--ds-radius-card: calc(var(--ds-card-corner-size, 4px) * var(--ds-sf, 1))}:host{--ds-toast-width: calc(16 * var(--ds-unit));--ds-toast-offset: var(--ds-unit);--ds-toast-gap: calc(var(--ds-unit) - var(--ds-control-height));--ds-toast-background: var(--ds-tooltip-background, light-dark(var(--ds-black, #2a2a2a), var(--ds-white, #ffffff)));--ds-toast-color: var(--ds-tooltip-color, light-dark(var(--ds-white, #ffffff), var(--ds-black, #2a2a2a)));display: contents}section{display: contents}ol{--lift: -1;position: fixed;z-index: 2147483000;inset: auto;overflow: visible;border: 0;background: transparent;color: inherit;box-sizing: border-box;width: min(var(--ds-toast-width), calc(100% - 2 * var(--ds-toast-offset)));height: calc(var(--height) + var(--gaps) * var(--ds-toast-gap));margin: 0;padding: 0;list-style: none;outline: none;pointer-events: none}ol[data-y=\"top\"]{--lift: 1;top: calc(var(--ds-toast-offset) + env(safe-area-inset-top, 0px))}ol[data-y=\"bottom\"]{bottom: calc(var(--ds-toast-offset) + env(safe-area-inset-bottom, 0px))}ol[data-x=\"left\"]{left: var(--ds-toast-offset)}ol[data-x=\"right\"]{right: var(--ds-toast-offset)}ol[data-x=\"center\"]{left: 50%;transform: translateX(-50%)}:host([contained]) ol{position: absolute;z-index: var(--ds-toast-z-index, 60)}:host([contained]) ol[data-y=\"top\"]{top: var(--ds-toast-offset)}:host([contained]) ol[data-y=\"bottom\"]{bottom: var(--ds-toast-offset)}.toast{--swipe: 0px;position: absolute;left: 0;right: 0;box-sizing: border-box;width: max-content;max-width: 100%;overflow: hidden;border-radius: var(--ds-radius);background: var(--ds-toast-background);color: var(--ds-toast-color);font-family: var(--ds-typeface-regular);font-feature-settings: var(--ds-font-features);font-size: var(--ds-type-size-default);line-height: var(--ds-control-height);letter-spacing: var(--ds-type-letterspacing-default);outline: none;touch-action: none;user-select: none;pointer-events: auto;opacity: 0;transition: transform 400ms ease, opacity 400ms ease}ol[data-y=\"bottom\"] .toast{bottom: 0}ol[data-y=\"top\"] .toast{top: 0}ol[data-x=\"center\"] .toast{margin-inline: auto}ol[data-x=\"left\"] .toast{margin-right: auto}ol[data-x=\"right\"] .toast{margin-left: auto}.toast:focus-visible{outline: 2px solid var(--ds-focus-ring);outline-offset: 1px}.toast[data-mounted=\"false\"]{transform: translateY(calc(var(--lift) * -100%))}.toast[data-mounted=\"true\"]{opacity: 1}ol[data-expanded=\"false\"] .toast[data-mounted=\"true\"]{transform: translateY(calc(var(--lift) * var(--index) * var(--ds-toast-gap) + var(--swipe))) scale(calc(1 - var(--index) * 0.05))}ol[data-expanded=\"false\"] .toast[data-front=\"false\"]{height: var(--front-height)}ol[data-expanded=\"false\"] .toast[data-front=\"false\"] .body{opacity: 0}ol[data-expanded=\"true\"] .toast[data-mounted=\"true\"]{transform: translateY(calc(var(--lift) * (var(--offset) + var(--index) * var(--ds-toast-gap)) + var(--swipe)))}.toast[data-visible=\"false\"]{opacity: 0;pointer-events: none}.toast[data-swiping=\"true\"]{transition: none}.toast[data-removed=\"true\"]{opacity: 0;pointer-events: none}.toast[data-removed=\"true\"][data-front=\"true\"]{transform: translateY(calc(var(--lift) * -100%))}.toast[data-swipe-out=\"true\"]{transform: translateY(calc(var(--swipe) + var(--lift) * -100%))}.body{display: flex;align-items: flex-start;gap: calc(4px * var(--ds-sf));box-sizing: border-box;min-height: var(--ds-control-height);padding-inline: calc(4px * var(--ds-sf));transition: opacity 400ms ease}.icon{display: inline-flex;flex: 0 0 auto;align-items: center;justify-content: center;width: var(--ds-control-height);height: var(--ds-control-height);--ds-icon-color: currentColor}.icon:first-child{margin-left: calc(-4px * var(--ds-sf))}.icon > svg, .close > svg{width: calc(14px * var(--ds-sf));height: calc(14px * var(--ds-sf))}.content{flex: 1 1 auto;min-width: 0}.title, .description{white-space: normal;overflow-wrap: anywhere}.description{opacity: 0.7}.button{flex: 0 0 auto;margin: 0 0 0 calc(4px * var(--ds-sf));padding: 0;border: 0;background: none;color: inherit;font: inherit;letter-spacing: inherit;text-decoration: underline;text-underline-offset: 2px;cursor: pointer}a.button{display: inline-block}.button.cancel{opacity: 0.7}.button:focus-visible, .close:focus-visible{outline: 1px solid currentColor;outline-offset: 1px;border-radius: 1px}.close{display: inline-flex;flex: 0 0 auto;align-items: center;justify-content: center;width: var(--ds-control-height);height: var(--ds-control-height);margin: 0 calc(-4px * var(--ds-sf)) 0 0;padding: 0;border: 0;background: none;color: inherit;cursor: pointer;--ds-icon-color: currentColor}.spinner{width: calc(10px * var(--ds-sf));height: calc(10px * var(--ds-sf));box-sizing: border-box;border: 1px solid color-mix(in srgb, currentColor 25%, transparent);border-top-color: currentColor;border-radius: 50%;animation: ds-toast-spin 720ms linear infinite}@keyframes ds-toast-spin{to{transform: rotate(360deg)}}:host([rich-colors]) .toast[data-type=\"success\"]{--ds-toast-background: var(--ds-apple-green)}:host([rich-colors]) .toast[data-type=\"info\"]{--ds-toast-background: var(--ds-zenith-blue)}:host([rich-colors]) .toast[data-type=\"warning\"]{--ds-toast-background: var(--ds-orange)}:host([rich-colors]) .toast[data-type=\"error\"]{--ds-toast-background: var(--ds-tuned-red)}:host([rich-colors]) .toast:is([data-type=\"success\"], [data-type=\"info\"], [data-type=\"warning\"], [data-type=\"error\"]){--ds-toast-color: var(--ds-black)}@media (prefers-reduced-motion: reduce){.toast, .body{transition: none}.spinner{animation: none}}";
|
|
9
9
|
|
|
10
10
|
export type ToastIconName = "check" | "close" | "notifications" | "warning";
|
|
11
11
|
|
package/DS1/0-face/header.ts
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* draws them); call `startHeaders()` again for one added later.
|
|
5
5
|
*
|
|
6
6
|
* Each header's menu button (`.ds-header__toggle`) opens its navigation and
|
|
7
|
-
* actions in a drawer (`0-face/
|
|
7
|
+
* actions in a drawer (`0-face/attach-overlay.ts`): a panel over the screen with focus
|
|
8
8
|
* kept inside, closed by Escape, its close button, a link or the screen
|
|
9
9
|
* widening, the page's scroll held behind it. The close button is named by the
|
|
10
10
|
* toggle's `data-close-label`, "Close" without one; the page writes both names
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
* </header>
|
|
26
26
|
* <script type="module">import "/ds1/0-face/header.ts";</script>
|
|
27
27
|
*/
|
|
28
|
-
import {
|
|
28
|
+
import { attachOverlay, type Drawer } from "./attach-overlay";
|
|
29
29
|
|
|
30
30
|
const started = new WeakMap<Element, Drawer>();
|
|
31
31
|
|
|
@@ -61,7 +61,7 @@ export function startHeaders(root: ParentNode = document): Drawer[] {
|
|
|
61
61
|
if (started.has(header)) continue;
|
|
62
62
|
const toggle = header.querySelector<HTMLElement>(":scope > .ds-header__toggle");
|
|
63
63
|
if (!toggle) continue;
|
|
64
|
-
const drawer =
|
|
64
|
+
const drawer = attachOverlay({
|
|
65
65
|
toggle,
|
|
66
66
|
content: () => [...header.querySelectorAll(":scope > :is(.ds-header__nav, .ds-header__actions)")],
|
|
67
67
|
closeLabel: toggle.dataset.closeLabel,
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Swipe a sheet or a drawer away: a finger that drags a dialog towards the
|
|
3
3
|
* edge it came from moves it, and letting go far or fast enough dismisses it.
|
|
4
|
-
* `ds-
|
|
5
|
-
* without Lit binds its own `dialog.ds-
|
|
6
|
-
* with `
|
|
4
|
+
* `ds-overlay` binds it for `type="sheet"` and `type="drawer"`; a page
|
|
5
|
+
* without Lit binds its own `dialog.ds-overlay--sheet` (or `--drawer`, with
|
|
6
|
+
* `--start` for the start side) with `bindOverlaySwipe(dialog)`. Plain
|
|
7
|
+
* TypeScript with no framework.
|
|
7
8
|
*
|
|
8
9
|
* It follows the finger as touch does on Apple's platforms:
|
|
9
10
|
*
|
|
@@ -13,26 +14,27 @@
|
|
|
13
14
|
* from the dialog's place on screen, not from where it was going).
|
|
14
15
|
* - A drag along the content's own scroll is the content's: a sheet scrolled
|
|
15
16
|
* down scrolls back up first, a horizontal strip in a drawer scrolls, and a
|
|
16
|
-
* slider, a range input or anything marked `data-ds-
|
|
17
|
+
* slider, a range input or anything marked `data-ds-overlay-no-swipe` keeps
|
|
17
18
|
* its touches.
|
|
18
19
|
* - Dragged the other way, it gives a little and pulls back (rubber-banding).
|
|
19
20
|
* - On release it projects where the throw would come to rest (the
|
|
20
21
|
* deceleration of a scroll, rate 0.998) and dismisses when that is past half
|
|
21
22
|
* its size and the finger is not moving back; otherwise it settles home.
|
|
22
|
-
* - The backdrop fades as the dialog goes (`--ds-
|
|
23
|
+
* - The backdrop fades as the dialog goes (`--ds-overlay-progress`).
|
|
23
24
|
*
|
|
24
25
|
* Dismissing asks first, as a close button does: the element sends
|
|
25
26
|
* `ds-close-request` with the reason `"swipe"`, the native frame a cancelable
|
|
26
27
|
* `ds-close-request` on the dialog, and closes it when no listener prevents
|
|
27
28
|
* that. Declined (a form with unsaved changes asking first), it settles home.
|
|
28
|
-
* The motion itself is
|
|
29
|
+
* The motion itself is overlay.css's, in transform alone.
|
|
29
30
|
*/
|
|
30
31
|
|
|
31
|
-
|
|
32
|
+
/** The edge an overlay comes from and a finger swipes it back to: a sheet's foot, a drawer's side, or none. */
|
|
33
|
+
export type OverlayEdge = "none" | "bottom" | "end" | "start";
|
|
32
34
|
|
|
33
|
-
export interface
|
|
34
|
-
/**
|
|
35
|
-
|
|
35
|
+
export interface OverlaySwipeOptions {
|
|
36
|
+
/** Its edge, read at each touch; from `data-ds-edge` or its class by default. */
|
|
37
|
+
edge?: OverlayEdge | (() => OverlayEdge);
|
|
36
38
|
/**
|
|
37
39
|
* Called when a swipe would dismiss it. By default a cancelable
|
|
38
40
|
* `ds-close-request` (`detail.reason` `"swipe"`) on the dialog, then
|
|
@@ -51,14 +53,15 @@ const project = (velocity: number) => ((velocity / 1000) * DECELERATION) / (1 -
|
|
|
51
53
|
const rubberband = (overshoot: number, dimension: number, constant = 0.55) =>
|
|
52
54
|
(overshoot * dimension * constant) / (dimension + constant * Math.abs(overshoot));
|
|
53
55
|
|
|
54
|
-
const
|
|
56
|
+
const EDGES = new Set<OverlayEdge>(["none", "bottom", "end", "start"]);
|
|
55
57
|
|
|
56
|
-
/**
|
|
57
|
-
export function
|
|
58
|
-
const marked = dialog.getAttribute("data-ds-
|
|
59
|
-
if (marked &&
|
|
60
|
-
|
|
61
|
-
return "
|
|
58
|
+
/** An overlay's edge, from `data-ds-edge` (the element marks it) or its native classes. */
|
|
59
|
+
export function overlayEdge(dialog: HTMLDialogElement): OverlayEdge {
|
|
60
|
+
const marked = dialog.getAttribute("data-ds-edge") as OverlayEdge | null;
|
|
61
|
+
if (marked && EDGES.has(marked)) return marked;
|
|
62
|
+
if (dialog.classList.contains("ds-overlay--sheet")) return "bottom";
|
|
63
|
+
if (dialog.classList.contains("ds-overlay--drawer")) return dialog.classList.contains("ds-overlay--start") ? "start" : "end";
|
|
64
|
+
return "none";
|
|
62
65
|
}
|
|
63
66
|
|
|
64
67
|
type Gesture = {
|
|
@@ -77,10 +80,10 @@ type Gesture = {
|
|
|
77
80
|
};
|
|
78
81
|
|
|
79
82
|
/** The axis the dialog leaves along, and the sign of the way out on it. */
|
|
80
|
-
function direction(dialog: HTMLDialogElement,
|
|
81
|
-
if (
|
|
83
|
+
function direction(dialog: HTMLDialogElement, edge: OverlayEdge): { axis: "x" | "y"; sign: 1 | -1 } {
|
|
84
|
+
if (edge === "bottom" || edge === "none") return { axis: "y", sign: 1 };
|
|
82
85
|
const rtl = dialog.ownerDocument.defaultView?.getComputedStyle(dialog).direction === "rtl";
|
|
83
|
-
const right = (
|
|
86
|
+
const right = (edge === "end") !== rtl;
|
|
84
87
|
return { axis: "x", sign: right ? 1 : -1 };
|
|
85
88
|
}
|
|
86
89
|
|
|
@@ -123,10 +126,10 @@ function presented(dialog: HTMLDialogElement, axis: "x" | "y", sign: 1 | -1): nu
|
|
|
123
126
|
* Let a finger swipe a sheet or drawer away. Returns the function that
|
|
124
127
|
* unbinds it and clears what it set on the dialog.
|
|
125
128
|
*/
|
|
126
|
-
export function
|
|
127
|
-
const
|
|
128
|
-
const given = typeof options.
|
|
129
|
-
return given ??
|
|
129
|
+
export function bindOverlaySwipe(dialog: HTMLDialogElement, options: OverlaySwipeOptions = {}): () => void {
|
|
130
|
+
const edgeNow = (): OverlayEdge => {
|
|
131
|
+
const given = typeof options.edge === "function" ? options.edge() : options.edge;
|
|
132
|
+
return given ?? overlayEdge(dialog);
|
|
130
133
|
};
|
|
131
134
|
let gesture: Gesture | null = null;
|
|
132
135
|
let settleTimer = 0;
|
|
@@ -134,23 +137,23 @@ export function bindDialogSwipe(dialog: HTMLDialogElement, options: DialogSwipeO
|
|
|
134
137
|
const frame = (callback: () => void) => (view?.requestAnimationFrame ? view.requestAnimationFrame(callback) : view?.setTimeout(callback, 16) ?? 0);
|
|
135
138
|
|
|
136
139
|
const write = (offset: number, size: number, sign: 1 | -1) => {
|
|
137
|
-
dialog.style.setProperty("--ds-
|
|
138
|
-
dialog.style.setProperty("--ds-
|
|
140
|
+
dialog.style.setProperty("--ds-overlay-drag", `${sign * offset}px`);
|
|
141
|
+
dialog.style.setProperty("--ds-overlay-progress", String(size > 0 ? Math.min(1, Math.max(0, offset / size)) : 0));
|
|
139
142
|
};
|
|
140
143
|
const clear = () => {
|
|
141
|
-
dialog.style.removeProperty("--ds-
|
|
142
|
-
dialog.style.removeProperty("--ds-
|
|
143
|
-
dialog.removeAttribute("data-ds-
|
|
144
|
+
dialog.style.removeProperty("--ds-overlay-drag");
|
|
145
|
+
dialog.style.removeProperty("--ds-overlay-progress");
|
|
146
|
+
dialog.removeAttribute("data-ds-overlay-dragging");
|
|
144
147
|
};
|
|
145
148
|
/** Home: the stylesheet's transition takes it back from where it is. */
|
|
146
149
|
const settle = () => {
|
|
147
|
-
dialog.removeAttribute("data-ds-
|
|
148
|
-
dialog.style.setProperty("--ds-
|
|
149
|
-
dialog.style.setProperty("--ds-
|
|
150
|
+
dialog.removeAttribute("data-ds-overlay-dragging");
|
|
151
|
+
dialog.style.setProperty("--ds-overlay-drag", "0px");
|
|
152
|
+
dialog.style.setProperty("--ds-overlay-progress", "0");
|
|
150
153
|
};
|
|
151
154
|
|
|
152
155
|
const dismiss = () => {
|
|
153
|
-
dialog.removeAttribute("data-ds-
|
|
156
|
+
dialog.removeAttribute("data-ds-overlay-dragging");
|
|
154
157
|
if (options.onDismiss) options.onDismiss(dialog);
|
|
155
158
|
else {
|
|
156
159
|
const request = new CustomEvent("ds-close-request", { detail: { reason: "swipe" }, bubbles: true, composed: true, cancelable: true });
|
|
@@ -162,17 +165,17 @@ export function bindDialogSwipe(dialog: HTMLDialogElement, options: DialogSwipeO
|
|
|
162
165
|
};
|
|
163
166
|
|
|
164
167
|
const onStart = (event: TouchEvent) => {
|
|
165
|
-
const
|
|
166
|
-
if (!dialog.open ||
|
|
168
|
+
const edge = edgeNow();
|
|
169
|
+
if (!dialog.open || edge === "none") return;
|
|
167
170
|
if (event.touches.length !== 1) {
|
|
168
171
|
if (gesture?.mode === "drag") settle();
|
|
169
172
|
gesture = null;
|
|
170
173
|
return;
|
|
171
174
|
}
|
|
172
175
|
const path = event.composedPath();
|
|
173
|
-
if (path.some(node => node instanceof Element && node.matches('[data-ds-
|
|
176
|
+
if (path.some(node => node instanceof Element && node.matches('[data-ds-overlay-no-swipe], input[type="range"], [role="slider"]'))) return;
|
|
174
177
|
const touch = event.touches[0]!;
|
|
175
|
-
const { axis, sign } = direction(dialog,
|
|
178
|
+
const { axis, sign } = direction(dialog, edge);
|
|
176
179
|
gesture = { id: touch.identifier, x: touch.clientX, y: touch.clientY, base: Math.max(0, presented(dialog, axis, sign)), start: 0, mode: "pending", offset: 0, samples: [], target: event.target, path };
|
|
177
180
|
};
|
|
178
181
|
|
|
@@ -180,9 +183,9 @@ export function bindDialogSwipe(dialog: HTMLDialogElement, options: DialogSwipeO
|
|
|
180
183
|
if (!gesture || gesture.mode === "ignore") return;
|
|
181
184
|
const touch = [...event.touches].find(each => each.identifier === gesture!.id);
|
|
182
185
|
if (!touch) return;
|
|
183
|
-
const
|
|
184
|
-
if (
|
|
185
|
-
const { axis, sign } = direction(dialog,
|
|
186
|
+
const edge = edgeNow();
|
|
187
|
+
if (edge === "none") { gesture = null; return; }
|
|
188
|
+
const { axis, sign } = direction(dialog, edge);
|
|
186
189
|
const dx = touch.clientX - gesture.x, dy = touch.clientY - gesture.y;
|
|
187
190
|
const along = axis === "y" ? dy * sign : dx * sign;
|
|
188
191
|
const across = axis === "y" ? dx : dy;
|
|
@@ -195,7 +198,7 @@ export function bindDialogSwipe(dialog: HTMLDialogElement, options: DialogSwipeO
|
|
|
195
198
|
gesture.mode = "drag";
|
|
196
199
|
gesture.start = along;
|
|
197
200
|
settleTimer++;
|
|
198
|
-
dialog.setAttribute("data-ds-
|
|
201
|
+
dialog.setAttribute("data-ds-overlay-dragging", "");
|
|
199
202
|
}
|
|
200
203
|
event.preventDefault();
|
|
201
204
|
const rect = dialog.getBoundingClientRect();
|
|
@@ -214,8 +217,8 @@ export function bindDialogSwipe(dialog: HTMLDialogElement, options: DialogSwipeO
|
|
|
214
217
|
const ended = gesture;
|
|
215
218
|
gesture = null;
|
|
216
219
|
if (ended.mode !== "drag") return;
|
|
217
|
-
const
|
|
218
|
-
const { axis } = direction(dialog,
|
|
220
|
+
const edge = edgeNow();
|
|
221
|
+
const { axis } = direction(dialog, edge);
|
|
219
222
|
const rect = dialog.getBoundingClientRect();
|
|
220
223
|
const size = axis === "y" ? rect.height : rect.width;
|
|
221
224
|
// The speed of the last tenth of a second: a finger held still before letting go throws nothing.
|
|
@@ -225,7 +228,7 @@ export function bindDialogSwipe(dialog: HTMLDialogElement, options: DialogSwipeO
|
|
|
225
228
|
const elapsed = first && last ? last.time - first.time : 0;
|
|
226
229
|
const velocity = first && last && elapsed > 0 ? ((last.offset - first.offset) / elapsed) * 1000 : 0;
|
|
227
230
|
const resting = ended.offset + project(velocity);
|
|
228
|
-
if (event.type !== "touchcancel" &&
|
|
231
|
+
if (event.type !== "touchcancel" && edge !== "none" && ended.offset > 0 && resting > size / 2 && velocity > -100) dismiss();
|
|
229
232
|
else settle();
|
|
230
233
|
};
|
|
231
234
|
|
package/DS1/0-face/toaster.ts
CHANGED
|
@@ -98,7 +98,9 @@ function icon(name: string, className: string): HTMLSpanElement | undefined {
|
|
|
98
98
|
class PageToaster {
|
|
99
99
|
readonly host = element("div", { "data-ds-toaster": "" });
|
|
100
100
|
private readonly section = element("section", { "aria-live": "polite", "aria-relevant": "additions text", "aria-atomic": "false" });
|
|
101
|
-
private readonly list = element("ol", { tabindex: "-1" });
|
|
101
|
+
private readonly list = element("ol", { tabindex: "-1", popover: "manual" });
|
|
102
|
+
/** The front toast the stack was last raised for. */
|
|
103
|
+
private raisedFor: ToastId | undefined;
|
|
102
104
|
private items: Item[] = [];
|
|
103
105
|
private heights = new Map<ToastId, number>();
|
|
104
106
|
private timers = new Map<ToastId, Timer>();
|
|
@@ -267,7 +269,9 @@ class PageToaster {
|
|
|
267
269
|
toast.addEventListener("pointerdown", event => {
|
|
268
270
|
const now = this.live.find(entry => entry.data.id === id);
|
|
269
271
|
if (!now || now.data.dismissible === false || now.data.type === "loading") return;
|
|
270
|
-
|
|
272
|
+
// Controls can contain icons with their own shadow root. Inspect the whole
|
|
273
|
+
// event path so swipe capture never steals their click.
|
|
274
|
+
if (event.composedPath().some(node => node instanceof Element && node.matches("button, a"))) return;
|
|
271
275
|
toast.setPointerCapture?.(event.pointerId);
|
|
272
276
|
this.swipe = { id, start: event.clientY, time: Date.now(), amount: 0 };
|
|
273
277
|
this.interacting = true;
|
|
@@ -298,7 +302,21 @@ class PageToaster {
|
|
|
298
302
|
}
|
|
299
303
|
|
|
300
304
|
/** Draw the stack: every toast's place, and each toast's words where they changed. */
|
|
305
|
+
/** In the top layer, above an open sheet or modal, as `<ds-toast>`: shown again for each new front toast. */
|
|
306
|
+
private raise() {
|
|
307
|
+
const front = this.live[0]?.data.id;
|
|
308
|
+
if (front === this.raisedFor || typeof this.list.showPopover !== "function" || !this.list.isConnected) return;
|
|
309
|
+
this.raisedFor = front;
|
|
310
|
+
try {
|
|
311
|
+
if (this.list.matches(":popover-open")) this.list.hidePopover();
|
|
312
|
+
if (front !== undefined) this.list.showPopover();
|
|
313
|
+
} catch {
|
|
314
|
+
// A browser without popovers: it stays as it is drawn.
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
|
|
301
318
|
render() {
|
|
319
|
+
this.raise();
|
|
302
320
|
if (!this.live.length) { this.hovered = false; this.focused = false; }
|
|
303
321
|
this.section.setAttribute("aria-label", `${settings.label ?? feedbackWord("Notifications")} alt+T`);
|
|
304
322
|
this.host.toggleAttribute("rich-colors", !!settings.richColors);
|
package/DS1/1-root/cjk.css
CHANGED
|
@@ -88,6 +88,10 @@
|
|
|
88
88
|
takes Orange's dim (#4c3f1a, its pastel in dark mode); dark paper takes
|
|
89
89
|
Orange itself. Both read above 4.5:1 as 14px text. */
|
|
90
90
|
--ds-warning-color: light-dark(#4c3f1a, var(--ds-orange));
|
|
91
|
+
/* What went well or is done. Apple green is too light for text and marks on
|
|
92
|
+
light paper, so light paper takes it deepened with the ink (Money's green
|
|
93
|
+
for money in); dark paper takes Apple green itself. */
|
|
94
|
+
--ds-success-color: light-dark(color-mix(in oklab, var(--ds-apple-green) 30%, var(--ds-black)), var(--ds-apple-green));
|
|
91
95
|
--ds-border-color: light-dark(#d8d8d8, #3d3d3d);
|
|
92
96
|
--ds-fill: light-dark(var(--ds-slate-light), var(--ds-slate-dark));
|
|
93
97
|
--ds-fill-hover: light-dark(#e2e2e2, #404040);
|
package/DS1/1-root/controls.css
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
@import "./text.css";
|
|
4
4
|
@import "./empty.css";
|
|
5
5
|
@import "./notice.css";
|
|
6
|
-
@import "./
|
|
6
|
+
@import "./overlay.css";
|
|
7
7
|
@import "./header.css";
|
|
8
8
|
@import "./footer.css";
|
|
9
9
|
@import "./document.css";
|
|
@@ -481,25 +481,27 @@ textarea.ds-input {
|
|
|
481
481
|
}
|
|
482
482
|
|
|
483
483
|
/*
|
|
484
|
-
* `.ds-
|
|
485
|
-
* twenty squares wide, a square of padding, square corners,
|
|
486
|
-
*
|
|
487
|
-
*
|
|
484
|
+
* `.ds-overlay` is the `ds-overlay` element's frame on a native `<dialog>`, a
|
|
485
|
+
* modal by default: twenty squares wide, a square of padding, square corners,
|
|
486
|
+
* a hairline and the paper. `ds-overlay--sheet`, `ds-overlay--drawer` (with
|
|
487
|
+
* `ds-overlay--start` for the start side) and `ds-overlay--screen` are its
|
|
488
|
+
* other types (overlay.css). Its header, title, description, footer and close button are marked
|
|
489
|
+
* with data-ds-overlay, as inside the element.
|
|
488
490
|
*/
|
|
489
491
|
|
|
490
|
-
dialog.ds-
|
|
492
|
+
dialog.ds-overlay {
|
|
491
493
|
box-sizing: border-box;
|
|
492
494
|
position: fixed;
|
|
493
495
|
inset: 0;
|
|
494
|
-
width: var(--ds-
|
|
495
|
-
max-width: var(--ds-
|
|
496
|
-
max-height: var(--ds-
|
|
497
|
-
height: var(--ds-
|
|
496
|
+
width: var(--ds-overlay-width, calc(20 * var(--ds-unit)));
|
|
497
|
+
max-width: var(--ds-overlay-max-width, calc(100vw - 2 * var(--ds-unit)));
|
|
498
|
+
max-height: var(--ds-overlay-max-height, calc(100dvh - 2 * var(--ds-unit)));
|
|
499
|
+
height: var(--ds-overlay-height, fit-content);
|
|
498
500
|
margin: auto;
|
|
499
|
-
padding: var(--ds-
|
|
500
|
-
border: var(--ds-
|
|
501
|
+
padding: var(--ds-overlay-padding, var(--ds-unit));
|
|
502
|
+
border: var(--ds-overlay-border, 1px solid var(--ds-border-color));
|
|
501
503
|
border-radius: 0;
|
|
502
|
-
background: var(--ds-
|
|
504
|
+
background: var(--ds-overlay-paper, var(--ds-paper));
|
|
503
505
|
color: var(--ds-text-color);
|
|
504
506
|
box-shadow: none;
|
|
505
507
|
overflow: auto;
|
|
@@ -512,40 +514,45 @@ dialog.ds-dialog {
|
|
|
512
514
|
letter-spacing: var(--ds-type-letterspacing-default);
|
|
513
515
|
}
|
|
514
516
|
|
|
515
|
-
dialog.ds-
|
|
517
|
+
dialog.ds-overlay[open] {
|
|
516
518
|
display: flex;
|
|
517
519
|
flex-direction: column;
|
|
518
520
|
gap: var(--ds-unit);
|
|
519
521
|
}
|
|
520
522
|
|
|
521
|
-
dialog.ds-
|
|
523
|
+
dialog.ds-overlay::backdrop {
|
|
522
524
|
background: color-mix(in srgb, var(--ds-black) 40%, transparent);
|
|
523
525
|
}
|
|
524
526
|
|
|
525
|
-
.ds-
|
|
527
|
+
.ds-overlay [data-ds-overlay="header"] {
|
|
526
528
|
display: flex;
|
|
527
529
|
flex-direction: column;
|
|
528
|
-
padding-right: var(--ds-unit);
|
|
529
530
|
}
|
|
530
531
|
|
|
531
|
-
.
|
|
532
|
+
/* The header keeps the last square of its row for the close button, only when it has one. */
|
|
533
|
+
dialog.ds-overlay:has(> [data-ds-overlay="close"]) [data-ds-overlay="header"] {
|
|
534
|
+
padding-inline-end: var(--ds-unit);
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
/* The title on its square's lower sixteen, as every row's words. */
|
|
538
|
+
.ds-overlay [data-ds-overlay="title"] {
|
|
532
539
|
display: flex;
|
|
533
|
-
align-items:
|
|
540
|
+
align-items: flex-end;
|
|
534
541
|
min-height: var(--ds-row-height);
|
|
535
542
|
margin: 0;
|
|
536
543
|
color: var(--ds-text-color);
|
|
537
544
|
font: inherit;
|
|
538
545
|
font-weight: var(--ds-type-weight-bold);
|
|
539
|
-
line-height: var(--ds-
|
|
546
|
+
line-height: var(--ds-type-lineheight-default);
|
|
540
547
|
}
|
|
541
548
|
|
|
542
|
-
.ds-
|
|
549
|
+
.ds-overlay [data-ds-overlay="description"] {
|
|
543
550
|
margin: 0;
|
|
544
551
|
color: var(--ds-text-color-muted);
|
|
545
552
|
font: inherit;
|
|
546
553
|
}
|
|
547
554
|
|
|
548
|
-
.ds-
|
|
555
|
+
.ds-overlay [data-ds-overlay="footer"] {
|
|
549
556
|
display: flex;
|
|
550
557
|
flex-wrap: wrap;
|
|
551
558
|
align-items: center;
|
|
@@ -554,7 +561,7 @@ dialog.ds-dialog::backdrop {
|
|
|
554
561
|
min-height: var(--ds-row-height);
|
|
555
562
|
}
|
|
556
563
|
|
|
557
|
-
.ds-
|
|
564
|
+
.ds-overlay [data-ds-overlay="close"] {
|
|
558
565
|
position: absolute;
|
|
559
566
|
top: var(--ds-unit);
|
|
560
567
|
right: var(--ds-unit);
|
|
@@ -562,17 +569,17 @@ dialog.ds-dialog::backdrop {
|
|
|
562
569
|
|
|
563
570
|
/* A field in the frame, as a prompt has: its label one row high, the field
|
|
564
571
|
* one row high beneath it and as wide as the frame, two squares in all. */
|
|
565
|
-
.ds-
|
|
572
|
+
.ds-overlay [data-ds-overlay="field"] {
|
|
566
573
|
display: flex;
|
|
567
574
|
flex-direction: column;
|
|
568
575
|
}
|
|
569
576
|
|
|
570
|
-
.ds-
|
|
577
|
+
.ds-overlay [data-ds-overlay="label"] {
|
|
571
578
|
min-height: var(--ds-row-height);
|
|
572
579
|
line-height: var(--ds-row-height);
|
|
573
580
|
}
|
|
574
581
|
|
|
575
|
-
.ds-
|
|
582
|
+
.ds-overlay [data-ds-overlay="field"] > .ds-input {
|
|
576
583
|
--ds-input-height: var(--ds-row-height);
|
|
577
584
|
width: 100%;
|
|
578
585
|
}
|