@threadlabs/looma 0.2.19 → 0.3.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 (51) hide show
  1. package/dist/{chunk-A5MSXQ7Y.js → chunk-3NVMCHGR.js} +2225 -1255
  2. package/dist/chunk-XDNZIKNI.js +2721 -0
  3. package/dist/declarative-generated.cjs +93 -79
  4. package/dist/declarative-generated.d.cts +7 -1
  5. package/dist/declarative-generated.d.ts +7 -1
  6. package/dist/declarative-generated.js +91 -78
  7. package/dist/declarative.cjs +1499 -1702
  8. package/dist/declarative.d.cts +77 -3
  9. package/dist/declarative.d.ts +77 -3
  10. package/dist/declarative.js +5 -3
  11. package/dist/index.cjs +3732 -3025
  12. package/dist/index.d.cts +211 -35
  13. package/dist/index.d.ts +211 -35
  14. package/dist/index.js +6 -61
  15. package/dist/loader.cjs +3725 -2962
  16. package/dist/loader.js +2 -2
  17. package/editor/{chunk-WI7ZA44X.js → chunk-U36XNFJN.js} +111 -77
  18. package/editor/extensions/index.d.ts +272 -27
  19. package/editor/index.d.ts +1 -1
  20. package/editor/index.js +1 -1
  21. package/editor/{table-overlay-DoiLie4h.d.ts → table-overlay-DZFGHQDt.d.ts} +48 -6
  22. package/editor/ui.d.ts +15 -11
  23. package/editor/ui.js +1 -1
  24. package/layout/index.cjs +197 -340
  25. package/layout/index.d.cts +11 -0
  26. package/layout/index.d.ts +11 -0
  27. package/layout/index.js +197 -340
  28. package/layout.css +31 -134
  29. package/package.json +24 -11
  30. package/react/index.d.ts +1276 -0
  31. package/react/index.js +1526 -0
  32. package/styles.css +5 -10
  33. package/svelte/index.d.ts +204 -0
  34. package/svelte/index.js +1881 -0
  35. package/theme-dark.css +81 -2
  36. package/theme-high-contrast.css +14 -0
  37. package/theme-light.css +30 -1
  38. package/tokens.css +45 -1
  39. package/vue/chunk-UZMTYDP6.js +4355 -0
  40. package/vue/editor/index.d.ts +78 -8
  41. package/vue/editor/index.js +59 -49
  42. package/vue/index.d.ts +67 -78
  43. package/vue/index.js +3 -9
  44. package/dist/chunk-ECJQ4YGC.js +0 -2925
  45. package/dist/valibot.cjs +0 -31
  46. package/dist/valibot.d.cts +0 -10
  47. package/dist/valibot.d.ts +0 -10
  48. package/dist/valibot.js +0 -9
  49. package/dist/validation-bxcou-l-.d.cts +0 -58
  50. package/dist/validation-bxcou-l-.d.ts +0 -58
  51. package/vue/chunk-P7BZ2KJZ.js +0 -3559
package/dist/loader.js CHANGED
@@ -1,10 +1,10 @@
1
1
  import {
2
2
  records,
3
3
  styles
4
- } from "./chunk-A5MSXQ7Y.js";
4
+ } from "./chunk-3NVMCHGR.js";
5
5
  import {
6
6
  registerLoomaPackage
7
- } from "./chunk-ECJQ4YGC.js";
7
+ } from "./chunk-XDNZIKNI.js";
8
8
  import "./chunk-MLKGABMK.js";
9
9
 
10
10
  // src/loader.ts
