@0001-labs/design-system-1 0.3.0-alpha.16 → 0.3.0-alpha.18

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.
Files changed (152) hide show
  1. package/DS1/0-face/{drawer.ts → attach-overlay.ts} +17 -17
  2. package/DS1/0-face/{dialogs.ts → feedback-overlays.ts} +8 -8
  3. package/DS1/0-face/feedback.ts +3 -3
  4. package/DS1/0-face/header.ts +3 -3
  5. package/DS1/0-face/{dialog-swipe.ts → overlay-swipe.ts} +47 -44
  6. package/DS1/1-root/cjk.css +1 -1
  7. package/DS1/1-root/context.css +151 -0
  8. package/DS1/1-root/controls.css +35 -27
  9. package/DS1/1-root/fonts.css +1 -1
  10. package/DS1/1-root/header.css +4 -4
  11. package/DS1/1-root/one.css +1 -1
  12. package/DS1/1-root/one.semantic.css +1 -1
  13. package/DS1/1-root/overlay.css +283 -0
  14. package/DS1/1-root/row.css +65 -10
  15. package/DS1/1-root/table.css +12 -0
  16. package/DS1/2-core/ds-image.ts +42 -0
  17. package/DS1/2-core/generated/ds-icon-map.ts +6 -0
  18. package/DS1/2-core/generated/icons-extra.ts +3 -0
  19. package/DS1/2-core/generated/icons-immediate.ts +1 -1
  20. package/DS1/2-core/styles/ds-image.css +14 -0
  21. package/DS1/3-unit/context-trigger.ts +4 -1
  22. package/DS1/3-unit/ds-calendar-large.ts +5 -1
  23. package/DS1/3-unit/ds-context.ts +102 -0
  24. package/DS1/3-unit/ds-menu.ts +61 -15
  25. package/DS1/3-unit/ds-row.ts +6 -2
  26. package/DS1/3-unit/ds-select.ts +212 -0
  27. package/DS1/3-unit/ds-token-field.ts +2 -2
  28. package/DS1/3-unit/styles/ds-calendar-large.css +13 -0
  29. package/DS1/3-unit/styles/ds-context.css +109 -0
  30. package/DS1/3-unit/styles/ds-row.css +15 -0
  31. package/DS1/{2-core → 3-unit}/styles/ds-select.css +71 -6
  32. package/DS1/3-unit/styles/ds-tabs.css +1 -1
  33. package/DS1/4-page/ds-header.ts +5 -5
  34. package/DS1/4-page/{ds-dialog.ts → ds-overlay.ts} +77 -51
  35. package/DS1/4-page/ds-page.ts +5 -5
  36. package/DS1/4-page/styles/ds-header.css +2 -2
  37. package/DS1/4-page/styles/ds-overlay.css +88 -0
  38. package/DS1/4-page/styles/ds-page.css +1 -1
  39. package/DS1/components.ts +2 -1
  40. package/DS1/index.ts +5 -2
  41. package/DS1/react/choice.ts +2 -14
  42. package/DS1/react/{context-menu.ts → context.ts} +35 -35
  43. package/DS1/react/image.ts +10 -0
  44. package/DS1/react/{menu.ts → menu-engine.ts} +10 -3
  45. package/DS1/react/{dialog.ts → overlay.ts} +40 -37
  46. package/DS1/react/select.ts +118 -0
  47. package/DS1/x-icon/dot-happy.svg +3 -0
  48. package/DS1/x-icon/dot-neutral.svg +3 -0
  49. package/DS1/x-icon/dot-sad.svg +3 -0
  50. package/dist/0-face/{drawer.d.ts → attach-overlay.d.ts} +8 -8
  51. package/dist/0-face/attach-overlay.d.ts.map +1 -0
  52. package/dist/0-face/{drawer.js → attach-overlay.js} +15 -15
  53. package/dist/0-face/{dialogs.d.ts → feedback-overlays.d.ts} +1 -1
  54. package/dist/0-face/feedback-overlays.d.ts.map +1 -0
  55. package/dist/0-face/{dialogs.js → feedback-overlays.js} +8 -8
  56. package/dist/0-face/feedback.d.ts +3 -3
  57. package/dist/0-face/feedback.d.ts.map +1 -1
  58. package/dist/0-face/feedback.js +2 -2
  59. package/dist/0-face/header.d.ts +2 -2
  60. package/dist/0-face/header.d.ts.map +1 -1
  61. package/dist/0-face/header.js +3 -3
  62. package/dist/0-face/{dialog-swipe.d.ts → overlay-swipe.d.ts} +16 -14
  63. package/dist/0-face/overlay-swipe.d.ts.map +1 -0
  64. package/dist/0-face/{dialog-swipe.js → overlay-swipe.js} +44 -42
  65. package/dist/2-core/ds-image.d.ts +59 -0
  66. package/dist/2-core/ds-image.d.ts.map +1 -0
  67. package/dist/2-core/ds-image.js +42 -0
  68. package/dist/2-core/generated/ds-icon-map.d.ts +4 -1
  69. package/dist/2-core/generated/ds-icon-map.d.ts.map +1 -1
  70. package/dist/2-core/generated/ds-icon-map.js +6 -0
  71. package/dist/2-core/generated/icons-extra.d.ts.map +1 -1
  72. package/dist/2-core/generated/icons-extra.js +3 -0
  73. package/dist/2-core/generated/icons-immediate.d.ts +1 -1
  74. package/dist/2-core/generated/icons-immediate.d.ts.map +1 -1
  75. package/dist/2-core/generated/icons-immediate.js +1 -1
  76. package/dist/3-unit/context-trigger.d.ts +2 -0
  77. package/dist/3-unit/context-trigger.d.ts.map +1 -1
  78. package/dist/3-unit/context-trigger.js +3 -1
  79. package/dist/3-unit/ds-calendar-large.d.ts +5 -0
  80. package/dist/3-unit/ds-calendar-large.d.ts.map +1 -1
  81. package/dist/3-unit/ds-calendar-large.js +4 -1
  82. package/dist/3-unit/ds-context.d.ts +103 -0
  83. package/dist/3-unit/ds-context.d.ts.map +1 -0
  84. package/dist/3-unit/ds-context.js +104 -0
  85. package/dist/3-unit/ds-menu.d.ts +23 -4
  86. package/dist/3-unit/ds-menu.d.ts.map +1 -1
  87. package/dist/3-unit/ds-menu.js +64 -11
  88. package/dist/3-unit/ds-row.d.ts +6 -0
  89. package/dist/3-unit/ds-row.d.ts.map +1 -1
  90. package/dist/3-unit/ds-row.js +5 -2
  91. package/dist/3-unit/ds-select.d.ts +131 -0
  92. package/dist/3-unit/ds-select.d.ts.map +1 -0
  93. package/dist/3-unit/ds-select.js +222 -0
  94. package/dist/3-unit/ds-token-field.d.ts +1 -1
  95. package/dist/3-unit/ds-token-field.d.ts.map +1 -1
  96. package/dist/3-unit/ds-token-field.js +2 -2
  97. package/dist/4-page/ds-header.js +5 -5
  98. package/dist/4-page/{ds-dialog.d.ts → ds-overlay.d.ts} +43 -21
  99. package/dist/4-page/ds-overlay.d.ts.map +1 -0
  100. package/dist/4-page/{ds-dialog.js → ds-overlay.js} +64 -46
  101. package/dist/4-page/ds-page.d.ts +1 -1
  102. package/dist/4-page/ds-page.js +5 -5
  103. package/dist/components.d.ts +2 -1
  104. package/dist/components.d.ts.map +1 -1
  105. package/dist/components.js +2 -1
  106. package/dist/design-system-1.bundle.js +7760 -7369
  107. package/dist/design-system-1.bundle.js.map +1 -1
  108. package/dist/design-system-1.bundle.min.js +5003 -4785
  109. package/dist/design-system-1.bundle.min.js.map +1 -1
  110. package/dist/design-system-1.css +1 -1
  111. package/dist/feedback.js +7 -7
  112. package/dist/feedback.js.map +1 -1
  113. package/dist/{icons-extra-CiDDs7_-.js → icons-extra-BbbpXocy.js} +10 -1
  114. package/dist/{icons-extra-CiDDs7_-.js.map → icons-extra-BbbpXocy.js.map} +1 -1
  115. package/dist/{icons-extra-hauwCx07.js → icons-extra-C0E3yQpX.js} +4 -1
  116. package/dist/{icons-extra-hauwCx07.js.map → icons-extra-C0E3yQpX.js.map} +1 -1
  117. package/dist/index.d.ts +4 -2
  118. package/dist/index.d.ts.map +1 -1
  119. package/dist/index.js +4 -2
  120. package/dist/react/choice.d.ts +2 -14
  121. package/dist/react/choice.d.ts.map +1 -1
  122. package/dist/react/choice.js +1 -4
  123. package/dist/react/{context-menu.d.ts → context.d.ts} +22 -22
  124. package/dist/react/context.d.ts.map +1 -0
  125. package/dist/react/{context-menu.js → context.js} +29 -29
  126. package/dist/react/image.d.ts +29 -0
  127. package/dist/react/image.d.ts.map +1 -0
  128. package/dist/react/image.js +4 -0
  129. package/dist/react/{menu.d.ts → menu-engine.d.ts} +11 -2
  130. package/dist/react/menu-engine.d.ts.map +1 -0
  131. package/dist/react/{menu.js → menu-engine.js} +6 -2
  132. package/dist/react/{dialog.d.ts → overlay.d.ts} +25 -23
  133. package/dist/react/overlay.d.ts.map +1 -0
  134. package/dist/react/{dialog.js → overlay.js} +24 -23
  135. package/dist/react/select.d.ts +62 -0
  136. package/dist/react/select.d.ts.map +1 -0
  137. package/dist/react/select.js +80 -0
  138. package/package.json +1 -1
  139. package/DS1/1-root/dialog-placement.css +0 -193
  140. package/DS1/1-root/drawer.css +0 -83
  141. package/DS1/2-core/ds-select.ts +0 -44
  142. package/DS1/4-page/styles/ds-dialog.css +0 -94
  143. package/dist/0-face/dialog-swipe.d.ts.map +0 -1
  144. package/dist/0-face/dialogs.d.ts.map +0 -1
  145. package/dist/0-face/drawer.d.ts.map +0 -1
  146. package/dist/2-core/ds-select.d.ts +0 -30
  147. package/dist/2-core/ds-select.d.ts.map +0 -1
  148. package/dist/2-core/ds-select.js +0 -45
  149. package/dist/4-page/ds-dialog.d.ts.map +0 -1
  150. package/dist/react/context-menu.d.ts.map +0 -1
  151. package/dist/react/dialog.d.ts.map +0 -1
  152. 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
