@threadlabs/looma 0.26.26 → 0.27.0

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 (69) hide show
  1. package/components/shared/editor.js +4 -5
  2. package/components/shared/overlay.js +231 -55
  3. package/components/ui-affordance-scope/ui-affordance-scope.js +4 -8
  4. package/components/ui-card/ui-card.html +3 -0
  5. package/components/ui-dialog/ui-dialog.html +7 -5
  6. package/components/ui-dialog/ui-dialog.js +67 -7
  7. package/components/ui-editor-mention-menu/ui-editor-mention-menu.html +6 -0
  8. package/components/ui-editor-mention-menu/ui-editor-mention-menu.js +5 -9
  9. package/components/ui-editor-slash-menu/ui-editor-slash-menu.html +6 -0
  10. package/components/ui-editor-slash-menu/ui-editor-slash-menu.js +5 -9
  11. package/components/ui-editor-table-context-menu/ui-editor-table-context-menu.html +21 -4
  12. package/components/ui-editor-table-context-menu/ui-editor-table-context-menu.js +50 -22
  13. package/components/ui-editor-table-overlay/ui-editor-table-overlay.js +4 -6
  14. package/components/ui-editor-table-toolbar/ui-editor-table-toolbar.html +3 -11
  15. package/components/ui-editor-table-toolbar/ui-editor-table-toolbar.js +33 -44
  16. package/components/ui-listbox/ui-listbox.html +22 -17
  17. package/components/ui-menu/ui-menu.js +5 -3
  18. package/components/ui-popover/ui-popover.js +3 -2
  19. package/components/ui-scroll-area/ui-scroll-area.html +16 -0
  20. package/components/ui-search-shell/ui-search-shell.html +1 -1
  21. package/components/ui-search-shell/ui-search-shell.js +3 -5
  22. package/components/ui-section/ui-section.html +3 -0
  23. package/components/ui-toast/ui-toast.html +2 -1
  24. package/components/ui-toast/ui-toast.js +18 -2
  25. package/components/ui-toast-region/ui-toast-region.js +11 -3
  26. package/dist/index.js +1 -1
  27. package/editor/chunks/extensions.js +2 -0
  28. package/editor/extensions/slash-command.d.ts +2 -0
  29. package/editor/mention-contract.d.ts +2 -0
  30. package/package.json +1 -1
  31. package/styles/ui-card.css +3 -0
  32. package/styles/ui-dialog.css +4 -2
  33. package/styles/ui-editor-mention-menu.css +7 -1
  34. package/styles/ui-editor-slash-menu.css +7 -1
  35. package/styles/ui-editor-table-context-menu.css +29 -5
  36. package/styles/ui-editor-table-toolbar.css +3 -11
  37. package/styles/ui-listbox.css +6 -0
  38. package/styles/ui-scroll-area.css +13 -0
  39. package/styles/ui-search-shell.css +1 -1
  40. package/styles/ui-section.css +3 -0
  41. package/vanilla/UiDialog.js +10 -6
  42. package/vanilla/UiListbox.js +10 -9
  43. package/vanilla/UiScrollArea.d.ts +1 -0
  44. package/vanilla/UiScrollArea.js +4 -4
  45. package/vue/UiCard.vue +3 -0
  46. package/vue/UiDialog.vue +12 -6
  47. package/vue/UiEditorMentionMenu.vue +6 -0
  48. package/vue/UiEditorSlashMenu.vue +6 -0
  49. package/vue/UiEditorTableContextMenu.vue +21 -4
  50. package/vue/UiEditorTableToolbar.vue +3 -11
  51. package/vue/UiListbox.vue +20 -15
  52. package/vue/UiScrollArea.d.ts +9 -0
  53. package/vue/UiScrollArea.vue +39 -1
  54. package/vue/UiSearchShell.vue +1 -1
  55. package/vue/UiSection.vue +3 -0
  56. package/vue/UiToast.vue +1 -1
  57. package/vue/chunks/UiCard.js +1 -1
  58. package/vue/chunks/UiDialog.js +25 -8
  59. package/vue/chunks/UiEditorMentionMenu.js +1 -1
  60. package/vue/chunks/UiEditorSlashMenu.js +1 -1
  61. package/vue/chunks/UiEditorTableContextMenu.js +1 -1
  62. package/vue/chunks/UiEditorTableToolbar.js +1 -1
  63. package/vue/chunks/UiListbox.js +12 -12
  64. package/vue/chunks/UiScrollArea.js +33 -2
  65. package/vue/chunks/UiSearchShell.js +1 -1
  66. package/vue/chunks/UiSection.js +1 -1
  67. package/vue/chunks/UiToast.js +2 -2
  68. package/vue/components.css +258 -213
  69. package/vue/editor/index.js +225 -40