@@ -5,7 +5,7 @@ import {
5
5
  // src/ui.ts
6
6
  import { registerLoomaPackage } from "@threadlabs/looma/core/declarative";
7
7
 
8
- // ../../tools/migrate-html-next/generated/editor/components/controllers/ui-editor-insert-table-grid.js
8
+ // src/declarative/components/controllers/ui-editor-insert-table-grid.js
9
9
  var ui_editor_insert_table_grid_exports = {};
10
10
  __export(ui_editor_insert_table_grid_exports, {
11
11
  default: () => controller
@@ -20,7 +20,8 @@ function controller(host) {
20
20
  let selectedCols = 3;
21
21
  let previewRows = null;
22
22
  let previewCols = null;
23
- let withHeaderRow = true;
23
+ let lastHeaderRow = Boolean(host.state.headerRow);
24
+ let withHeaderRow = lastHeaderRow;
24
25
  let selectionCommitted = false;
25
26
  const updateSelection = () => {
26
27
  const activeRows = previewRows ?? selectedRows;
@@ -34,6 +35,11 @@ function controller(host) {
34
35
  }
35
36
  };
36
37
  const render = () => {
38
+ const configuredHeaderRow = Boolean(host.state.headerRow);
39
+ if (configuredHeaderRow !== lastHeaderRow) {
40
+ lastHeaderRow = configuredHeaderRow;
41
+ withHeaderRow = configuredHeaderRow;
42
+ }
37
43
  element.hidden = !host.state.open;
38
44
  if (!host.state.open) return;
39
45
  const maxRows = bounded(host.state.maxRows, 8);
@@ -65,7 +71,7 @@ function controller(host) {
65
71
  return;
66
72
  }
67
73
  if (event.target.closest?.("[data-insert-table]")) {
68
- host.dispatch("looma-editor-insert-table", { rows: selectedRows, cols: selectedCols, withHeaderRow });
74
+ host.dispatch("insert", { rows: selectedRows, cols: selectedCols, withHeaderRow });
69
75
  }
70
76
  };
71
77
  const onMouseover = (event) => {
@@ -98,13 +104,13 @@ function controller(host) {
98
104
  };
99
105
  }
100
106
 
101
- // ../../tools/migrate-html-next/generated/editor/components/controllers/ui-editor-mention-menu.js
107
+ // src/declarative/components/controllers/ui-editor-mention-menu.js
102
108
  var ui_editor_mention_menu_exports = {};
103
109
  __export(ui_editor_mention_menu_exports, {
104
110
  default: () => controller2
105
111
  });
106
112
 
107
- // ../../tools/migrate-html-next/generated/editor/components/controllers/shared/editor.js
113
+ // src/declarative/components/controllers/shared/editor.js
108
114
  var backgrounds = [
109
115
  ["background-none", "Default", ""],
110
116
  ["background-gray", "Gray", "#f3f4f6"],
@@ -168,7 +174,7 @@ function viewport() {
168
174
  return { left, top, width, height, right: left + width, bottom: top + height };
169
175
  }
170
176
 
171
- // ../../tools/migrate-html-next/generated/editor/components/controllers/ui-editor-mention-menu.js
177
+ // src/declarative/components/controllers/ui-editor-mention-menu.js
172
178
  function controller2(host) {
173
179
  const element = host.element;
174
180
  let selectedIndex = Number(host.state.selectedIndex ?? 0);
@@ -220,7 +226,7 @@ function controller2(host) {
220
226
  const onMousedown = (event) => event.preventDefault();
221
227
  const onClick = (event) => {
222
228
  const option = event.target.closest?.("[data-index]");
223
- if (option) host.dispatch("looma-editor-mention-menu-select", { index: Number(option.dataset.index) });
229
+ if (option) host.dispatch("select", { index: Number(option.dataset.index) });
224
230
  };
225
231
  const onMouseover = (event) => {
226
232
  const option = event.target.closest?.("[data-index]");
@@ -228,7 +234,7 @@ function controller2(host) {
228
234
  if (!Number.isFinite(index) || index === selectedIndex) return;
229
235
  selectedIndex = index;
230
236
  syncSelection();
231
- host.dispatch("looma-editor-mention-menu-highlight", { index });
237
+ host.dispatch("highlight", { index });
232
238
  };
233
239
  const onViewport = () => {
234
240
  if (host.state.open) position();
@@ -256,7 +262,7 @@ function controller2(host) {
256
262
  };
257
263
  }
258
264
 
259
- // ../../tools/migrate-html-next/generated/editor/components/controllers/ui-editor-slash-menu.js
265
+ // src/declarative/components/controllers/ui-editor-slash-menu.js
260
266
  var ui_editor_slash_menu_exports = {};
261
267
  __export(ui_editor_slash_menu_exports, {
262
268
  default: () => controller3
@@ -308,7 +314,7 @@ function controller3(host) {
308
314
  const onMousedown = (event) => event.preventDefault();
309
315
  const onClick = (event) => {
310
316
  const option = event.target.closest?.("[data-index]");
311
- if (option) host.dispatch("looma-editor-slash-menu-select", { index: Number(option.dataset.index) });
317
+ if (option) host.dispatch("select", { index: Number(option.dataset.index) });
312
318
  };
313
319
  const onMouseover = (event) => {
314
320
  const option = event.target.closest?.("[data-index]");
@@ -316,7 +322,7 @@ function controller3(host) {
316
322
  if (!Number.isFinite(index) || index === selectedIndex) return;
317
323
  selectedIndex = index;
318
324
  syncSelection();
319
- host.dispatch("looma-editor-slash-menu-highlight", { index });
325
+ host.dispatch("highlight", { index });
320
326
  };
321
327
  const onViewport = () => {
322
328
  if (host.state.open) position();
@@ -344,27 +350,27 @@ function controller3(host) {
344
350
  };
345
351
  }
346
352
 
347
- // ../../tools/migrate-html-next/generated/editor/components/controllers/ui-editor-table-context-menu.js
353
+ // src/declarative/components/controllers/ui-editor-table-context-menu.js
348
354
  var ui_editor_table_context_menu_exports = {};
349
355
  __export(ui_editor_table_context_menu_exports, {
350
356
  default: () => controller4
351
357
  });
352
358
  var sections = [
353
359
  ["Structure", [
354
- ["add-row-before", "Add row above", "panel-top", "canAddRowBefore"],
355
- ["add-row-after", "Add row below", "panel-bottom", "canAddRowAfter"],
356
- ["add-column-before", "Add column left", "panel-left", "canAddColumnBefore"],
357
- ["add-column-after", "Add column right", "panel-right", "canAddColumnAfter"]
360
+ ["add-row-before", "Add row above", "panel-top"],
361
+ ["add-row-after", "Add row below", "panel-bottom"],
362
+ ["add-column-before", "Add column left", "panel-left"],
363
+ ["add-column-after", "Add column right", "panel-right"]
358
364
  ]],
359
365
  ["Cells", [
360
366
  ["clear-cells", "Clear selected cells", "eraser"],
361
- ["merge-cells", "Merge cells", "merge", "canMergeCells"],
362
- ["split-cell", "Split cell", "split", "canSplitCell"]
367
+ ["merge-cells", "Merge cells", "merge"],
368
+ ["split-cell", "Split cell", "split"]
363
369
  ]],
364
370
  ["Table", [
365
- ["delete-row", "Delete row", "trash", "canDeleteRow", "danger"],
366
- ["delete-column", "Delete column", "trash", "canDeleteColumn", "danger"],
367
- ["delete-table", "Delete table", "trash", "canDeleteTable", "danger"]
371
+ ["delete-row", "Delete row", "trash", "danger"],
372
+ ["delete-column", "Delete column", "trash", "danger"],
373
+ ["delete-table", "Delete table", "trash", "danger"]
368
374
  ]]
369
375
  ];
370
376
  function controller4(host) {
@@ -393,24 +399,25 @@ function controller4(host) {
393
399
  const render = () => {
394
400
  element.hidden = !host.state.open;
395
401
  if (!host.state.open) return;
402
+ const enabled = new Set(Array.isArray(host.state.actions) ? host.state.actions : []);
396
403
  const background = String(host.state.cellBackground ?? "");
397
404
  const rendered = [];
398
- const swatches = backgrounds.map(([action, label, swatch]) => {
405
+ const swatches = backgrounds.filter(([action]) => enabled.has(action)).map(([action, label, swatch]) => {
399
406
  const selected = swatch === background;
400
407
  return `<button type="button" class="ui-editor-table-context-menu__swatch-button" role="menuitemradio" aria-checked="${selected}" data-action="${action}"${selected ? ' data-selected="true"' : ""}><span class="ui-editor-table-context-menu__swatch${swatch ? "" : " ui-editor-table-context-menu__swatch--default"}"${swatch ? ` style="--ui-editor-table-context-swatch:${swatch}"` : ""}></span><span class="ui-editor-table-context-menu__swatch-label">${label}</span></button>`;
401
408
  }).join("");
402
- rendered.push(`<div class="ui-editor-table-context-menu__section" role="none"><div class="ui-editor-table-context-menu__heading" role="presentation">Cell background</div><div class="ui-editor-table-context-menu__swatches" role="group" aria-label="Cell background">${swatches}</div></div>`);
409
+ if (swatches) rendered.push(`<div class="ui-editor-table-context-menu__section" role="none"><div class="ui-editor-table-context-menu__heading" role="presentation">Cell background</div><div class="ui-editor-table-context-menu__swatches" role="group" aria-label="Cell background">${swatches}</div></div>`);
403
410
  for (const [heading, actions] of sections) {
404
- const available = actions.filter(([, , , capability]) => !capability || host.state[capability]);
411
+ const available = actions.filter(([action]) => enabled.has(action));
405
412
  if (!available.length) continue;
406
- rendered.push(`<div class="ui-editor-table-context-menu__sep"></div><div class="ui-editor-table-context-menu__section" role="none"><div class="ui-editor-table-context-menu__heading" role="presentation">${heading}</div>${available.map(([action, label, iconName, , tone]) => `<button type="button" role="menuitem" data-action="${action}"${tone ? ` data-tone="${tone}"` : ""}>${icon(iconName)}<span>${label}</span></button>`).join("")}</div>`);
413
+ rendered.push(`<div class="ui-editor-table-context-menu__sep"></div><div class="ui-editor-table-context-menu__section" role="none"><div class="ui-editor-table-context-menu__heading" role="presentation">${heading}</div>${available.map(([action, label, iconName, tone]) => `<button type="button" role="menuitem" data-action="${action}"${tone ? ` data-tone="${tone}"` : ""}>${icon(iconName)}<span>${label}</span></button>`).join("")}</div>`);
407
414
  }
408
415
  element.innerHTML = `<div class="ui-editor-table-context-menu" role="menu">${rendered.join("")}</div>`;
409
416
  schedule();
410
417
  };
411
418
  const onClick = (event) => {
412
419
  const action = event.target.closest?.("[data-action]")?.dataset.action;
413
- if (action) host.dispatch("looma-editor-table-action", { action });
420
+ if (action) host.dispatch("action", { action });
414
421
  };
415
422
  element.addEventListener("click", onClick);
416
423
  window.addEventListener("resize", schedule);
@@ -428,28 +435,14 @@ function controller4(host) {
428
435
  };
429
436
  }
430
437
 
431
- // ../../tools/migrate-html-next/generated/editor/components/controllers/ui-editor-table-overlay.js
438
+ // src/declarative/components/controllers/ui-editor-table-overlay.js
432
439
  var ui_editor_table_overlay_exports = {};
433
440
  __export(ui_editor_table_overlay_exports, {
434
441
  default: () => controller5
435
442
  });
436
- function bounded2(value, fallback) {
437
- const parsed = Number.parseInt(String(value ?? fallback), 10);
438
- return Number.isFinite(parsed) ? Math.min(100, Math.max(1, parsed)) : fallback;
439
- }
440
443
  function fallbackBoundaries(segments, length) {
441
444
  return Array.from({ length: segments + 1 }, (_, index) => index * length / segments);
442
445
  }
443
- function numbers(value) {
444
- const values = Array.isArray(value) ? value.map(Number) : typeof value === "string" ? value.split(/[\s,]+/).filter(Boolean).map(Number) : [];
445
- return values.every(Number.isFinite) ? values : [];
446
- }
447
- function cellFrom(value) {
448
- const values = numbers(value);
449
- if (values.length !== 6) return null;
450
- const [left, top, width, height, rowIndex, columnIndex] = values;
451
- return { left, top, width, height, rowIndex, columnIndex };
452
- }
453
446
  function createProximity(scope, selector = ".ui-editor-table-overlay__handle[data-ui-affordance]", radius = 16) {
454
447
  const owner = scope.ownerDocument.defaultView;
455
448
  const pointerTarget = scope.ownerDocument;
@@ -517,13 +510,12 @@ function controller5(host) {
517
510
  const element = host.element;
518
511
  let activeControlKey = null;
519
512
  const proximity = createProximity(element);
520
- const boundaries = (axis, segments) => {
513
+ const boundaries = (axis) => {
521
514
  const geometry = host.state.geometry;
522
- const values = geometry?.[axis === "row" ? "rowBoundaries" : "columnBoundaries"] ?? host.state[axis === "row" ? "rowBoundaries" : "columnBoundaries"];
523
- const parsed = numbers(values);
524
- if (parsed.length === segments + 1) return parsed;
515
+ const values = geometry?.[axis === "row" ? "rowBoundaries" : "columnBoundaries"];
516
+ if (Array.isArray(values) && values.length >= 2 && values.every(Number.isFinite)) return values;
525
517
  const rect = element.getBoundingClientRect();
526
- return fallbackBoundaries(segments, axis === "row" ? rect.height : rect.width);
518
+ return fallbackBoundaries(3, axis === "row" ? rect.height : rect.width);
527
519
  };
528
520
  const insertion = (axis, index, position) => {
529
521
  const first = index === 0;
@@ -545,13 +537,11 @@ function controller5(host) {
545
537
  activeControlKey = null;
546
538
  return;
547
539
  }
548
- const rows = bounded2(host.state.rows, 3);
549
- const cols = bounded2(host.state.cols, 3);
550
- const rowBoundaries = boundaries("row", rows);
551
- const columnBoundaries = boundaries("column", cols);
540
+ const rowBoundaries = boundaries("row");
541
+ const columnBoundaries = boundaries("column");
552
542
  const geometry = host.state.geometry;
553
- const active = geometry?.activeCell ?? cellFrom(host.state.activeCell);
554
- const hovered = geometry?.hoveredCell ?? cellFrom(host.state.hoveredCell);
543
+ const active = geometry?.activeCell ?? null;
544
+ const hovered = geometry?.hoveredCell ?? null;
555
545
  element.innerHTML = `<div class="ui-editor-table-overlay" aria-label="Table controls"><div class="ui-editor-table-overlay__rows">${rowBoundaries.map((position, index) => insertion("row", index, position)).join("")}</div><div class="ui-editor-table-overlay__cols">${columnBoundaries.map((position, index) => insertion("col", index, position)).join("")}</div>${hovered ? selectors(hovered) : ""}${active ? menu(active) : ""}</div>`;
556
546
  proximity.refresh();
557
547
  };
@@ -565,15 +555,15 @@ function controller5(host) {
565
555
  if (!button) return;
566
556
  const action = button.dataset.action;
567
557
  if (action === "select-row" || action === "select-column") {
568
- host.dispatch("looma-editor-table-overlay-action", { action, rowIndex: Number(button.dataset.rowIndex), columnIndex: Number(button.dataset.columnIndex) });
558
+ host.dispatch("action", { action, rowIndex: Number(button.dataset.rowIndex), columnIndex: Number(button.dataset.columnIndex) });
569
559
  return;
570
560
  }
571
561
  if (action === "open-cell-menu") {
572
562
  const rect = button.getBoundingClientRect();
573
- host.dispatch("looma-editor-table-overlay-action", { action, rowIndex: Number(button.dataset.rowIndex), columnIndex: Number(button.dataset.columnIndex), anchor: { left: rect.left, top: rect.top, right: rect.right, bottom: rect.bottom } });
563
+ host.dispatch("action", { action, rowIndex: Number(button.dataset.rowIndex), columnIndex: Number(button.dataset.columnIndex), anchor: { left: rect.left, top: rect.top, right: rect.right, bottom: rect.bottom } });
574
564
  return;
575
565
  }
576
- host.dispatch("looma-editor-table-overlay-action", { action, boundaryIndex: Number(button.dataset.boundaryIndex) });
566
+ host.dispatch("action", { action, boundaryIndex: Number(button.dataset.boundaryIndex) });
577
567
  };
578
568
  const enter = (event) => setActive(event.target.closest?.("[data-control-key]")?.dataset.controlKey ?? null);
579
569
  const leave = (event) => setActive(event.relatedTarget instanceof Element ? event.relatedTarget.closest?.("[data-control-key]")?.dataset.controlKey ?? null : null);
@@ -595,15 +585,15 @@ function controller5(host) {
595
585
  };
596
586
  }
597
587
 
598
- // ../../tools/migrate-html-next/generated/editor/components/controllers/ui-editor-table-toolbar.js
588
+ // src/declarative/components/controllers/ui-editor-table-toolbar.js
599
589
  var ui_editor_table_toolbar_exports = {};
600
590
  __export(ui_editor_table_toolbar_exports, {
601
591
  default: () => controller6
602
592
  });
603
593
  var overflowSections = [
604
- ["Structure", [["add-row-before", "Add row above", "panel-top", "canAddRowBefore"], ["add-column-before", "Add column left", "panel-left", "canAddColumnBefore"]]],
605
- ["Cells", [["clear-cells", "Clear selected cells", "eraser"], ["merge-cells", "Merge selected cells", "merge", "canMergeCells"], ["split-cell", "Split merged cell", "split", "canSplitCell"]]],
606
- ["Table", [["delete-row", "Delete row", "trash", "canDeleteRow", "danger"], ["delete-column", "Delete column", "trash", "canDeleteColumn", "danger"], ["delete-table", "Delete table", "trash", "canDeleteTable", "danger"]]]
594
+ ["Structure", [["add-row-before", "Add row above", "panel-top"], ["add-column-before", "Add column left", "panel-left"]]],
595
+ ["Cells", [["clear-cells", "Clear selected cells", "eraser"], ["merge-cells", "Merge selected cells", "merge"], ["split-cell", "Split merged cell", "split"]]],
596
+ ["Table", [["delete-row", "Delete row", "trash", "danger"], ["delete-column", "Delete column", "trash", "danger"], ["delete-table", "Delete table", "trash", "danger"]]]
607
597
  ];
608
598
  function controller6(host) {
609
599
  const element = host.element;
@@ -614,18 +604,24 @@ function controller6(host) {
614
604
  overflowOpen = false;
615
605
  return;
616
606
  }
607
+ const enabled = new Set(Array.isArray(host.state.actions) ? host.state.actions : []);
617
608
  const active = `align-${host.state.cellAlignment === "center" || host.state.cellAlignment === "right" ? host.state.cellAlignment : "left"}`;
618
- const alignments = [["align-left", "Align left"], ["align-center", "Align center"], ["align-right", "Align right"]];
619
- const structure = [["add-row-after", "Add row", "rows", "canAddRowAfter"], ["add-column-after", "Add column", "columns", "canAddColumnAfter"]].filter(([, , , capability]) => host.state[capability]);
609
+ const alignments = [["align-left", "Align left"], ["align-center", "Align center"], ["align-right", "Align right"]].filter(([action]) => enabled.has(action));
610
+ const structure = [["add-row-after", "Add row", "rows"], ["add-column-after", "Add column", "columns"]].filter(([action]) => enabled.has(action));
620
611
  const background = String(host.state.cellBackground ?? "");
621
- const menu = overflowOpen ? `<div class="ui-editor-table-toolbar__menu" role="menu" aria-label="More table actions"><div class="ui-editor-table-toolbar__menu-section" role="none"><div class="ui-editor-table-toolbar__menu-heading" role="presentation">Background</div><div class="ui-editor-table-toolbar__swatches" role="group" aria-label="Cell background">${backgrounds.map(([action, label, swatch]) => {
612
+ const backgroundActions = backgrounds.filter(([action]) => enabled.has(action));
613
+ const overflowGroups = overflowSections.map(([heading, actions]) => [heading, actions.filter(([action]) => enabled.has(action))]);
614
+ const hasOverflow = backgroundActions.length > 0 || overflowGroups.some(([, actions]) => actions.length > 0);
615
+ if (!hasOverflow) overflowOpen = false;
616
+ const menu = overflowOpen ? `<div class="ui-editor-table-toolbar__menu" role="menu" aria-label="More table actions">${backgroundActions.length ? `<div class="ui-editor-table-toolbar__menu-section" role="none"><div class="ui-editor-table-toolbar__menu-heading" role="presentation">Background</div><div class="ui-editor-table-toolbar__swatches" role="group" aria-label="Cell background">${backgroundActions.map(([action, label, swatch]) => {
622
617
  const selected = swatch === background;
623
618
  return `<button type="button" class="ui-editor-table-toolbar__swatch-button" role="menuitemradio" aria-checked="${selected}" data-action="${action}"${selected ? ' data-selected="true"' : ""}><span class="ui-editor-table-toolbar__swatch${swatch ? "" : " ui-editor-table-toolbar__swatch--default"}"${swatch ? ` style="--ui-editor-table-toolbar-swatch:${swatch}"` : ""}></span><span>${label}</span></button>`;
624
- }).join("")}</div></div>${overflowSections.map(([heading, actions]) => {
625
- const available = actions.filter(([, , , capability]) => !capability || host.state[capability]);
626
- return available.length ? `<div class="ui-editor-table-toolbar__menu-section ui-editor-table-toolbar__menu-section--divided" role="none"><div class="ui-editor-table-toolbar__menu-heading" role="presentation">${heading}</div>${available.map(([action, label, iconName, , tone]) => `<button type="button" role="menuitem" data-action="${action}"${tone ? ` data-tone="${tone}"` : ""}>${icon(iconName)}<span>${label}</span></button>`).join("")}</div>` : "";
627
- }).join("")}</div>` : "";
628
- element.innerHTML = `<div class="ui-editor-table-toolbar" role="toolbar" aria-label="Table actions"><div class="ui-editor-table-toolbar__group" role="group" aria-label="Cell alignment">${alignments.map(([action, label]) => `<button type="button" class="ui-editor-table-toolbar__icon-button" data-action="${action}" aria-label="${label}" title="${label}" aria-pressed="${action === active}" data-active="${action === active}">${icon(action)}</button>`).join("")}</div>${structure.length ? '<div class="ui-editor-table-toolbar__sep" aria-hidden="true"></div><div class="ui-editor-table-toolbar__group" role="group" aria-label="Table structure">' + structure.map(([action, label, iconName]) => `<button type="button" data-action="${action}">${icon(iconName)}<span>${label}</span></button>`).join("") + "</div>" : ""}<div class="ui-editor-table-toolbar__sep" aria-hidden="true"></div><div class="ui-editor-table-toolbar__overflow"><button type="button" class="ui-editor-table-toolbar__more" data-action="toggle-overflow" aria-haspopup="menu" aria-expanded="${overflowOpen}">${icon("table")}<span>Table options</span>${icon("chevron-down", "looma-icon looma-icon--chevron")}</button>${menu}</div></div>`;
619
+ }).join("")}</div></div>` : ""}${overflowGroups.map(([heading, actions]) => actions.length ? `<div class="ui-editor-table-toolbar__menu-section ui-editor-table-toolbar__menu-section--divided" role="none"><div class="ui-editor-table-toolbar__menu-heading" role="presentation">${heading}</div>${actions.map(([action, label, iconName, tone]) => `<button type="button" role="menuitem" data-action="${action}"${tone ? ` data-tone="${tone}"` : ""}>${icon(iconName)}<span>${label}</span></button>`).join("")}</div>` : "").join("")}</div>` : "";
620
+ const groups = [];
621
+ if (alignments.length) groups.push(`<div class="ui-editor-table-toolbar__group" role="group" aria-label="Cell alignment">${alignments.map(([action, label]) => `<button type="button" class="ui-editor-table-toolbar__icon-button" data-action="${action}" aria-label="${label}" title="${label}" aria-pressed="${action === active}" data-active="${action === active}">${icon(action)}</button>`).join("")}</div>`);
622
+ if (structure.length) groups.push(`<div class="ui-editor-table-toolbar__group" role="group" aria-label="Table structure">${structure.map(([action, label, iconName]) => `<button type="button" data-action="${action}">${icon(iconName)}<span>${label}</span></button>`).join("")}</div>`);
623
+ if (hasOverflow) groups.push(`<div class="ui-editor-table-toolbar__overflow"><button type="button" class="ui-editor-table-toolbar__more" data-action="toggle-overflow" aria-haspopup="menu" aria-expanded="${overflowOpen}">${icon("table")}<span>Table options</span>${icon("chevron-down", "looma-icon looma-icon--chevron")}</button>${menu}</div>`);
624
+ element.innerHTML = `<div class="ui-editor-table-toolbar" role="toolbar" aria-label="Table actions">${groups.join('<div class="ui-editor-table-toolbar__sep" aria-hidden="true"></div>')}</div>`;
629
625
  };
630
626
  const onClick = (event) => {
631
627
  const action = event.target.closest?.("[data-action]")?.dataset.action;
@@ -637,7 +633,7 @@ function controller6(host) {
637
633
  }
638
634
  overflowOpen = false;
639
635
  render();
640
- host.dispatch("looma-editor-table-action", { action });
636
+ host.dispatch("action", { action });
641
637
  };
642
638
  const onOutside = (event) => {
643
639
  if (!overflowOpen || event.composedPath().includes(element)) return;
@@ -655,7 +651,7 @@ function controller6(host) {
655
651
  };
656
652
  }
657
653
 
658
- // ../../tools/migrate-html-next/generated/editor/components/controllers/ui-editor-toolbar.js
654
+ // src/declarative/components/controllers/ui-editor-toolbar.js
659
655
  var ui_editor_toolbar_exports = {};
660
656
  __export(ui_editor_toolbar_exports, {
661
657
  default: () => controller7
@@ -666,14 +662,14 @@ function controller7(host) {
666
662
  if (!element.hasAttribute("aria-label")) element.setAttribute("aria-label", "Editor toolbar");
667
663
  }
668
664
 
669
- // ../../tools/migrate-html-next/generated/adoption/editor/registry.js
665
+ // src/declarative/registry.js
670
666
  var records = [
671
- { tag: "ui-editor-insert-table-grid", source: '<template component="ui-editor-insert-table-grid" status="early" summary="Looma ui-editor-insert-table-grid editor surface." controller="./controllers/ui-editor-insert-table-grid.js">\n <defs>\n <prop name="open" type="boolean" default="false">open value.</prop>\n <prop name="maxRows" type="integer" default="8">maxRows value.</prop>\n <prop name="maxCols" type="integer" default="8">maxCols value.</prop>\n <event name="looma-editor-insert-table" type="object({ rows: integer, cols: integer, withHeaderRow: boolean })"></event>\n </defs>\n <div></div>\n</template>\n', controller: ui_editor_insert_table_grid_exports },
672
- { tag: "ui-editor-mention-menu", source: '<template component="ui-editor-mention-menu" status="early" summary="Looma ui-editor-mention-menu editor surface." controller="./controllers/ui-editor-mention-menu.js">\n <defs>\n <prop name="open" type="boolean" default="false">open value.</prop>\n <prop name="query" type="string" default="">query value.</prop>\n <prop name="items" type="list(object({ id: string, label: string, detail?: string, initials?: string }))">items value.</prop>\n <prop name="selectedIndex" type="integer" default="0">selectedIndex value.</prop>\n <prop name="loading" type="boolean" default="false">loading value.</prop>\n <prop name="anchorRect" type="object({ left?: number, top?: number, right?: number, bottom?: number, x?: number, y?: number, width?: number, height?: number }) | null">anchorRect value.</prop>\n <event name="looma-editor-mention-menu-highlight" type="object({ index: integer })"></event>\n <event name="looma-editor-mention-menu-select" type="object({ index: integer })"></event>\n </defs>\n <div .items="items" .anchorRect="anchorRect"></div>\n</template>\n', controller: ui_editor_mention_menu_exports },
673
- { tag: "ui-editor-slash-menu", source: '<template component="ui-editor-slash-menu" status="early" summary="Looma ui-editor-slash-menu editor surface." controller="./controllers/ui-editor-slash-menu.js">\n <defs>\n <prop name="open" type="boolean" default="false">open value.</prop>\n <prop name="query" type="string" default="">query value.</prop>\n <prop name="items" type="list(object({ title: string, description: string, icon: string }))">items value.</prop>\n <prop name="selectedIndex" type="integer" default="0">selectedIndex value.</prop>\n <prop name="anchorRect" type="object({ left?: number, top?: number, right?: number, bottom?: number, x?: number, y?: number, width?: number, height?: number }) | null">anchorRect value.</prop>\n <event name="looma-editor-slash-menu-highlight" type="object({ index: integer })"></event>\n <event name="looma-editor-slash-menu-select" type="object({ index: integer })"></event>\n </defs>\n <div .items="items" .anchorRect="anchorRect"></div>\n</template>\n', controller: ui_editor_slash_menu_exports },
674
- { tag: "ui-editor-table-context-menu", source: '<template component="ui-editor-table-context-menu" status="early" summary="Looma ui-editor-table-context-menu editor surface." controller="./controllers/ui-editor-table-context-menu.js">\n <defs>\n <prop name="open" type="boolean" default="false">open value.</prop>\n <prop name="cellBackground" type="string" default="">cellBackground value.</prop>\n <prop name="canAddRowBefore" type="boolean" default="false">canAddRowBefore value.</prop>\n <prop name="canAddRowAfter" type="boolean" default="false">canAddRowAfter value.</prop>\n <prop name="canAddColumnBefore" type="boolean" default="false">canAddColumnBefore value.</prop>\n <prop name="canAddColumnAfter" type="boolean" default="false">canAddColumnAfter value.</prop>\n <prop name="canDeleteRow" type="boolean" default="false">canDeleteRow value.</prop>\n <prop name="canDeleteColumn" type="boolean" default="false">canDeleteColumn value.</prop>\n <prop name="canDeleteTable" type="boolean" default="false">canDeleteTable value.</prop>\n <prop name="canMergeCells" type="boolean" default="false">canMergeCells value.</prop>\n <prop name="canSplitCell" type="boolean" default="false">canSplitCell value.</prop>\n <event name="looma-editor-table-action" type="object({ action: string })"></event>\n </defs>\n <div></div>\n</template>\n', controller: ui_editor_table_context_menu_exports },
675
- { tag: "ui-editor-table-overlay", source: '<template component="ui-editor-table-overlay" status="early" summary="Looma ui-editor-table-overlay editor surface." controller="./controllers/ui-editor-table-overlay.js">\n <defs>\n <prop name="open" type="boolean" default="false">open value.</prop>\n <prop name="rows" type="integer" default="3">rows value.</prop>\n <prop name="cols" type="integer" default="3">cols value.</prop>\n <prop name="rowBoundaries" type="string | list(number)">rowBoundaries value.</prop>\n <prop name="columnBoundaries" type="string | list(number)">columnBoundaries value.</prop>\n <prop name="activeCell" type="string | list(number)">activeCell value.</prop>\n <prop name="hoveredCell" type="string | list(number)">hoveredCell value.</prop>\n <prop name="geometry" type="object({ rowBoundaries: list(number), columnBoundaries: list(number), activeCell: object({ left: number, top: number, width: number, height: number, rowIndex: integer, columnIndex: integer }) | null, hoveredCell?: object({ left: number, top: number, width: number, height: number, rowIndex: integer, columnIndex: integer }) | null }) | null">geometry value.</prop>\n <event name="looma-editor-table-overlay-action" type="object({ action: string, boundaryIndex?: integer, rowIndex?: integer, columnIndex?: integer, anchor?: object({ left: number, top: number, right: number, bottom: number }) })"></event>\n </defs>\n <div .geometry="geometry"></div>\n</template>\n', controller: ui_editor_table_overlay_exports },
676
- { tag: "ui-editor-table-toolbar", source: '<template component="ui-editor-table-toolbar" status="early" summary="Looma ui-editor-table-toolbar editor surface." controller="./controllers/ui-editor-table-toolbar.js">\n <defs>\n <prop name="open" type="boolean" default="false">open value.</prop>\n <prop name="cellAlignment" type="left | center | right" default="left">cellAlignment value.</prop>\n <prop name="cellBackground" type="string" default="">cellBackground value.</prop>\n <prop name="canAddRowBefore" type="boolean" default="false">canAddRowBefore value.</prop>\n <prop name="canAddRowAfter" type="boolean" default="false">canAddRowAfter value.</prop>\n <prop name="canAddColumnBefore" type="boolean" default="false">canAddColumnBefore value.</prop>\n <prop name="canAddColumnAfter" type="boolean" default="false">canAddColumnAfter value.</prop>\n <prop name="canDeleteRow" type="boolean" default="false">canDeleteRow value.</prop>\n <prop name="canDeleteColumn" type="boolean" default="false">canDeleteColumn value.</prop>\n <prop name="canDeleteTable" type="boolean" default="false">canDeleteTable value.</prop>\n <prop name="canMergeCells" type="boolean" default="false">canMergeCells value.</prop>\n <prop name="canSplitCell" type="boolean" default="false">canSplitCell value.</prop>\n <event name="looma-editor-table-action" type="object({ action: string })"></event>\n </defs>\n <div></div>\n</template>\n', controller: ui_editor_table_toolbar_exports },
667
+ { tag: "ui-editor-insert-table-grid", source: '<template component="ui-editor-insert-table-grid" status="early" summary="Looma ui-editor-insert-table-grid editor surface." controller="./controllers/ui-editor-insert-table-grid.js">\n <defs>\n <prop name="open" type="boolean" default="false">open value.</prop>\n <prop name="maxRows" type="integer" default="8">maxRows value.</prop>\n <prop name="maxCols" type="integer" default="8">maxCols value.</prop>\n <prop name="headerRow" type="boolean" default="false">Whether table insertion initially includes a header row.</prop>\n <event name="insert" type="object({ rows: integer, cols: integer, withHeaderRow: boolean })"></event>\n </defs>\n <div></div>\n</template>\n', controller: ui_editor_insert_table_grid_exports },
668
+ { tag: "ui-editor-mention-menu", source: '<template component="ui-editor-mention-menu" status="early" summary="Looma ui-editor-mention-menu editor surface." controller="./controllers/ui-editor-mention-menu.js">\n <defs>\n <prop name="open" type="boolean" default="false">open value.</prop>\n <prop name="query" type="string" default="">query value.</prop>\n <prop name="items" type="list(object({ id: string, label: string, detail?: string, initials?: string }))">items value.</prop>\n <prop name="selectedIndex" type="integer" default="0">selectedIndex value.</prop>\n <prop name="loading" type="boolean" default="false">loading value.</prop>\n <prop name="anchorRect" type="object({ left?: number, top?: number, right?: number, bottom?: number, x?: number, y?: number, width?: number, height?: number }) | null">anchorRect value.</prop>\n <event name="highlight" type="object({ index: integer })"></event>\n <event name="select" type="object({ index: integer })"></event>\n </defs>\n <div></div>\n</template>\n', controller: ui_editor_mention_menu_exports },
669
+ { tag: "ui-editor-slash-menu", source: '<template component="ui-editor-slash-menu" status="early" summary="Looma ui-editor-slash-menu editor surface." controller="./controllers/ui-editor-slash-menu.js">\n <defs>\n <prop name="open" type="boolean" default="false">open value.</prop>\n <prop name="query" type="string" default="">query value.</prop>\n <prop name="items" type="list(object({ title: string, description: string, icon: string }))">items value.</prop>\n <prop name="selectedIndex" type="integer" default="0">selectedIndex value.</prop>\n <prop name="anchorRect" type="object({ left?: number, top?: number, right?: number, bottom?: number, x?: number, y?: number, width?: number, height?: number }) | null">anchorRect value.</prop>\n <event name="highlight" type="object({ index: integer })"></event>\n <event name="select" type="object({ index: integer })"></event>\n </defs>\n <div></div>\n</template>\n', controller: ui_editor_slash_menu_exports },
670
+ { tag: "ui-editor-table-context-menu", source: '<template component="ui-editor-table-context-menu" status="early" summary="Presents the table commands an editor explicitly enables for the current cell selection." controller="./controllers/ui-editor-table-context-menu.js">\n <defs>\n <prop name="open" type="boolean" default="false">Shows the menu at the position owned by the editor integration.</prop>\n <prop name="cellBackground" type="string" default="">Identifies the background choice that is active for the current cell.</prop>\n <prop name="actions" type="list(align-left | align-center | align-right | background-none | background-gray | background-yellow | background-blue | background-green | background-red | add-row-before | add-row-after | add-column-before | add-column-after | clear-cells | merge-cells | split-cell | delete-row | delete-column | delete-table)">The complete set of commands the current selection permits; omitted commands are not rendered.</prop>\n <event name="action" type="object({ action: string })"></event>\n </defs>\n <div></div>\n</template>\n', controller: ui_editor_table_context_menu_exports },
671
+ { tag: "ui-editor-table-overlay", source: '<template component="ui-editor-table-overlay" status="early" summary="Projects editor-measured table geometry into insertion, selection, and cell-action controls without owning document mutations." controller="./controllers/ui-editor-table-overlay.js">\n <defs>\n <prop name="open" type="boolean" default="false">Shows the controls after the editor has supplied current geometry.</prop>\n <prop name="geometry" type="object({ rowBoundaries: list(number), columnBoundaries: list(number), activeCell: object({ left: number, top: number, width: number, height: number, rowIndex: integer, columnIndex: integer }) | null, hoveredCell?: object({ left: number, top: number, width: number, height: number, rowIndex: integer, columnIndex: integer }) | null }) | null">The authoritative editor-measured table boundaries and active or hovered cell rectangles.</prop>\n <event name="action" type="object({ action: string, boundaryIndex?: integer, rowIndex?: integer, columnIndex?: integer, anchor?: object({ left: number, top: number, right: number, bottom: number }) })"></event>\n </defs>\n <div></div>\n</template>\n', controller: ui_editor_table_overlay_exports },
672
+ { tag: "ui-editor-table-toolbar", source: '<template component="ui-editor-table-toolbar" status="early" summary="Offers selection-aware table commands without deciding which mutations the editor performs." controller="./controllers/ui-editor-table-toolbar.js">\n <defs>\n <prop name="open" type="boolean" default="false">Shows the toolbar while a table selection is active.</prop>\n <prop name="cellAlignment" type="left | center | right" default="left">Marks the alignment command that is active for the current cell.</prop>\n <prop name="cellBackground" type="string" default="">Identifies the background choice that is active for the current cell.</prop>\n <prop name="actions" type="list(align-left | align-center | align-right | background-none | background-gray | background-yellow | background-blue | background-green | background-red | add-row-before | add-row-after | add-column-before | add-column-after | clear-cells | merge-cells | split-cell | delete-row | delete-column | delete-table)">The complete set of commands the current selection permits; omitted commands are not rendered.</prop>\n <event name="action" type="object({ action: string })"></event>\n </defs>\n <div></div>\n</template>\n', controller: ui_editor_table_toolbar_exports },
677
673
  { tag: "ui-editor-toolbar", source: '<template component="ui-editor-toolbar" status="early" summary="Looma ui-editor-toolbar editor surface." controller="./controllers/ui-editor-toolbar.js">\n <defs>\n\n </defs>\n <div><slot></slot></div>\n</template>\n', controller: ui_editor_toolbar_exports }
678
674
  ];
679
675
  var styles = `/**
@@ -983,6 +979,38 @@ var styles = `/**
983
979
  stroke-width: var(--ui-editor-toolbar-icon-stroke-width, 1.75);
984
980
  }
985
981
 
982
+ .looma-editor__sticky-toolbar-shell {
983
+ position: sticky;
984
+ top: var(--ui-editor-sticky-toolbar-top, 0);
985
+ z-index: var(--ui-editor-toolbar-z-index, 10);
986
+ min-width: 0;
987
+ background: var(--ui-editor-toolbar-bg, var(--ui-surface, #fff));
988
+ border-bottom: 1px solid var(--ui-editor-toolbar-border, var(--ui-border, #e5e7eb));
989
+ }
990
+
991
+ .looma-editor__sticky-toolbar-shell [data-component-root~="ui-editor-toolbar"] {
992
+ display: flex;
993
+ align-items: center;
994
+ justify-content: flex-start;
995
+ gap: var(--ui-editor-toolbar-gap, 2px);
996
+ min-height: var(--ui-editor-toolbar-min-height, 48px);
997
+ padding:
998
+ var(--ui-editor-toolbar-padding-block, 6px)
999
+ var(--ui-editor-toolbar-padding-inline, 12px);
1000
+ overflow-x: auto;
1001
+ overscroll-behavior-inline: contain;
1002
+ scrollbar-width: none;
1003
+ white-space: nowrap;
1004
+ }
1005
+
1006
+ .looma-editor__sticky-toolbar-shell [data-component-root~="ui-editor-toolbar"]::-webkit-scrollbar {
1007
+ display: none;
1008
+ }
1009
+
1010
+ .looma-editor__sticky-toolbar-shell [data-component-root~="ui-editor-toolbar"] > * {
1011
+ flex: 0 0 auto;
1012
+ }
1013
+
986
1014
  .looma-editor__table-picker-popover,
987
1015
  .looma-editor__table-toolbar-shell,
988
1016
  .looma-editor__table-menu-shell {
@@ -2360,6 +2388,7 @@ if (typeof HTMLElement !== "undefined") {
2360
2388
  this.#rows = this.#readBoundedNumber("rows", DEFAULT_ROWS);
2361
2389
  this.#cols = this.#readBoundedNumber("cols", DEFAULT_COLS);
2362
2390
  this.render();
2391
+ this.addEventListener("mousedown", this.onMouseDown);
2363
2392
  this.addEventListener("click", this.onClick);
2364
2393
  this.addEventListener("pointerover", this.onPointerOver);
2365
2394
  this.addEventListener("pointerout", this.onPointerOut);
@@ -2371,6 +2400,7 @@ if (typeof HTMLElement !== "undefined") {
2371
2400
  });
2372
2401
  }
2373
2402
  disconnectedCallback() {
2403
+ this.removeEventListener("mousedown", this.onMouseDown);
2374
2404
  this.removeEventListener("click", this.onClick);
2375
2405
  this.removeEventListener("pointerover", this.onPointerOver);
2376
2406
  this.removeEventListener("pointerout", this.onPointerOut);
@@ -2423,10 +2453,14 @@ if (typeof HTMLElement !== "undefined") {
2423
2453
  }
2424
2454
  dispatch(detail) {
2425
2455
  this.dispatchEvent(new CustomEvent(
2426
- "looma-editor-table-overlay-action",
2456
+ "action",
2427
2457
  { detail, bubbles: true, composed: true }
2428
2458
  ));
2429
2459
  }
2460
+ onMouseDown = (event) => {
2461
+ const target = event.target instanceof Element ? event.target.closest("button[data-action]") : null;
2462
+ if (target) event.preventDefault();
2463
+ };
2430
2464
  onClick = (event) => {
2431
2465
  const target = event.target.closest("button[data-action]");
2432
2466
  if (!target) return;