- * `attachDrawer()` ties a toggle button to the parts it folds away (a
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-dialog ds-drawer">` laid over the screen
9
- * (1-root/drawer.css draws it), shown modally so the rest of the page is
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 = attachDrawer({ toggle, content: [nav, actions], closeLabel: "Close" });
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-drawer__close`. A
33
- * `<dialog class="ds-dialog ds-drawer">` is made beside the toggle when none
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 attachDrawer(options: DrawerOptions): Drawer {
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-drawer__close`. */
127
- let closeButton: HTMLElement | null = options.panel?.querySelector<HTMLElement>(".ds-drawer__close") ?? null;
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-dialog ds-drawer";
130
+ panel.className = "ds-overlay ds-overlay--screen";
131
131
  const bar = document.createElement("div");
132
- bar.className = "ds-drawer__bar";
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-drawer__toggle", "ds-header__toggle");
141
- closeButton.classList.add("ds-drawer__close");
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-drawer");
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-drawer-${Math.random().toString(36).slice(2, 10)}`;
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-drawer__toggle");
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-drawer");
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-dialog` frame from `1-root/controls.css`
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-dialog",
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-dialog": "header" });
109
- header.appendChild(element("h2", { id: `${id}-title`, "data-ds-dialog": "title" }, spec.title));
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-dialog": "description" }, spec.text));
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-dialog": "field" });
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-dialog": "label" }, spec.field.label));
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-dialog": "footer" });
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);
@@ -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-dialog` frame from `1-root/controls.css`, which the page loads.
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 "./dialogs";
36
- export type { AlertOptions, ConfirmOptions, PromptOptions } from "./dialogs";
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";
@@ -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/drawer.ts`): a panel over the screen with focus
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 { attachDrawer, type Drawer } from "./drawer";
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 = attachDrawer({
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-dialog` binds it for `placement="sheet"`, `"end"` and `"start"`; a page
5
- * without Lit binds its own `dialog.ds-dialog--sheet` (or `--end`, `--start`)
6
- * with `bindDialogSwipe(dialog)`. Plain TypeScript with no framework.
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-dialog-no-swipe` keeps
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-dialog-progress`).
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 dialog-placement.css's, in transform alone.
29
+ * The motion itself is overlay.css's, in transform alone.
29
30
  */
30
31
 
31
- export type DialogPlacement = "center" | "sheet" | "end" | "start";
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 DialogSwipeOptions {
34
- /** Where the dialog stands, read at each touch; from its class or `data-ds-placement` by default. */
35
- placement?: DialogPlacement | (() => DialogPlacement);
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 PLACEMENTS = new Set<DialogPlacement>(["center", "sheet", "end", "start"]);
56
+ const EDGES = new Set<OverlayEdge>(["none", "bottom", "end", "start"]);
55
57
 
56
- /** A dialog's placement, from `data-ds-placement` or its `ds-dialog--` class. */
57
- export function dialogPlacement(dialog: HTMLDialogElement): DialogPlacement {
58
- const marked = dialog.getAttribute("data-ds-placement") as DialogPlacement | null;
59
- if (marked && PLACEMENTS.has(marked)) return marked;
60
- for (const placement of ["sheet", "end", "start"] as const) if (dialog.classList.contains(`ds-dialog--${placement}`)) return placement;
61
- return "center";
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, placement: DialogPlacement): { axis: "x" | "y"; sign: 1 | -1 } {
81
- if (placement === "sheet") return { axis: "y", sign: 1 };
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 = (placement === "end") !== rtl;
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 bindDialogSwipe(dialog: HTMLDialogElement, options: DialogSwipeOptions = {}): () => void {
127
- const placementNow = (): DialogPlacement => {
128
- const given = typeof options.placement === "function" ? options.placement() : options.placement;
129
- return given ?? dialogPlacement(dialog);
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-dialog-drag", `${sign * offset}px`);
138
- dialog.style.setProperty("--ds-dialog-progress", String(size > 0 ? Math.min(1, Math.max(0, offset / size)) : 0));
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-dialog-drag");
142
- dialog.style.removeProperty("--ds-dialog-progress");
143
- dialog.removeAttribute("data-ds-dialog-dragging");
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-dialog-dragging");
148
- dialog.style.setProperty("--ds-dialog-drag", "0px");
149
- dialog.style.setProperty("--ds-dialog-progress", "0");
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-dialog-dragging");
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 placement = placementNow();
166
- if (!dialog.open || placement === "center") return;
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-dialog-no-swipe], input[type="range"], [role="slider"]'))) return;
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, placement);
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 placement = placementNow();
184
- if (placement === "center") { gesture = null; return; }
185
- const { axis, sign } = direction(dialog, placement);
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-dialog-dragging", "");
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 placement = placementNow();
218
- const { axis } = direction(dialog, placement === "center" ? "sheet" : placement);
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" && placement !== "center" && ended.offset > 0 && resting > size / 2 && velocity > -100) dismiss();
231
+ if (event.type !== "touchcancel" && edge !== "none" && ended.offset > 0 && resting > size / 2 && velocity > -100) dismiss();
229
232
  else settle();
230
233
  };
231
234
 
@@ -1,4 +1,4 @@
1
- /* version 0.3.0-alpha.16 */
1
+ /* version 0.3.0-alpha.18 */
2
2
 
3
3
  /* Noto Sans for Japanese and Chinese, for a product that shows them. Opt in:
4
4
  link it beside one.css or fonts.css, or ask Sync 1 for "cjk": true. Without
@@ -0,0 +1,151 @@
1
+ @import "./component-metrics.css";
2
+
3
+ /*
4
+ * `.ds-context` is the `ds-context` element's look on a native menu, for a page
5
+ * without Lit or a menu a library draws: Ezo's gap face. The rows carry the
6
+ * colour and the menu only cuts the outer silhouette, so the page shows
7
+ * through a pixel between rows and two between groups. The ink is the rows'
8
+ * fill and the paper their words, turned over with the theme as the tooltip
9
+ * is. Every row is 19px with the pixel under it, a square's pitch, its words
10
+ * on the sixteen and an icon at its end. A Select's menu is the light face,
11
+ * `.ds-menu`; a Context's is this dark one.
12
+ *
13
+ * ds-context the menu (`role="menu"`); its rows are its buttons,
14
+ * links and menu items, or `ds-context__item`
15
+ * ds-context__item a row; `--danger` (or `data-danger`) in red
16
+ * ds-context__label a heading row, muted
17
+ * ds-context__shortcut a shortcut at the row's end, muted
18
+ * ds-context__separator a group gap (or an `hr`, or `role="separator"`):
19
+ * never a line; first, last or doubled it is quiet
20
+ * [data-ds-refused] a refused change: the row turns red
21
+ *
22
+ * The page places and opens it (a popover at the pointer, say) and owns its
23
+ * keys; the faces of hover, focus, disabled and the wait of a pending menu
24
+ * (`aria-busy="true"`) are here. Tokens as the element's: --ds-context-fill,
25
+ * --ds-context-ink, --ds-context-hover, --ds-context-danger,
26
+ * --ds-context-refused, --ds-context-radius.
27
+ */
28
+
29
+ .ds-context {
30
+ --ds-context-row: calc(var(--ds-row-height) - 1px);
31
+ --ds-context-fill: light-dark(var(--ds-black, #1e1e1e), var(--ds-white, #ffffff));
32
+ --ds-context-ink: light-dark(var(--ds-white, #ffffff), var(--ds-black, #1e1e1e));
33
+ --ds-context-hover: color-mix(in srgb, var(--ds-context-fill) 78%, var(--ds-context-ink));
34
+ --ds-context-danger: light-dark(var(--ds-tuned-red), var(--ds-red-for-blood));
35
+ --ds-context-refused: var(--ds-tuned-red);
36
+ box-sizing: border-box;
37
+ display: flex;
38
+ flex-direction: column;
39
+ gap: 1px;
40
+ width: var(--ds-menu-width, max-content);
41
+ min-width: calc(4 * var(--ds-unit));
42
+ max-height: var(--ds-menu-max-height, calc(16 * var(--ds-unit)));
43
+ overflow-y: auto;
44
+ margin: 0;
45
+ padding: 0;
46
+ border: 0;
47
+ background: transparent;
48
+ box-shadow: none;
49
+ clip-path: inset(0 round var(--ds-context-radius, var(--ds-radius, 4px)));
50
+ color: var(--ds-context-ink);
51
+ font-family: var(--ds-typeface-regular);
52
+ font-feature-settings: var(--ds-font-features);
53
+ font-size: var(--ds-type-size-default);
54
+ font-weight: var(--ds-type-weight-default);
55
+ line-height: var(--ds-type-lineheight-default);
56
+ letter-spacing: var(--ds-type-letterspacing-default);
57
+ list-style: none;
58
+ }
59
+
60
+ .ds-context > [role="group"] {
61
+ display: contents;
62
+ }
63
+
64
+ /* A row: whatever the page or library styles its buttons with, one row on the sixteen. */
65
+ .ds-context :is(.ds-context__item, :is(.ds-context, .ds-context > [role="group"]) > :is(button, a, li, [role^="menuitem"]):not(.ds-context__label, .ds-context__separator, [role="separator"])) {
66
+ box-sizing: border-box !important;
67
+ display: flex !important;
68
+ flex: 0 0 auto !important;
69
+ align-items: flex-end !important;
70
+ justify-content: space-between !important;
71
+ gap: calc(4px * var(--ds-sf));
72
+ width: 100%;
73
+ height: var(--ds-context-row) !important;
74
+ min-height: var(--ds-context-row) !important;
75
+ max-height: var(--ds-context-row) !important;
76
+ margin: 0 !important;
77
+ padding: 0 calc(4px * var(--ds-sf)) !important;
78
+ border: 0 !important;
79
+ border-radius: 0 !important;
80
+ background: var(--ds-context-fill) !important;
81
+ color: var(--ds-context-ink) !important;
82
+ font: inherit;
83
+ line-height: var(--ds-type-lineheight-default) !important;
84
+ letter-spacing: inherit;
85
+ text-align: start;
86
+ text-decoration: none;
87
+ white-space: nowrap;
88
+ cursor: pointer;
89
+ outline: none;
90
+ appearance: none;
91
+ -webkit-tap-highlight-color: transparent;
92
+ }
93
+
94
+ .ds-context :is(.ds-context__item, :is(.ds-context, .ds-context > [role="group"]) > :is(button, a, li, [role^="menuitem"])):is(:hover, :focus-visible, [data-highlighted]):not(:disabled, [aria-disabled="true"], [data-disabled]) {
95
+ background: var(--ds-context-hover) !important;
96
+ }
97
+
98
+ .ds-context :is(.ds-context__item, :is(.ds-context, .ds-context > [role="group"]) > :is(button, a, li, [role^="menuitem"])):focus-visible {
99
+ box-shadow: inset 0 0 0 2px var(--ds-focus-ring);
100
+ }
101
+
102
+ .ds-context :is(.ds-context__item, :is(.ds-context, .ds-context > [role="group"]) > :is(button, a, li, [role^="menuitem"])):is(:disabled, [aria-disabled="true"], [data-disabled]) {
103
+ cursor: default;
104
+ opacity: 0.5;
105
+ }
106
+
107
+ .ds-context[aria-busy="true"] > * {
108
+ cursor: wait !important;
109
+ }
110
+
111
+ /* Danger is red, on either fill. */
112
+ .ds-context :is(.ds-context__item, :is(.ds-context, .ds-context > [role="group"]) > :is(button, a, li, [role^="menuitem"])):is(.ds-context__item--danger, [data-danger], [data-variant="destructive"]) {
113
+ color: var(--ds-context-danger) !important;
114
+ }
115
+
116
+ /* A refused change: its row turns red a moment. */
117
+ .ds-context :is(.ds-context__item, :is(.ds-context, .ds-context > [role="group"]) > :is(button, a, li, [role^="menuitem"]))[data-ds-refused] {
118
+ background: var(--ds-context-refused) !important;
119
+ }
120
+
121
+ /* Groups are a gap, never a line: the separator takes nothing but the gap after it. */
122
+ .ds-context > :is(hr, .ds-context__separator, [role="separator"]) {
123
+ flex: 0 0 0 !important;
124
+ height: 0 !important;
125
+ margin: 0 !important;
126
+ padding: 0 !important;
127
+ border: 0 !important;
128
+ background: none !important;
129
+ }
130
+
131
+ /* A group left empty leaves no gap. */
132
+ .ds-context > :is(hr, .ds-context__separator, [role="separator"]):is(:first-child, :last-child),
133
+ .ds-context > :is(hr, .ds-context__separator, [role="separator"]) + :is(hr, .ds-context__separator, [role="separator"]) {
134
+ display: none !important;
135
+ }
136
+
137
+ .ds-context :is(.ds-context__label, [data-ds-menu-label]) {
138
+ box-sizing: border-box;
139
+ height: var(--ds-context-row);
140
+ /* The spare three above the sixteen. */
141
+ padding: calc(var(--ds-context-row) - var(--ds-type-lineheight-default)) calc(4px * var(--ds-sf)) 0;
142
+ background: var(--ds-context-fill);
143
+ color: color-mix(in srgb, var(--ds-context-ink) 60%, var(--ds-context-fill));
144
+ line-height: var(--ds-type-lineheight-default);
145
+ white-space: nowrap;
146
+ }
147
+
148
+ .ds-context__shortcut {
149
+ margin-left: auto;
150
+ color: color-mix(in srgb, var(--ds-context-ink) 60%, var(--ds-context-fill));
151
+ }