@@ -6612,6 +6612,7 @@ function createLoomaMentionExtension(options = {}) {
6612
6612
  selectedIndex,
6613
6613
  query: props.query,
6614
6614
  rect: props.clientRect?.() ?? null,
6615
+ getRect: () => props.clientRect?.() ?? null,
6615
6616
  loading,
6616
6617
  highlight: (index) => {
6617
6618
  if (index >= 0 && index < props.items.length) {
@@ -10697,6 +10698,7 @@ var LoomaSlashCommand = Extension.create({
10697
10698
  selectedIndex,
10698
10699
  query: props.query,
10699
10700
  rect: props.clientRect?.() ?? null,
10701
+ getRect: () => props.clientRect?.() ?? null,
10700
10702
  select: (index) => {
10701
10703
  if (revision !== generation || props !== currentProps || document !== this.editor.state.doc || !this.editor.isEditable || !isCurrent(props)) return;
10702
10704
  const item = props.items[index];
@@ -39,6 +39,8 @@ export interface LoomaSlashMenuSnapshot {
39
39
  selectedIndex: number;
40
40
  query: string;
41
41
  rect: DOMRect | null;
42
+ /** Re-measures the current virtual anchor after scrolling; valid for this snapshot only. */
43
+ getRect?: () => DOMRect | null;
42
44
  select: ((index: number) => void) | null;
43
45
  /** Keeps editor keyboard selection synchronized with a UI's hovered row. */
44
46
  highlight?: ((index: number) => void) | null;
@@ -44,6 +44,8 @@ export interface LoomaMentionMenuSnapshot {
44
44
  selectedIndex: number;
45
45
  query: string;
46
46
  rect: DOMRect | null;
47
+ /** Re-measures the current virtual anchor after scrolling; valid for this snapshot only. */
48
+ getRect?: () => DOMRect | null;
47
49
  loading: boolean;
48
50
  highlight: ((index: number) => void) | null;
49
51
  select: ((index: number) => void) | null;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@threadlabs/looma",
3
- "version": "0.26.26",
3
+ "version": "0.27.0",
4
4
  "type": "module",
5
5
  "description": "Looma components as HTML, Vue, and plain DOM, with design tokens.",
6
6
  "license": "MIT",
@@ -69,6 +69,9 @@
69
69
  :scope[data-ui-card-state~="tone=danger"] {
70
70
  border-color: color-mix(in srgb, var(--ui-danger) 45%, var(--ui-border-default));
71
71
  border-inline-start: var(--ui-accent-line-width) solid var(--ui-danger);
72
+ /* An accent edge keeps the corners on its side straight. */
73
+ border-start-start-radius: 0;
74
+ border-end-start-radius: 0;
72
75
  --_ui-card-surface: var(--ui-danger-soft);
73
76
  }
74
77
 
@@ -110,7 +110,9 @@
110
110
  align-items: center;
111
111
  gap: var(--ui-space-3);
112
112
  flex: none;
113
- padding: var(--ui-space-3) var(--ui-space-3) var(--ui-space-3) var(--ui-space-5);
113
+ /* The title's ink sits inside its line box and the taller close target. Balance that
114
+ visible top inset against the action row's bottom inset, not just the boxes. */
115
+ padding: calc(var(--ui-space-3) + var(--ui-space-1) / 2) var(--ui-space-3) var(--ui-space-3) var(--ui-space-5);
114
116
  }
115
117
 
116
118
  /* :host-qualified so page heading rules that reach in cannot outrank the dialog chrome. */
@@ -211,7 +213,7 @@
211
213
  overlay var(--_ui-dialog-duration, var(--ui-motion-base)) var(--ui-motion-ease) allow-discrete;
212
214
  }
213
215
 
214
- :scope[open]::backdrop {
216
+ :scope[open]:modal::backdrop {
215
217
  background-color: var(--ui-overlay-backdrop);
216
218
  animation: ui-dialog-backdrop-enter var(--_ui-dialog-duration, var(--ui-motion-base)) var(--ui-motion-ease);
217
219
  }
@@ -1,6 +1,12 @@
1
1
  /* Generated by HTML Next 1.0.0-alpha.35. Do not edit. */
2
2
  @scope ([data-component~="ui-editor-mention-menu"]) to ([data-component], [data-slotted]) {
3
- :scope {
3
+ :scope[popover] {
4
+ position: fixed;
5
+ inset: auto;
6
+ margin: 0;
7
+ }
8
+
9
+ :scope {
4
10
  user-select: none;
5
11
  }
6
12
 
@@ -1,6 +1,12 @@
1
1
  /* Generated by HTML Next 1.0.0-alpha.35. Do not edit. */
2
2
  @scope ([data-component~="ui-editor-slash-menu"]) to ([data-component], [data-slotted]) {
3
- :scope {
3
+ :scope[popover] {
4
+ position: fixed;
5
+ inset: auto;
6
+ margin: 0;
7
+ }
8
+
9
+ :scope {
4
10
  user-select: none;
5
11
  }
6
12
 
@@ -1,9 +1,15 @@
1
1
  /* Generated by HTML Next 1.0.0-alpha.35. Do not edit. */
2
2
  @scope ([data-component~="ui-editor-table-context-menu"]) to ([data-component], [data-slotted]) {
3
- :scope {
3
+ :scope[popover] {
4
+ position: fixed;
5
+ inset: auto;
6
+ margin: 0;
7
+ }
8
+
9
+ :scope {
4
10
  /* The max-width is a promise about the menu's footprint, so it counts padding and border. */
5
11
  box-sizing: border-box;
6
- min-width: 14rem;
12
+ min-width: min(14rem, calc(100vw - 24px));
7
13
  max-width: min(17rem, calc(100vw - 24px));
8
14
  max-height: min(28rem, calc(100vh - 24px));
9
15
  padding: var(--ui-space-2);
@@ -58,16 +64,22 @@
58
64
  color: var(--ui-danger);
59
65
  }
60
66
 
67
+ /* Swatches wrap like words: the row asks for the menu's full width, and a narrower menu fits fewer. */
61
68
  .swatches {
62
- display: grid;
63
- grid-template-columns: repeat(3, minmax(0, 1fr));
69
+ display: flex;
70
+ flex-wrap: wrap;
64
71
  gap: 3px;
65
72
  }
66
73
 
67
- .swatch-button {
74
+ /* Three to a row at the menu's full width; 5rem keeps the longest label, "Default", whole. */
75
+ :scope .swatch-button {
68
76
  display: inline-flex;
69
77
  align-items: center;
70
78
  gap: var(--ui-space-2);
79
+ flex: 1 0 5rem;
80
+ min-width: 5rem;
81
+ max-width: calc((100% - 6px) / 3);
82
+ padding-inline-end: var(--ui-space-1);
71
83
  }
72
84
 
73
85
  .swatch {
@@ -114,7 +126,19 @@
114
126
  margin: var(--ui-space-1) 0;
115
127
  }
116
128
 
129
+ /* Separators divide sections, so none sits above the first. */
130
+ .sep:first-child {
131
+ display: none;
132
+ }
133
+
117
134
  :scope[hidden] {
118
135
  display: none;
119
136
  }
120
137
  }
138
+ @scope ([data-component~="ui-editor-table-context-menu"]) to ([data-component]) {
139
+ /* Swatches wrap like words: the row asks for the menu's full width, and a narrower menu fits fewer. */
140
+
141
+ /* Three to a row at the menu's full width; 5rem keeps the longest label, "Default", whole. */
142
+
143
+ /* Separators divide sections, so none sits above the first. */
144
+ }
@@ -21,14 +21,6 @@
21
21
  background: var(--ui-accent-soft, var(--ui-accent-subtle));
22
22
  }
23
23
 
24
- .looma-editor__mobile-toolbar-shell :scope .menu {
25
- top: auto;
26
- right: 0;
27
- bottom: calc(100% + var(--ui-space-2));
28
- max-height: min(50dvh, 22rem);
29
- overflow-y: auto;
30
- overscroll-behavior: contain;
31
- }
32
24
  :scope {
33
25
  display: inline-flex;
34
26
  align-items: center;
@@ -119,9 +111,9 @@
119
111
 
120
112
  .menu {
121
113
  box-sizing: border-box;
122
- position: absolute;
123
- top: calc(100% + var(--ui-space-2));
124
- right: 0;
114
+ position: fixed;
115
+ inset: auto;
116
+ margin: 0;
125
117
  min-width: 12rem;
126
118
  max-width: min(17rem, calc(100vw - 24px));
127
119
  padding: var(--ui-space-1);
@@ -38,7 +38,10 @@
38
38
  outline: none;
39
39
  }
40
40
 
41
+ /* Its own box around the nested Scroll Area, which it shrinks into so the choices scroll. */
41
42
  .choices {
43
+ display: flex;
44
+ flex-direction: column;
42
45
  max-block-size: calc(max(2, var(--_rows, 5)) * var(--ui-control-size-md));
43
46
  }
44
47
 
@@ -92,3 +95,6 @@
92
95
  display: none;
93
96
  }
94
97
  }
98
+ @scope ([data-component~="ui-listbox"]) to ([data-component]) {
99
+ /* Its own box around the nested Scroll Area, which it shrinks into so the choices scroll. */
100
+ }
@@ -53,6 +53,19 @@
53
53
  max-block-size: 30rem;
54
54
  }
55
55
 
56
+ :scope[data-ui-scroll-area-state~="padding=xs"] {
57
+ padding: var(--ui-space-2);
58
+ }
59
+ :scope[data-ui-scroll-area-state~="padding=sm"] {
60
+ padding: var(--ui-space-3);
61
+ }
62
+ :scope[data-ui-scroll-area-state~="padding=md"] {
63
+ padding: var(--ui-space-5);
64
+ }
65
+ :scope[data-ui-scroll-area-state~="padding=lg"] {
66
+ padding: var(--ui-space-6);
67
+ }
68
+
56
69
  /* A component that sets its display still honours the hidden attribute on its root. */
57
70
  :scope[hidden] {
58
71
  display: none;
@@ -76,7 +76,7 @@
76
76
  color: var(--ui-text-primary);
77
77
  }
78
78
 
79
- dialog::backdrop {
79
+ dialog:modal::backdrop {
80
80
  background: var(--ui-overlay-backdrop);
81
81
  }
82
82
 
@@ -36,6 +36,9 @@
36
36
  --_ui-selection-context-text: var(--ui-text-primary);
37
37
  border-color: color-mix(in srgb, var(--ui-danger) 45%, var(--ui-border-default));
38
38
  border-inline-start: var(--ui-accent-line-width) solid var(--ui-danger);
39
+ /* An accent edge keeps the corners on its side straight. */
40
+ border-start-start-radius: 0;
41
+ border-end-start-radius: 0;
39
42
  background: var(--ui-danger-soft);
40
43
  }
41
44
 
@@ -62,7 +62,7 @@ export function createUiDialog(options = {}, html) {
62
62
  if (c === -1) {
63
63
  const x1 = true;
64
64
  const s1 = toAttribute(x1, "trim");
65
- if (s1 !== r.v1) writeAttribute(r.a3, "trim", r.v1 = s1);
65
+ if (s1 !== r.v1) writeAttribute(r.a5, "trim", r.v1 = s1);
66
66
  }
67
67
  if (c & 516) {
68
68
  const x2 = hostState(["size","alert"], [v[9], v[2]]);
@@ -74,15 +74,15 @@ export function createUiDialog(options = {}, html) {
74
74
  if (true) {
75
75
  {
76
76
  r.b0 = undefined;
77
- clearRegion(r.a1, r.e0);
77
+ clearRegion(r.a2, r.e0);
78
78
  if (t0) { r.b0 = m1(d); r.e0.before(r.b0.n); }
79
79
  }
80
80
  } else if (r.b0 !== undefined) p1(r.b0, c, d);
81
81
  } else if (r.b0 !== undefined) p1(r.b0, c, d);
82
- if (r.f1 === undefined) { r.f1 = 1; const at = fillSlot(r.a4, r.e1, "", J, false, undefined, d);
82
+ if (r.f1 === undefined) { r.f1 = 1; const at = fillSlot(r.a6, r.e1, "", J, false, undefined, d);
83
83
  if (Array.isArray(at)) (r.z ??= []).push(() => { at[0].l.delete(at[1]); dispose(at[1]); }); else if (at !== undefined) { r.b1 = m2(d); at.before(r.b1.n); } }
84
84
  else if (r.b1 !== undefined) p2(r.b1, c, d);
85
- if (r.f2 === undefined) { r.f2 = 1; const at = fillSlot(r.a5, r.e2, "actions", J, false, undefined, d);
85
+ if (r.f2 === undefined) { r.f2 = 1; const at = fillSlot(r.a8, r.e2, "actions", J, false, undefined, d);
86
86
  if (Array.isArray(at)) (r.z ??= []).push(() => { at[0].l.delete(at[1]); dispose(at[1]); }); else if (at !== undefined) { r.b2 = m3(d); at.before(r.b2.n); } }
87
87
  else if (r.b2 !== undefined) p3(r.b2, c, d);
88
88
  };
@@ -92,13 +92,17 @@ export function createUiDialog(options = {}, html) {
92
92
  const t3 = t0.nextSibling;
93
93
  const t4 = t3.firstChild;
94
94
  const t5 = t4.firstChild;
95
- const t6 = t3.nextSibling.firstChild;
96
- const R = { n: element, a3: t4, v0: undefined, v1: null, v2: undefined, a1: t1, e0: t1.nextSibling, b0: undefined, a4: t5, e1: t5.nextSibling, b1: undefined, f1: undefined, a5: t6, e2: t6.nextSibling, b2: undefined, f2: undefined };
95
+ const t6 = t3.nextSibling;
96
+ const t7 = t6.firstChild;
97
+ const R = { n: element, a5: t4, v0: undefined, v1: null, v2: undefined, a2: t1, e0: t1.nextSibling, b0: undefined, a6: t5, e1: t5.nextSibling, b1: undefined, f1: undefined, a8: t7, e2: t7.nextSibling, b2: undefined, f2: undefined };
97
98
  const I = { B };
98
99
  const J = project(I, children, slots);
99
100
  I.R = new Set();
100
101
  attachGeneratedController(element, S, v, (c, d) => { ((c, d) => p0(R, c, d))(c, d); for (const f of I.R) f(c, d); }, C, undefined, I);
101
102
  manageProps(I);
103
+ I.r["header"] = t0;
102
104
  I.r["close"] = t2;
105
+ I.r["body"] = t3;
106
+ I.r["footer"] = t6;
103
107
  return element;
104
108
  }
@@ -3,7 +3,7 @@ import { attachGeneratedController, buildTemplate, checkReference, clearRegion,
3
3
  import * as controller from "../components/ui-listbox/ui-listbox.js";
4
4
  import "../styles/ui-listbox.css";
5
5
 
6
- const T0 = ["",[],["ui-scroll-area",["class","choices"],2],["select",["class","fallback","aria-label","Choices"],4],1,["input",["class","validity","type","checkbox","aria-hidden","true","tabindex","-1"]]];
6
+ const T0 = ["",[],["div",["class","choices"],["ui-scroll-area",[],2]],["select",["class","fallback","aria-label","Choices"],4],1,["input",["class","validity","type","checkbox","aria-hidden","true","tabindex","-1"]]];
7
7
  const T1 = ["div",["class","option","role","option","data-ui-choice-option",""],["span",[],0]];
8
8
  const T2 = ["",[]];
9
9
  const T3 = ["",[],2];
@@ -201,18 +201,19 @@ export function createUiListbox(options = {}, html) {
201
201
  };
202
202
  const t0 = element.firstChild;
203
203
  const t1 = t0.firstChild;
204
- const t2 = t0.nextSibling;
205
- const t3 = t2.firstChild;
206
- const t4 = t2.nextSibling;
207
- const t5 = t4.nextSibling.nextSibling;
208
- const R = { n: element, a3: t2, a6: t5, v0: undefined, v1: undefined, v2: undefined, v3: undefined, v4: undefined, v5: undefined, v6: null, v7: null, v8: null, v9: null, v10: null, v11: null, v12: null, v13: null, L0: new KeyedList(t1, t1.nextSibling, m1, p1, (o) => readMember(o, "index"), "choice"), a4: t3, e1: t3.nextSibling, b1: undefined, f1: undefined, a5: t4, e2: t4.nextSibling, b2: undefined };
204
+ const t2 = t1.firstChild;
205
+ const t3 = t0.nextSibling;
206
+ const t4 = t3.firstChild;
207
+ const t5 = t3.nextSibling;
208
+ const t6 = t5.nextSibling.nextSibling;
209
+ const R = { n: element, a3: t3, a6: t6, v0: undefined, v1: undefined, v2: undefined, v3: undefined, v4: undefined, v5: undefined, v6: null, v7: null, v8: null, v9: null, v10: null, v11: null, v12: null, v13: null, L0: new KeyedList(t2, t2.nextSibling, m1, p1, (o) => readMember(o, "index"), "choice"), a4: t4, e1: t4.nextSibling, b1: undefined, f1: undefined, a5: t5, e2: t5.nextSibling, b2: undefined };
209
210
  const I = { B };
210
211
  const J = project(I, children, slots);
211
212
  I.R = new Set();
212
213
  attachGeneratedController(element, S, v, (c, d) => { ((c, d) => p0(R, c, d))(c, d); for (const f of I.R) f(c, d); }, C, undefined, I);
213
214
  manageProps(I);
214
- I.r["choices"] = t0;
215
- I.r["options"] = t2;
216
- I.r["validity"] = t5;
215
+ I.r["choices"] = t1;
216
+ I.r["options"] = t3;
217
+ I.r["validity"] = t6;
217
218
  return element;
218
219
  }
@@ -8,6 +8,7 @@ export interface UiScrollAreaElement extends HTMLDivElement {
8
8
  export interface UiScrollAreaProps {
9
9
  fill?: boolean | null;
10
10
  orientation?: "vertical" | "horizontal" | null;
11
+ padding?: "none" | "xs" | "sm" | "md" | "lg" | null;
11
12
  size?: "content" | "sm" | "md" | "lg" | null;
12
13
  trim?: boolean | null;
13
14
  attributes?: Readonly<Record<string, string | number | boolean | null | undefined>>;
@@ -7,7 +7,7 @@ const T0 = ["",[],["div",["class","content"],4]];
7
7
  const T1 = ["",[]];
8
8
  let P0, P1;
9
9
  const S = { n: [], t: [], f: import.meta.url, g: "ui-scroll-area" };
10
- const D = { props: { "fill": { "required":false,"default":false, c: checkBoolean, t: "boolean", j: 0, m: "b" }, "orientation": { "required":false,"values":["vertical","horizontal"],"default":"vertical", c: checkFormat("keyword", keywordFormat), t: "keyword", j: 0, m: ["p",formatOf(keywordFormat),false] }, "size": { "required":false,"values":["content","sm","md","lg"],"default":"content", c: checkFormat("keyword", keywordFormat), t: "keyword", j: 0, m: ["p",formatOf(keywordFormat),false] }, "trim": { "required":false,"default":false, c: checkBoolean, t: "boolean", j: 0, m: "b" } } };
10
+ const D = { props: { "fill": { "required":false,"default":false, c: checkBoolean, t: "boolean", j: 0, m: "b" }, "orientation": { "required":false,"values":["vertical","horizontal"],"default":"vertical", c: checkFormat("keyword", keywordFormat), t: "keyword", j: 0, m: ["p",formatOf(keywordFormat),false] }, "padding": { "required":false,"values":["none","xs","sm","md","lg"],"default":"none", c: checkFormat("keyword", keywordFormat), t: "keyword", j: 0, m: ["p",formatOf(keywordFormat),false] }, "size": { "required":false,"values":["content","sm","md","lg"],"default":"content", c: checkFormat("keyword", keywordFormat), t: "keyword", j: 0, m: ["p",formatOf(keywordFormat),false] }, "trim": { "required":false,"default":false, c: checkBoolean, t: "boolean", j: 0, m: "b" } } };
11
11
  const C = (host) => controller.default(host);
12
12
 
13
13
  export function createUiScrollArea(options = {}, html) {
@@ -19,7 +19,7 @@ export function createUiScrollArea(options = {}, html) {
19
19
  }
20
20
  element.setAttribute("data-component", "ui-scroll-area");
21
21
  element.append((P0 ??= buildTemplate(T0)).cloneNode(true));
22
- const v = [null, null, null, null];
22
+ const v = [null, null, null, null, null];
23
23
  const B = acceptProps(element, D, S.n, v, componentProps, [], html);
24
24
  const m1 = (d) => {
25
25
  const n = (P1 ??= buildTemplate(T1)).cloneNode(true);
@@ -30,8 +30,8 @@ export function createUiScrollArea(options = {}, html) {
30
30
  const p1 = (r, c, d) => {
31
31
  };
32
32
  const p0 = (r, c, d) => {
33
- if (c & 15) {
34
- const x0 = hostState(["fill","orientation","size","trim"], [v[0], v[1], v[2], v[3]]);
33
+ if (c & 31) {
34
+ const x0 = hostState(["fill","orientation","size","padding","trim"], [v[0], v[1], v[3], v[2], v[4]]);
35
35
  const s0 = x0;
36
36
  if (s0 !== r.v0) writeAttribute(r.n, "data-ui-scroll-area-state", (r.v0 = s0) === "" ? null : s0);
37
37
  }
package/vue/UiCard.vue CHANGED
@@ -331,6 +331,9 @@ const hostState = computed(() =>
331
331
  [data-component~="ui-card"][data-ui-card-state~="tone=danger"] {
332
332
  border-color: color-mix(in srgb, var(--ui-danger) 45%, var(--ui-border-default));
333
333
  border-inline-start: var(--ui-accent-line-width) solid var(--ui-danger);
334
+ /* An accent edge keeps the corners on its side straight. */
335
+ border-start-start-radius: 0;
336
+ border-end-start-radius: 0;
334
337
  --_ui-card-surface: var(--ui-danger-soft);
335
338
  }
336
339
 
package/vue/UiDialog.vue CHANGED
@@ -228,7 +228,10 @@ const emit = defineEmits<{
228
228
  }>()
229
229
 
230
230
  const root = ref<HTMLElement | null>(null)
231
+ const headerElement = useTemplateRef<HTMLElement>('headerElement')
231
232
  const closeElement = useTemplateRef<HTMLElement>('closeElement')
233
+ const bodyElement = useTemplateRef<HTMLElement>('bodyElement')
234
+ const footerElement = useTemplateRef<HTMLElement>('footerElement')
232
235
  const internalOpen = ref(false)
233
236
  const accessibleLabel = ref('Dialog')
234
237
  let guardedPrevious: any
@@ -324,7 +327,7 @@ useComponentHost(() => import('../components/ui-dialog/ui-dialog.js'), {
324
327
  propNames: ['alert', 'closedby', 'for', 'label', 'modal', 'modeless', 'open', 'size'],
325
328
  propValidity: (name: string) =>
326
329
  propValidityState({ contract: propValidityContract, values: { ...props } }, name),
327
- refs: { close: closeElement },
330
+ refs: { header: headerElement, close: closeElement, body: bodyElement, footer: footerElement },
328
331
  state: { internalOpen, accessibleLabel },
329
332
  })
330
333
  </script>
@@ -346,12 +349,12 @@ useComponentHost(() => import('../components/ui-dialog/ui-dialog.js'), {
346
349
  :data-ui-dialog-state="hostState || undefined"
347
350
  ref="root"
348
351
  >
349
- <header>
352
+ <header ref="headerElement">
350
353
  <h2 v-if="checkedProps.label" class="title">{{ checkedProps.label }}</h2>
351
354
  <button class="close" type="button" aria-label="Close" ref="closeElement"></button>
352
355
  </header>
353
- <div class="body"><UiScrollArea :trim="true"><slot /></UiScrollArea></div>
354
- <footer><slot name="actions" /></footer>
356
+ <div class="body" ref="bodyElement"><UiScrollArea :trim="true"><slot /></UiScrollArea></div>
357
+ <footer ref="footerElement"><slot name="actions" /></footer>
355
358
  </dialog>
356
359
  </template>
357
360
 
@@ -459,7 +462,10 @@ header {
459
462
  align-items: center;
460
463
  gap: var(--ui-space-3);
461
464
  flex: none;
462
- padding: var(--ui-space-3) var(--ui-space-3) var(--ui-space-3) var(--ui-space-5);
465
+ /* The title's ink sits inside its line box and the taller close target. Balance that
466
+ visible top inset against the action row's bottom inset, not just the boxes. */
467
+ padding: calc(var(--ui-space-3) + var(--ui-space-1) / 2) var(--ui-space-3) var(--ui-space-3)
468
+ var(--ui-space-5);
463
469
  }
464
470
 
465
471
  /* :host-qualified so page heading rules that reach in cannot outrank the dialog chrome. */
@@ -569,7 +575,7 @@ footer:empty {
569
575
  overlay var(--_ui-dialog-duration, var(--ui-motion-base)) var(--ui-motion-ease) allow-discrete;
570
576
  }
571
577
 
572
- [data-component~="ui-dialog"][open]::backdrop {
578
+ [data-component~="ui-dialog"][open]:modal::backdrop {
573
579
  background-color: var(--ui-overlay-backdrop);
574
580
  animation: ui-dialog-backdrop-enter var(--_ui-dialog-duration, var(--ui-motion-base))
575
581
  var(--ui-motion-ease);
@@ -460,6 +460,12 @@ function concat<T extends unknown[]>(...values: T): ConcatResult<T> {
460
460
  </template>
461
461
 
462
462
  <style scoped>
463
+ [data-component~="ui-editor-mention-menu"][popover] {
464
+ position: fixed;
465
+ inset: auto;
466
+ margin: 0;
467
+ }
468
+
463
469
  [data-component~="ui-editor-mention-menu"] {
464
470
  user-select: none;
465
471
  }
@@ -394,6 +394,12 @@ useComponentHost(() => import('../components/ui-editor-slash-menu/ui-editor-slas
394
394
  </template>
395
395
 
396
396
  <style scoped>
397
+ [data-component~="ui-editor-slash-menu"][popover] {
398
+ position: fixed;
399
+ inset: auto;
400
+ margin: 0;
401
+ }
402
+
397
403
  [data-component~="ui-editor-slash-menu"] {
398
404
  user-select: none;
399
405
  }
@@ -654,10 +654,16 @@ function uniqueKeys<T>(
654
654
  </template>
655
655
 
656
656
  <style scoped>
657
+ [data-component~="ui-editor-table-context-menu"][popover] {
658
+ position: fixed;
659
+ inset: auto;
660
+ margin: 0;
661
+ }
662
+
657
663
  [data-component~="ui-editor-table-context-menu"] {
658
664
  /* The max-width is a promise about the menu's footprint, so it counts padding and border. */
659
665
  box-sizing: border-box;
660
- min-width: 14rem;
666
+ min-width: min(14rem, calc(100vw - 24px));
661
667
  max-width: min(17rem, calc(100vw - 24px));
662
668
  max-height: min(28rem, calc(100vh - 24px));
663
669
  padding: var(--ui-space-2);
@@ -712,16 +718,22 @@ function uniqueKeys<T>(
712
718
  color: var(--ui-danger);
713
719
  }
714
720
 
721
+ /* Swatches wrap like words: the row asks for the menu's full width, and a narrower menu fits fewer. */
715
722
  .swatches {
716
- display: grid;
717
- grid-template-columns: repeat(3, minmax(0, 1fr));
723
+ display: flex;
724
+ flex-wrap: wrap;
718
725
  gap: 3px;
719
726
  }
720
727
 
721
- .swatch-button {
728
+ /* Three to a row at the menu's full width; 5rem keeps the longest label, "Default", whole. */
729
+ [data-component~="ui-editor-table-context-menu"] .swatch-button {
722
730
  display: inline-flex;
723
731
  align-items: center;
724
732
  gap: var(--ui-space-2);
733
+ flex: 1 0 5rem;
734
+ min-width: 5rem;
735
+ max-width: calc((100% - 6px) / 3);
736
+ padding-inline-end: var(--ui-space-1);
725
737
  }
726
738
 
727
739
  .swatch {
@@ -768,6 +780,11 @@ function uniqueKeys<T>(
768
780
  margin: var(--ui-space-1) 0;
769
781
  }
770
782
 
783
+ /* Separators divide sections, so none sits above the first. */
784
+ .sep:first-child {
785
+ display: none;
786
+ }
787
+
771
788
  [data-component~="ui-editor-table-context-menu"][hidden] {
772
789
  display: none;
773
790
  }
@@ -796,14 +796,6 @@ function uniqueKeys<T>(
796
796
  background: var(--ui-accent-soft, var(--ui-accent-subtle));
797
797
  }
798
798
 
799
- .looma-editor__mobile-toolbar-shell [data-component~="ui-editor-table-toolbar"] .menu {
800
- top: auto;
801
- right: 0;
802
- bottom: calc(100% + var(--ui-space-2));
803
- max-height: min(50dvh, 22rem);
804
- overflow-y: auto;
805
- overscroll-behavior: contain;
806
- }
807
799
  [data-component~="ui-editor-table-toolbar"] {
808
800
  display: inline-flex;
809
801
  align-items: center;
@@ -894,9 +886,9 @@ function uniqueKeys<T>(
894
886
 
895
887
  .menu {
896
888
  box-sizing: border-box;
897
- position: absolute;
898
- top: calc(100% + var(--ui-space-2));
899
- right: 0;
889
+ position: fixed;
890
+ inset: auto;
891
+ margin: 0;
900
892
  min-width: 12rem;
901
893
  max-width: min(17rem, calc(100vw - 24px));
902
894
  padding: var(--ui-space-1);
package/vue/UiListbox.vue CHANGED
@@ -479,21 +479,23 @@ function uniqueKeys<T>(
479
479
  :data-values="reflectedProp('values', 'values', checkedProps.values, undefined, false)"
480
480
  ref="root"
481
481
  >
482
- <UiScrollArea class="choices" ref="choicesElement"><KeyedBoundary><div
483
- v-for="choice in uniqueKeys(choices, (choice, index, loop) => choice.index)"
484
- :key="choice.index"
485
- class="option"
486
- role="option"
487
- data-ui-choice-option=""
488
- :data-multiple="checkedProps.multiple ? '' : undefined"
489
- :id="concat(uid, '-option-', choice.index)"
490
- :data-index="choice.index"
491
- :aria-selected="choice.selected"
492
- :aria-disabled="choice.disabled"
493
- :class="{ active: choice.index === active }"
494
- >
495
- <span>{{ choice.label }}</span>
496
- </div></KeyedBoundary></UiScrollArea>
482
+ <div class="choices">
483
+ <UiScrollArea ref="choicesElement"><KeyedBoundary><div
484
+ v-for="choice in uniqueKeys(choices, (choice, index, loop) => choice.index)"
485
+ :key="choice.index"
486
+ class="option"
487
+ role="option"
488
+ data-ui-choice-option=""
489
+ :data-multiple="checkedProps.multiple ? '' : undefined"
490
+ :id="concat(uid, '-option-', choice.index)"
491
+ :data-index="choice.index"
492
+ :aria-selected="choice.selected"
493
+ :aria-disabled="choice.disabled"
494
+ :class="{ active: choice.index === active }"
495
+ >
496
+ <span>{{ choice.label }}</span>
497
+ </div></KeyedBoundary></UiScrollArea>
498
+ </div>
497
499
  <select
498
500
  class="fallback"
499
501
  aria-label="Choices"
@@ -566,7 +568,10 @@ function uniqueKeys<T>(
566
568
  outline: none;
567
569
  }
568
570
 
571
+ /* Its own box around the nested Scroll Area, which it shrinks into so the choices scroll. */
569
572
  .choices {
573
+ display: flex;
574
+ flex-direction: column;
570
575
  max-block-size: calc(max(2, var(--_rows, 5)) * var(--ui-control-size-md));
571
576
  }
572
577
 
@@ -12,6 +12,10 @@ declare const __VLS_base: import("vue").DefineComponent<import("vue").ExtractPro
12
12
  type: PropType<"vertical" | "horizontal" | null>;
13
13
  default: string;
14
14
  };
15
+ padding: {
16
+ type: PropType<"none" | "xs" | "sm" | "md" | "lg" | null>;
17
+ default: string;
18
+ };
15
19
  size: {
16
20
  type: PropType<"content" | "sm" | "md" | "lg" | null>;
17
21
  default: string;
@@ -29,6 +33,10 @@ declare const __VLS_base: import("vue").DefineComponent<import("vue").ExtractPro
29
33
  type: PropType<"vertical" | "horizontal" | null>;
30
34
  default: string;
31
35
  };
36
+ padding: {
37
+ type: PropType<"none" | "xs" | "sm" | "md" | "lg" | null>;
38
+ default: string;
39
+ };
32
40
  size: {
33
41
  type: PropType<"content" | "sm" | "md" | "lg" | null>;
34
42
  default: string;
@@ -40,6 +48,7 @@ declare const __VLS_base: import("vue").DefineComponent<import("vue").ExtractPro
40
48
  }>> & Readonly<{}>, {
41
49
  fill: boolean | null;
42
50
  size: "sm" | "md" | "lg" | "content" | null;
51
+ padding: "none" | "xs" | "sm" | "md" | "lg" | null;
43
52
  trim: boolean | null;
44
53
  orientation: "vertical" | "horizontal" | null;
45
54
  }, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;