@shadow-garden/bapbong-ui 0.13.3 → 0.14.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.
package/dist/index.cjs CHANGED
@@ -22,25 +22,38 @@ var index_exports = {};
22
22
  __export(index_exports, {
23
23
  COLOR_PALETTE: () => COLOR_PALETTE,
24
24
  Dialog: () => Dialog,
25
+ RECENT_MAX: () => RECENT_MAX,
26
+ SPECIAL_CHARACTERS: () => SPECIAL_CHARACTERS,
27
+ SYMBOL_GROUPS: () => SYMBOL_GROUPS,
28
+ SYMBOL_NAMES: () => SYMBOL_NAMES,
25
29
  cmToPx: () => cmToPx,
30
+ codePointLabel: () => codePointLabel,
26
31
  colorButton: () => colorButton,
27
32
  createFindDialog: () => createFindDialog,
28
33
  createSectionChip: () => createSectionChip,
34
+ createSymbolDialog: () => createSymbolDialog,
29
35
  defaultMenus: () => defaultMenus,
30
36
  defaultToolbarGroups: () => defaultToolbarGroups,
37
+ installScrollbars: () => installScrollbars,
31
38
  marginPresetPicker: () => marginPresetPicker,
32
39
  mountMenubar: () => mountMenubar,
33
40
  mountToolbar: () => mountToolbar,
34
41
  openCellProperties: () => openCellProperties,
35
42
  openColorPicker: () => openColorPicker,
36
43
  openFontDialog: () => openFontDialog,
44
+ openKeyboardShortcutsDialog: () => openKeyboardShortcutsDialog,
37
45
  openMarginsDialog: () => openMarginsDialog,
38
46
  openPageSizeDialog: () => openPageSizeDialog,
47
+ openSymbolPopover: () => openSymbolPopover,
39
48
  orientationPicker: () => orientationPicker,
40
49
  pageNumberPicker: () => pageNumberPicker,
41
50
  pageSizePicker: () => pageSizePicker,
51
+ panelAnchor: () => panelAnchor,
52
+ parseCodePoint: () => parseCodePoint,
42
53
  promptDialog: () => promptDialog,
54
+ pushRecent: () => pushRecent,
43
55
  pxToCm: () => pxToCm,
56
+ searchSymbols: () => searchSymbols,
44
57
  sectionPaperPanel: () => sectionPaperPanel,
45
58
  showContextMenu: () => showContextMenu,
46
59
  showLinkPanel: () => showLinkPanel,
@@ -51,15 +64,25 @@ __export(index_exports, {
51
64
  module.exports = __toCommonJS(index_exports);
52
65
 
53
66
  // packages/ui/src/lib/internal.ts
67
+ var import_bapbong_contracts = require("@shadow-garden/bapbong-contracts");
68
+ var IS_MAC = typeof navigator !== "undefined" ? /Mac|iP(hone|[oa]d)/.test(navigator.platform) : false;
69
+ function shortcutLabel(command, registries) {
70
+ for (const r of registries) {
71
+ if (!r) continue;
72
+ for (const b of r)
73
+ if (b.command === command) return (0, import_bapbong_contracts.keyLabel)(b.key, IS_MAC);
74
+ }
75
+ return void 0;
76
+ }
54
77
  function injectStyle(id, css) {
55
78
  if (document.getElementById(id)) return;
56
- const el2 = document.createElement("style");
57
- el2.id = id;
58
- el2.textContent = css;
59
- document.head.appendChild(el2);
79
+ const el5 = document.createElement("style");
80
+ el5.id = id;
81
+ el5.textContent = css;
82
+ document.head.appendChild(el5);
60
83
  }
61
- function placeFloating(el2, anchor) {
62
- const r = el2.getBoundingClientRect();
84
+ function placeFloating(el5, anchor) {
85
+ const r = el5.getBoundingClientRect();
63
86
  const pad = 6;
64
87
  const gap = 6;
65
88
  const x = Math.max(
@@ -69,16 +92,75 @@ function placeFloating(el2, anchor) {
69
92
  const below = anchor.y + anchor.height + gap;
70
93
  const above = anchor.y - gap - r.height;
71
94
  const y = below + r.height <= window.innerHeight - pad || above < pad ? Math.min(below, window.innerHeight - r.height - pad) : above;
72
- el2.style.left = `${x}px`;
73
- el2.style.top = `${y}px`;
95
+ el5.style.left = `${x}px`;
96
+ el5.style.top = `${y}px`;
97
+ }
98
+ function rovingGrid(cells) {
99
+ let cur = [0, 0];
100
+ const at = (r, c) => cells[r]?.[c];
101
+ const focusCell = (r, c) => {
102
+ const rows = cells.length;
103
+ if (rows === 0) return;
104
+ const nr = Math.min(rows - 1, Math.max(0, r));
105
+ const nc = Math.min(cells[nr].length - 1, Math.max(0, c));
106
+ const prev = at(cur[0], cur[1]);
107
+ if (prev) prev.tabIndex = -1;
108
+ const next = at(nr, nc);
109
+ if (!next) return;
110
+ next.tabIndex = 0;
111
+ next.focus();
112
+ cur = [nr, nc];
113
+ };
114
+ cells.forEach(
115
+ (row, r) => row.forEach((cell, c) => {
116
+ cell.setAttribute("role", "gridcell");
117
+ cell.tabIndex = r === 0 && c === 0 ? 0 : -1;
118
+ cell.addEventListener("keydown", (e) => {
119
+ const delta = {
120
+ ArrowRight: [0, 1],
121
+ ArrowLeft: [0, -1],
122
+ ArrowDown: [1, 0],
123
+ ArrowUp: [-1, 0],
124
+ Home: [0, -Infinity],
125
+ End: [0, Infinity]
126
+ };
127
+ const d = delta[e.key];
128
+ if (!d) return;
129
+ e.preventDefault();
130
+ focusCell(r + d[0], c + d[1]);
131
+ });
132
+ cell.addEventListener("focus", () => {
133
+ cur = [r, c];
134
+ });
135
+ })
136
+ );
137
+ return { focusCell };
74
138
  }
75
139
 
76
140
  // packages/ui/src/lib/dialog.ts
141
+ function panelAnchor(o) {
142
+ return () => {
143
+ const areaEl = o.area();
144
+ if (!areaEl) return null;
145
+ const a = areaEl.getBoundingClientRect();
146
+ let top = a.top;
147
+ for (const el5 of o.avoid?.() ?? []) {
148
+ if (!el5) continue;
149
+ const b = el5.getBoundingClientRect();
150
+ const overlapsX = b.right > a.left && b.left < a.right;
151
+ const overTop = b.top < a.bottom && b.bottom > top;
152
+ if (overlapsX && overTop) top = Math.max(top, b.bottom);
153
+ }
154
+ if (top >= a.bottom) return null;
155
+ return new DOMRect(a.left, top, a.width, a.bottom - top);
156
+ };
157
+ }
77
158
  var STYLE = `
78
159
  .bb-dialog{position:fixed;inset:auto;z-index:1100;margin:0;padding:0;border:1px solid var(--bb-ui-pop-border,var(--bb-ui-border,#e3e3e0));border-radius:10px;background:var(--bb-ui-dialog-bg,var(--bb-ui-menu-bg,#fff));-webkit-backdrop-filter:var(--bb-ui-dialog-filter,none);backdrop-filter:var(--bb-ui-dialog-filter,none);color:var(--bb-ui-fg,#2c2c2a);box-shadow:0 12px 40px rgba(0,0,0,.18);font-family:var(--bb-ui-font,system-ui,-apple-system,sans-serif);min-width:280px;max-width:min(92vw,440px)}
79
160
  .bb-dialog *{box-sizing:border-box}
80
161
  .bb-dialog::backdrop{background:rgba(0,0,0,.28)}
81
162
  .bb-dialog-modal{top:50%;left:50%;transform:translate(-50%,-50%)}
163
+ .bb-dialog-wide{max-width:min(92vw,640px)}
82
164
  .bb-dialog-float{top:62px;right:24px}
83
165
  .bb-dialog-header{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:9px 10px 9px 12px;border-bottom:1px solid var(--bb-ui-border,#e3e3e0)}
84
166
  .bb-dialog-title{font-size:13px;font-weight:600}
@@ -112,8 +194,8 @@ var Dialog = class {
112
194
  injectStyle("bb-ui-dialog-styles", STYLE);
113
195
  this.modal = options.modal ?? false;
114
196
  this.anchor = options.anchor;
115
- const el2 = document.createElement("dialog");
116
- el2.className = `bb-dialog ${this.modal ? "bb-dialog-modal" : "bb-dialog-float"}` + (options.className ? ` ${options.className}` : "");
197
+ const el5 = document.createElement("dialog");
198
+ el5.className = `bb-dialog ${this.modal ? "bb-dialog-modal" : "bb-dialog-float"}` + (options.wide ? " bb-dialog-wide" : "") + (options.className ? ` ${options.className}` : "");
117
199
  const header = document.createElement("div");
118
200
  header.className = "bb-dialog-header";
119
201
  this.title = document.createElement("span");
@@ -128,18 +210,18 @@ var Dialog = class {
128
210
  header.append(this.title, close);
129
211
  this.body = document.createElement("div");
130
212
  this.body.className = "bb-dialog-body";
131
- el2.append(header, this.body);
132
- document.body.appendChild(el2);
133
- el2.addEventListener("close", () => {
213
+ el5.append(header, this.body);
214
+ document.body.appendChild(el5);
215
+ el5.addEventListener("close", () => {
134
216
  this.detachReposition();
135
217
  this.closeListeners.forEach((cb) => cb());
136
218
  });
137
219
  if (!this.modal) {
138
- el2.addEventListener("keydown", (e) => {
220
+ el5.addEventListener("keydown", (e) => {
139
221
  if (e.key === "Escape") this.close();
140
222
  });
141
223
  }
142
- this.el = el2;
224
+ this.el = el5;
143
225
  }
144
226
  attachReposition() {
145
227
  window.addEventListener("scroll", this.reposition, true);
@@ -398,45 +480,24 @@ function openColorPicker({
398
480
  grid.className = "bb-cpk-grid";
399
481
  grid.setAttribute("role", "grid");
400
482
  const cells = [];
401
- COLOR_PALETTE.forEach((row, r2) => {
483
+ COLOR_PALETTE.forEach((row) => {
402
484
  const rowCells = [];
403
- row.forEach((color, c) => {
485
+ row.forEach((color) => {
404
486
  const sw = document.createElement("button");
405
487
  sw.type = "button";
406
488
  sw.className = "bb-cpk-sw";
407
489
  sw.style.background = color;
408
490
  sw.title = color;
409
- sw.setAttribute("role", "gridcell");
410
491
  sw.setAttribute("aria-label", color);
411
- sw.tabIndex = -1;
412
492
  if (color.toUpperCase() === value?.toUpperCase()) sw.classList.add("on");
413
493
  sw.addEventListener("click", () => pick(color));
414
- sw.addEventListener("keydown", (e) => onGridKey(e, r2, c));
415
494
  grid.append(sw);
416
495
  rowCells.push(sw);
417
496
  });
418
497
  cells.push(rowCells);
419
498
  });
420
499
  panel.append(grid);
421
- function onGridKey(e, r2, c) {
422
- const delta = {
423
- ArrowRight: [0, 1],
424
- ArrowLeft: [0, -1],
425
- ArrowDown: [1, 0],
426
- ArrowUp: [-1, 0]
427
- };
428
- const d = delta[e.key];
429
- if (!d) return;
430
- e.preventDefault();
431
- const rows = cells.length;
432
- const cols = cells[0].length;
433
- const nr = Math.min(rows - 1, Math.max(0, r2 + d[0]));
434
- const nc = Math.min(cols - 1, Math.max(0, c + d[1]));
435
- cells[r2][c].tabIndex = -1;
436
- const next = cells[nr][nc];
437
- next.tabIndex = 0;
438
- next.focus();
439
- }
500
+ const nav = rovingGrid(cells);
440
501
  panel.append(
441
502
  Object.assign(document.createElement("div"), {
442
503
  className: "bb-cpk-cap",
@@ -492,36 +553,38 @@ function openColorPicker({
492
553
  if (outside) close();
493
554
  });
494
555
  window.addEventListener("scroll", close, true);
495
- const start = cells.flat().find((el2) => el2.classList.contains("on")) ?? cells[0][0];
496
- start.tabIndex = 0;
497
- start.focus();
556
+ const onRow = cells.findIndex(
557
+ (row) => row.some((el5) => el5.classList.contains("on"))
558
+ );
559
+ const onCol = onRow >= 0 ? cells[onRow].findIndex((el5) => el5.classList.contains("on")) : 0;
560
+ nav.focusCell(Math.max(0, onRow), Math.max(0, onCol));
498
561
  return { close };
499
562
  }
500
563
  function colorButton(o) {
501
564
  injectStyle("bb-ui-colorpicker-styles", STYLE2);
502
- const el2 = document.createElement("button");
503
- el2.type = "button";
504
- el2.className = `bb-cpk-btn${o.glyph ? " glyphed" : ""}`;
505
- el2.title = o.title;
506
- el2.setAttribute("aria-label", o.title);
507
- el2.setAttribute("aria-haspopup", "dialog");
565
+ const el5 = document.createElement("button");
566
+ el5.type = "button";
567
+ el5.className = `bb-cpk-btn${o.glyph ? " glyphed" : ""}`;
568
+ el5.title = o.title;
569
+ el5.setAttribute("aria-label", o.title);
570
+ el5.setAttribute("aria-haspopup", "dialog");
508
571
  let chip;
509
572
  if (o.glyph) {
510
- const g = document.createElement("span");
511
- g.className = "bb-cpk-btn-glyph";
512
- g.innerHTML = o.glyph;
573
+ const g2 = document.createElement("span");
574
+ g2.className = "bb-cpk-btn-glyph";
575
+ g2.innerHTML = o.glyph;
513
576
  chip = document.createElement("span");
514
577
  chip.className = "bb-cpk-btn-bar";
515
- el2.append(g, chip);
578
+ el5.append(g2, chip);
516
579
  } else {
517
580
  chip = document.createElement("span");
518
581
  chip.className = "bb-cpk-btn-chip";
519
- el2.append(chip);
520
- if (o.label) el2.append(document.createTextNode(o.label));
582
+ el5.append(chip);
583
+ if (o.label) el5.append(document.createTextNode(o.label));
521
584
  const caret = document.createElement("span");
522
585
  caret.className = "bb-cpk-btn-caret";
523
586
  caret.textContent = "\u25BE";
524
- el2.append(caret);
587
+ el5.append(caret);
525
588
  }
526
589
  function sync() {
527
590
  const v = o.value();
@@ -529,10 +592,10 @@ function colorButton(o) {
529
592
  chip.classList.toggle("none", !v && !o.glyph);
530
593
  chip.textContent = !v && !o.glyph ? "\u29B8" : "";
531
594
  }
532
- el2.addEventListener("mousedown", (e) => e.preventDefault());
533
- el2.addEventListener("click", () => {
595
+ el5.addEventListener("mousedown", (e) => e.preventDefault());
596
+ el5.addEventListener("click", () => {
534
597
  openColorPicker({
535
- anchor: el2,
598
+ anchor: el5,
536
599
  value: o.value(),
537
600
  clearLabel: o.clearLabel,
538
601
  onPick: (c) => {
@@ -542,7 +605,7 @@ function colorButton(o) {
542
605
  });
543
606
  });
544
607
  sync();
545
- return { el: el2, sync };
608
+ return { el: el5, sync };
546
609
  }
547
610
 
548
611
  // packages/ui/src/lib/toolbar.ts
@@ -650,7 +713,7 @@ function defaultToolbarGroups(commands) {
650
713
  const names = [...commands].map((c) => c.name);
651
714
  const aligns = names.filter((n) => n.startsWith("align-"));
652
715
  const rest = names.filter((n) => !n.startsWith("align-"));
653
- return [rest, aligns].filter((g) => g.length > 0);
716
+ return [rest, aligns].filter((g2) => g2.length > 0);
654
717
  }
655
718
  function mountToolbar(host, editor, options = {}) {
656
719
  injectStyle("bb-ui-toolbar-styles", STYLE3);
@@ -695,6 +758,20 @@ function mountToolbar(host, editor, options = {}) {
695
758
  selects.push({ spec: entry, el: sel });
696
759
  continue;
697
760
  }
761
+ if (typeof entry !== "string" && entry.kind === "button") {
762
+ const btn2 = document.createElement("button");
763
+ btn2.type = "button";
764
+ btn2.className = "bb-toolbar-btn";
765
+ btn2.title = entry.title;
766
+ btn2.setAttribute("aria-label", entry.title);
767
+ btn2.setAttribute("aria-haspopup", "dialog");
768
+ if (entry.svg) btn2.innerHTML = entry.svg;
769
+ else btn2.textContent = entry.label ?? entry.title;
770
+ btn2.addEventListener("mousedown", (e) => e.preventDefault());
771
+ btn2.addEventListener("click", () => entry.onClick(btn2));
772
+ groupEl.appendChild(btn2);
773
+ continue;
774
+ }
698
775
  if (typeof entry !== "string" && entry.kind === "color") {
699
776
  const ctl = colorButton({
700
777
  title: entry.title,
@@ -805,6 +882,10 @@ function mountToolbar(host, editor, options = {}) {
805
882
  btn.type = "button";
806
883
  btn.className = "bb-toolbar-btn" + (item.className ? ` ${item.className}` : "");
807
884
  btn.title = item.title;
885
+ btn.addEventListener("mouseenter", () => {
886
+ const key = shortcutLabel(entry, [editor.keybindings]);
887
+ btn.title = key ? `${item.title} (${key})` : item.title;
888
+ });
808
889
  btn.setAttribute("aria-label", item.title);
809
890
  if (item.svg) btn.innerHTML = item.svg;
810
891
  else btn.textContent = item.label ?? entry;
@@ -855,7 +936,7 @@ function mountToolbar(host, editor, options = {}) {
855
936
  if (fits()) return;
856
937
  moreBtn.style.display = "";
857
938
  const groupEls = Array.from(root.children).filter(
858
- (el2) => el2.classList.contains("bb-toolbar-group")
939
+ (el5) => el5.classList.contains("bb-toolbar-group")
859
940
  );
860
941
  for (let i = groupEls.length - 1; i >= 0 && !fits(); i--) {
861
942
  pop.insertBefore(groupEls[i], pop.firstChild);
@@ -870,15 +951,15 @@ function mountToolbar(host, editor, options = {}) {
870
951
  layout();
871
952
  const refresh = (state) => {
872
953
  latest = state;
873
- for (const { name, el: el2 } of buttons) {
954
+ for (const { name, el: el5 } of buttons) {
874
955
  const cmd = editor.commands.get(name);
875
956
  if (!cmd) continue;
876
957
  const active = cmd.isActive?.(state) ?? false;
877
- el2.classList.toggle("is-active", active);
878
- el2.setAttribute("aria-pressed", String(active));
879
- el2.disabled = cmd.isEnabled ? !cmd.isEnabled(state) : false;
958
+ el5.classList.toggle("is-active", active);
959
+ el5.setAttribute("aria-pressed", String(active));
960
+ el5.disabled = cmd.isEnabled ? !cmd.isEnabled(state) : false;
880
961
  }
881
- for (const { spec, el: el2 } of selects) el2.value = spec.value(state);
962
+ for (const { spec, el: el5 } of selects) el5.value = spec.value(state);
882
963
  for (const c of colors) c.sync();
883
964
  for (const { spec, cards } of splits) {
884
965
  const selected = spec.value(state);
@@ -967,19 +1048,18 @@ function makeRow(label, opts) {
967
1048
  text.className = "bb-menu-label";
968
1049
  text.textContent = label;
969
1050
  item.append(check, text);
970
- if (opts.shortcut) {
971
- const sc = document.createElement("span");
972
- sc.className = "bb-menu-shortcut";
973
- sc.textContent = opts.shortcut;
974
- item.appendChild(sc);
975
- }
1051
+ const sc = document.createElement("span");
1052
+ sc.className = "bb-menu-shortcut";
1053
+ sc.textContent = opts.shortcut ?? "";
1054
+ if (!opts.shortcut) sc.hidden = true;
1055
+ item.appendChild(sc);
976
1056
  if (opts.hasSub) {
977
1057
  const arrow = document.createElement("span");
978
1058
  arrow.className = "bb-menu-arrow";
979
1059
  arrow.textContent = "\u203A";
980
1060
  item.appendChild(arrow);
981
1061
  }
982
- return { item, check };
1062
+ return { item, check, shortcutEl: sc };
983
1063
  }
984
1064
  function buildMenuEntries(entries, container, ctx) {
985
1065
  for (const entry of entries) {
@@ -1010,10 +1090,11 @@ function buildMenuEntries(entries, container, ctx) {
1010
1090
  const editor = ctx.editor;
1011
1091
  const cmd = editor?.commands.get(entry.command);
1012
1092
  if (!editor || !cmd) continue;
1013
- const { item, check } = makeRow(
1093
+ const { item, check, shortcutEl } = makeRow(
1014
1094
  entry.label ?? ctx.labels[entry.command] ?? entry.command,
1015
- {}
1095
+ { shortcut: ctx.shortcutOf?.(entry.command) }
1016
1096
  );
1097
+ ctx.shortcutRef?.(shortcutEl, entry.command);
1017
1098
  item.setAttribute("role", "menuitemcheckbox");
1018
1099
  item.addEventListener("click", () => {
1019
1100
  const s = ctx.state();
@@ -1026,9 +1107,10 @@ function buildMenuEntries(entries, container, ctx) {
1026
1107
  if (cmd.isEnabled) ctx.enable(item, (s) => cmd.isEnabled(s));
1027
1108
  container.appendChild(item);
1028
1109
  } else {
1029
- const { item, check } = makeRow(entry.label, {
1030
- shortcut: entry.shortcut
1110
+ const { item, check, shortcutEl } = makeRow(entry.label, {
1111
+ shortcut: (entry.shortcutOf && ctx.shortcutOf?.(entry.shortcutOf)) ?? entry.shortcut
1031
1112
  });
1113
+ if (entry.shortcutOf) ctx.shortcutRef?.(shortcutEl, entry.shortcutOf);
1032
1114
  item.setAttribute(
1033
1115
  "role",
1034
1116
  entry.isActive ? "menuitemcheckbox" : "menuitem"
@@ -1054,15 +1136,15 @@ function closeCurrentPopup() {
1054
1136
  function showPopup(content, at, onClose) {
1055
1137
  injectStyle("bb-ui-menubar-styles", STYLE4);
1056
1138
  closeCurrentPopup();
1057
- const el2 = document.createElement("div");
1058
- el2.className = "bb-menu bb-popup";
1059
- el2.setAttribute("role", "menu");
1060
- el2.appendChild(content);
1061
- document.body.appendChild(el2);
1139
+ const el5 = document.createElement("div");
1140
+ el5.className = "bb-menu bb-popup";
1141
+ el5.setAttribute("role", "menu");
1142
+ el5.appendChild(content);
1143
+ document.body.appendChild(el5);
1062
1144
  const place = () => {
1063
- const r = el2.getBoundingClientRect();
1064
- el2.style.left = `${Math.max(4, Math.min(at.x, window.innerWidth - r.width - 4))}px`;
1065
- el2.style.top = `${Math.max(4, Math.min(at.y, window.innerHeight - r.height - 4))}px`;
1145
+ const r = el5.getBoundingClientRect();
1146
+ el5.style.left = `${Math.max(4, Math.min(at.x, window.innerWidth - r.width - 4))}px`;
1147
+ el5.style.top = `${Math.max(4, Math.min(at.y, window.innerHeight - r.height - 4))}px`;
1066
1148
  };
1067
1149
  place();
1068
1150
  let disposed = false;
@@ -1072,7 +1154,7 @@ function showPopup(content, at, onClose) {
1072
1154
  document.removeEventListener("pointerdown", onPointer, true);
1073
1155
  document.removeEventListener("keydown", onKey, true);
1074
1156
  document.removeEventListener("scroll", onScroll, true);
1075
- el2.remove();
1157
+ el5.remove();
1076
1158
  onClose?.();
1077
1159
  };
1078
1160
  const close = () => {
@@ -1080,7 +1162,7 @@ function showPopup(content, at, onClose) {
1080
1162
  dispose();
1081
1163
  };
1082
1164
  const onPointer = (e) => {
1083
- if (!el2.contains(e.target)) close();
1165
+ if (!el5.contains(e.target)) close();
1084
1166
  };
1085
1167
  const onKey = (e) => {
1086
1168
  if (e.key === "Escape") {
@@ -1089,13 +1171,13 @@ function showPopup(content, at, onClose) {
1089
1171
  }
1090
1172
  };
1091
1173
  const onScroll = (e) => {
1092
- if (!el2.contains(e.target)) close();
1174
+ if (!el5.contains(e.target)) close();
1093
1175
  };
1094
1176
  document.addEventListener("pointerdown", onPointer, true);
1095
1177
  document.addEventListener("keydown", onKey, true);
1096
1178
  document.addEventListener("scroll", onScroll, true);
1097
1179
  currentPopup = dispose;
1098
- return { close, el: el2 };
1180
+ return { close, el: el5 };
1099
1181
  }
1100
1182
  function showMenu(entries, at, options = {}) {
1101
1183
  const labels = { ...DEFAULT_LABELS, ...options.labels ?? {} };
@@ -1119,12 +1201,13 @@ function showMenu(entries, at, options = {}) {
1119
1201
  close: () => handle?.close(),
1120
1202
  labels,
1121
1203
  editor: options.editor,
1204
+ shortcutOf: (command) => shortcutLabel(command, [options.editor?.keybindings]),
1122
1205
  state: stateOf,
1123
- check: (el2, active) => {
1124
- el2.textContent = evalNow(active, false) ? "\u2713" : "";
1206
+ check: (el5, active) => {
1207
+ el5.textContent = evalNow(active, false) ? "\u2713" : "";
1125
1208
  },
1126
- enable: (el2, enabled) => {
1127
- el2.disabled = !evalNow(enabled, true);
1209
+ enable: (el5, enabled) => {
1210
+ el5.disabled = !evalNow(enabled, true);
1128
1211
  }
1129
1212
  });
1130
1213
  handle = showPopup(container, at, options.onClose);
@@ -1168,6 +1251,7 @@ function mountMenubar(host, editor, options = {}) {
1168
1251
  const open = (idx, focusFirst = false) => {
1169
1252
  if (openIdx === idx) return;
1170
1253
  close();
1254
+ refreshShortcuts();
1171
1255
  const p = panels[idx];
1172
1256
  p.dropdown.hidden = false;
1173
1257
  p.title.setAttribute("aria-expanded", "true");
@@ -1176,13 +1260,24 @@ function mountMenubar(host, editor, options = {}) {
1176
1260
  if (focusFirst)
1177
1261
  p.dropdown.querySelector(".bb-menu-item")?.focus();
1178
1262
  };
1263
+ const registries = [editor.keybindings, ...options.keybindings ?? []];
1264
+ const shortcutRefs = [];
1265
+ const refreshShortcuts = () => {
1266
+ for (const { el: el5, command } of shortcutRefs) {
1267
+ const label = shortcutLabel(command, registries);
1268
+ el5.textContent = label ?? "";
1269
+ el5.hidden = !label;
1270
+ }
1271
+ };
1179
1272
  const buildCtx = {
1180
1273
  close: () => close(),
1181
1274
  labels,
1182
1275
  editor,
1276
+ shortcutOf: (command) => shortcutLabel(command, registries),
1277
+ shortcutRef: (el5, command) => shortcutRefs.push({ el: el5, command }),
1183
1278
  state: () => latest,
1184
- check: (el2, active) => checks.push({ el: el2, active }),
1185
- enable: (el2, enabled) => enables.push({ el: el2, enabled })
1279
+ check: (el5, active) => checks.push({ el: el5, active }),
1280
+ enable: (el5, enabled) => enables.push({ el: el5, enabled })
1186
1281
  };
1187
1282
  const buildEntries = (entries, container) => buildMenuEntries(entries, container, buildCtx);
1188
1283
  menus.forEach((menu, idx) => {
@@ -1394,15 +1489,15 @@ function closeCurrent() {
1394
1489
  function showContextMenu(entries, at) {
1395
1490
  injectStyle("bb-ui-contextmenu-styles", STYLE6);
1396
1491
  closeCurrent();
1397
- const el2 = document.createElement("div");
1398
- el2.className = "bb-ctx";
1399
- el2.setAttribute("role", "menu");
1492
+ const el5 = document.createElement("div");
1493
+ el5.className = "bb-ctx";
1494
+ el5.setAttribute("role", "menu");
1400
1495
  for (const entry of entries) {
1401
1496
  if (entry === "separator") {
1402
1497
  const sep = document.createElement("div");
1403
1498
  sep.className = "bb-ctx-sep";
1404
1499
  sep.setAttribute("role", "separator");
1405
- el2.appendChild(sep);
1500
+ el5.appendChild(sep);
1406
1501
  continue;
1407
1502
  }
1408
1503
  const item = document.createElement("button");
@@ -1425,22 +1520,22 @@ function showContextMenu(entries, at) {
1425
1520
  closeCurrent();
1426
1521
  entry.run();
1427
1522
  });
1428
- el2.appendChild(item);
1523
+ el5.appendChild(item);
1429
1524
  }
1430
- document.body.appendChild(el2);
1431
- const r = el2.getBoundingClientRect();
1525
+ document.body.appendChild(el5);
1526
+ const r = el5.getBoundingClientRect();
1432
1527
  const pad = 4;
1433
1528
  const fitsX = (v) => v >= pad && v + r.width <= window.innerWidth - pad;
1434
1529
  const fitsY = (v) => v >= pad && v + r.height <= window.innerHeight - pad;
1435
1530
  const x = fitsX(at.x) ? at.x : fitsX(at.x - r.width) ? at.x - r.width : Math.max(pad, Math.min(at.x, window.innerWidth - r.width - pad));
1436
1531
  const y = fitsY(at.y) ? at.y : fitsY(at.y - r.height) ? at.y - r.height : Math.max(pad, Math.min(at.y, window.innerHeight - r.height - pad));
1437
- el2.style.left = `${x}px`;
1438
- el2.style.top = `${y}px`;
1532
+ el5.style.left = `${x}px`;
1533
+ el5.style.top = `${y}px`;
1439
1534
  const items = () => Array.from(
1440
- el2.querySelectorAll(".bb-ctx-item:not(:disabled)")
1535
+ el5.querySelectorAll(".bb-ctx-item:not(:disabled)")
1441
1536
  );
1442
1537
  const onDown = (e) => {
1443
- if (!el2.contains(e.target)) closeCurrent();
1538
+ if (!el5.contains(e.target)) closeCurrent();
1444
1539
  };
1445
1540
  const onKey = (e) => {
1446
1541
  if (e.key === "Escape") return closeCurrent();
@@ -1462,7 +1557,7 @@ function showContextMenu(entries, at) {
1462
1557
  document.removeEventListener("pointerdown", onDown, true);
1463
1558
  document.removeEventListener("keydown", onKey, true);
1464
1559
  window.removeEventListener("scroll", onScroll, true);
1465
- el2.remove();
1560
+ el5.remove();
1466
1561
  };
1467
1562
  items()[0]?.focus();
1468
1563
  return { close: closeCurrent };
@@ -1523,10 +1618,10 @@ function showLinkPanel(opts) {
1523
1618
  return currentHandle;
1524
1619
  }
1525
1620
  closeCurrent2();
1526
- const el2 = document.createElement("div");
1527
- el2.className = "bb-linkpanel";
1528
- el2.setAttribute("role", "dialog");
1529
- el2.setAttribute("aria-label", "Link");
1621
+ const el5 = document.createElement("div");
1622
+ el5.className = "bb-linkpanel";
1623
+ el5.setAttribute("role", "dialog");
1624
+ el5.setAttribute("aria-label", "Link");
1530
1625
  const iconBtn = (svg, label, onClick) => {
1531
1626
  const b = document.createElement("button");
1532
1627
  b.type = "button";
@@ -1553,7 +1648,7 @@ function showLinkPanel(opts) {
1553
1648
  return { wrap, input };
1554
1649
  };
1555
1650
  const renderView = (existing) => {
1556
- el2.textContent = "";
1651
+ el5.textContent = "";
1557
1652
  const row = document.createElement("div");
1558
1653
  row.className = "bb-linkpanel-row";
1559
1654
  const internal = opts.internal;
@@ -1600,11 +1695,11 @@ function showLinkPanel(opts) {
1600
1695
  })
1601
1696
  );
1602
1697
  }
1603
- el2.appendChild(row);
1604
- placeFloating(el2, opts.anchor);
1698
+ el5.appendChild(row);
1699
+ placeFloating(el5, opts.anchor);
1605
1700
  };
1606
1701
  const renderForm = (prefill) => {
1607
- el2.textContent = "";
1702
+ el5.textContent = "";
1608
1703
  const form = document.createElement("div");
1609
1704
  form.className = "bb-linkpanel-form";
1610
1705
  const withText = !opts.hasSelection;
@@ -1645,16 +1740,16 @@ function showLinkPanel(opts) {
1645
1740
  }
1646
1741
  row.appendChild(apply);
1647
1742
  form.appendChild(row);
1648
- el2.appendChild(form);
1649
- placeFloating(el2, opts.anchor);
1743
+ el5.appendChild(form);
1744
+ placeFloating(el5, opts.anchor);
1650
1745
  u.input.focus();
1651
1746
  u.input.select();
1652
1747
  };
1653
- document.body.appendChild(el2);
1748
+ document.body.appendChild(el5);
1654
1749
  if (opts.existing) renderView(opts.existing);
1655
1750
  else renderForm(null);
1656
1751
  const onDown = (e) => {
1657
- if (el2.contains(e.target)) return;
1752
+ if (el5.contains(e.target)) return;
1658
1753
  if (opts.key) {
1659
1754
  cancelPendingClose();
1660
1755
  pendingClose = setTimeout(closeCurrent2, 80);
@@ -1675,7 +1770,7 @@ function showLinkPanel(opts) {
1675
1770
  document.removeEventListener("pointerdown", onDown, true);
1676
1771
  document.removeEventListener("keydown", onKey, true);
1677
1772
  window.removeEventListener("scroll", onScroll, true);
1678
- el2.remove();
1773
+ el5.remove();
1679
1774
  };
1680
1775
  current2 = dispose;
1681
1776
  currentKey = opts.key ?? null;
@@ -1878,8 +1973,8 @@ function openCellProperties({
1878
1973
  for (const [key, b] of vaBtns) b.classList.toggle("on", vAlign === key);
1879
1974
  }
1880
1975
  function syncPresets() {
1881
- for (const { el: el2, key } of presetEls)
1882
- el2.classList.toggle("on", preset === key);
1976
+ for (const { el: el5, key } of presetEls)
1977
+ el5.classList.toggle("on", preset === key);
1883
1978
  }
1884
1979
  syncVAlign();
1885
1980
  syncPresets();
@@ -1924,7 +2019,7 @@ function tableGridPicker(options) {
1924
2019
  label.className = "bb-grid-label";
1925
2020
  label.textContent = "0 \xD7 0";
1926
2021
  const cells = [];
1927
- const highlight = (r, c) => {
2022
+ const highlight2 = (r, c) => {
1928
2023
  for (let i = 0; i < cells.length; i++) {
1929
2024
  const row = Math.floor(i / maxCols);
1930
2025
  const col = i % maxCols;
@@ -1938,7 +2033,7 @@ function tableGridPicker(options) {
1938
2033
  cell.type = "button";
1939
2034
  cell.className = "bb-grid-cell";
1940
2035
  cell.addEventListener("mousedown", (e) => e.preventDefault());
1941
- cell.addEventListener("mouseenter", () => highlight(r, c));
2036
+ cell.addEventListener("mouseenter", () => highlight2(r, c));
1942
2037
  cell.addEventListener("click", () => options.onPick(r + 1, c + 1));
1943
2038
  cells.push(cell);
1944
2039
  cellsEl.appendChild(cell);
@@ -1987,9 +2082,9 @@ var STYLE10 = `
1987
2082
  `;
1988
2083
  var SVG_NS = "http://www.w3.org/2000/svg";
1989
2084
  function svgEl(name, attrs) {
1990
- const el2 = document.createElementNS(SVG_NS, name);
1991
- for (const [k, v] of Object.entries(attrs)) el2.setAttribute(k, String(v));
1992
- return el2;
2085
+ const el5 = document.createElementNS(SVG_NS, name);
2086
+ for (const [k, v] of Object.entries(attrs)) el5.setAttribute(k, String(v));
2087
+ return el5;
1993
2088
  }
1994
2089
  function pagePreview(ratio) {
1995
2090
  const svg = svgEl("svg", { viewBox: "0 0 34 42", class: "bb-ps-icon" });
@@ -2056,11 +2151,11 @@ function presetRow(icon, name, caption, active, onPick) {
2056
2151
  return row;
2057
2152
  }
2058
2153
  function captionLine(text) {
2059
- const el2 = document.createElement("span");
2060
- el2.className = "bb-ps-dims";
2061
- el2.style.display = "block";
2062
- el2.textContent = text;
2063
- return el2;
2154
+ const el5 = document.createElement("span");
2155
+ el5.className = "bb-ps-dims";
2156
+ el5.style.display = "block";
2157
+ el5.textContent = text;
2158
+ return el5;
2064
2159
  }
2065
2160
  function customRow(icon, title, description, onPick) {
2066
2161
  icon.appendChild(
@@ -2189,8 +2284,8 @@ function cmField(label, valuePx, min) {
2189
2284
  return { row, input };
2190
2285
  }
2191
2286
  function dialogActions() {
2192
- const el2 = document.createElement("div");
2193
- el2.className = "bb-pd-actions";
2287
+ const el5 = document.createElement("div");
2288
+ el5.className = "bb-pd-actions";
2194
2289
  const cancel = document.createElement("button");
2195
2290
  cancel.type = "button";
2196
2291
  cancel.className = "bb-prompt-btn";
@@ -2200,8 +2295,8 @@ function dialogActions() {
2200
2295
  ok.className = "bb-prompt-btn";
2201
2296
  ok.dataset["primary"] = "true";
2202
2297
  ok.textContent = "OK";
2203
- el2.append(cancel, ok);
2204
- return { el: el2, ok, cancel };
2298
+ el5.append(cancel, ok);
2299
+ return { el: el5, ok, cancel };
2205
2300
  }
2206
2301
  function readPx(input, fallbackPx) {
2207
2302
  const n = Number(input.value);
@@ -2398,15 +2493,15 @@ function sectionPaperPanel(options) {
2398
2493
  root.className = "bb-ps";
2399
2494
  root.setAttribute("role", "menu");
2400
2495
  const group = (text) => {
2401
- const el2 = document.createElement("div");
2402
- el2.className = "bb-ps-group";
2403
- el2.textContent = text;
2404
- return el2;
2496
+ const el5 = document.createElement("div");
2497
+ el5.className = "bb-ps-group";
2498
+ el5.textContent = text;
2499
+ return el5;
2405
2500
  };
2406
2501
  const sep = () => {
2407
- const el2 = document.createElement("div");
2408
- el2.className = "bb-ps-sep";
2409
- return el2;
2502
+ const el5 = document.createElement("div");
2503
+ el5.className = "bb-ps-sep";
2504
+ return el5;
2410
2505
  };
2411
2506
  root.appendChild(group(options.labels?.orientation ?? "Orientation"));
2412
2507
  const tiles = document.createElement("div");
@@ -2476,8 +2571,8 @@ var STYLE11 = `
2476
2571
  `;
2477
2572
  function createSectionChip(options) {
2478
2573
  injectStyle("bb-ui-section-chip-styles", STYLE11);
2479
- const el2 = document.createElement("div");
2480
- el2.className = "bb-secchip";
2574
+ const el5 = document.createElement("div");
2575
+ el5.className = "bb-secchip";
2481
2576
  const title = document.createElement("span");
2482
2577
  title.className = "bb-secchip-title";
2483
2578
  title.textContent = options.title;
@@ -2511,16 +2606,16 @@ function createSectionChip(options) {
2511
2606
  x.innerHTML = '<svg viewBox="0 0 16 16" width="9" height="9" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round"><path d="M3 3l10 10M13 3 3 13"/></svg>';
2512
2607
  x.addEventListener("mousedown", (e) => e.preventDefault());
2513
2608
  x.addEventListener("click", () => options.onDelete());
2514
- el2.append(title, divider(), nums.btn, divider(), paper.btn, divider(), x);
2609
+ el5.append(title, divider(), nums.btn, divider(), paper.btn, divider(), x);
2515
2610
  return {
2516
- el: el2,
2611
+ el: el5,
2517
2612
  update(data) {
2518
2613
  nums.label.textContent = data.pageNumbers;
2519
2614
  nums.btn.dataset["muted"] = String(!!data.pageNumbersMuted);
2520
2615
  paper.label.textContent = data.paper;
2521
2616
  },
2522
2617
  destroy() {
2523
- el2.remove();
2618
+ el5.remove();
2524
2619
  }
2525
2620
  };
2526
2621
  }
@@ -2672,7 +2767,7 @@ function openFontDialog({
2672
2767
  const italicBtn = mkSeg("I", initial.italic, "font-style:italic");
2673
2768
  const underBtn = mkSeg("U", initial.underline, "text-decoration:underline");
2674
2769
  let color = initial.color ?? null;
2675
- let highlight = initial.highlight ?? null;
2770
+ let highlight2 = initial.highlight ?? null;
2676
2771
  const colorBtn = colorButton({
2677
2772
  title: "Text color",
2678
2773
  label: "Text color",
@@ -2687,9 +2782,9 @@ function openFontDialog({
2687
2782
  title: "Highlight",
2688
2783
  label: "Highlight",
2689
2784
  clearLabel: "No highlight",
2690
- value: () => highlight,
2785
+ value: () => highlight2,
2691
2786
  onPick: (c) => {
2692
- highlight = c;
2787
+ highlight2 = c;
2693
2788
  paint();
2694
2789
  }
2695
2790
  });
@@ -2809,7 +2904,7 @@ function openFontDialog({
2809
2904
  s.textDecorationStyle = dstrikeCb.checked ? "double" : "solid";
2810
2905
  s.fontVariant = smallCapsCb.checked ? "small-caps" : "normal";
2811
2906
  s.color = color ?? "inherit";
2812
- s.background = highlight ?? "transparent";
2907
+ s.background = highlight2 ?? "transparent";
2813
2908
  s.letterSpacing = `${spacingPt}pt`;
2814
2909
  s.transform = scale === 100 ? "none" : `scaleX(${scale / 100})`;
2815
2910
  s.verticalAlign = supCb.checked ? "super" : subCb.checked ? "sub" : `${positionPt}pt`;
@@ -2838,7 +2933,7 @@ function openFontDialog({
2838
2933
  smallCaps: checkedOrMixed(smallCapsCb),
2839
2934
  vertAlign: supCb.checked ? "super" : subCb.checked ? "sub" : null,
2840
2935
  color,
2841
- highlight,
2936
+ highlight: highlight2,
2842
2937
  scalePercent: scale,
2843
2938
  letterSpacingTwips: ptToTwips(spacingPt),
2844
2939
  positionHalfPoints: ptToHalf(positionPt)
@@ -2853,29 +2948,1213 @@ function openFontDialog({
2853
2948
  paint();
2854
2949
  dialog.open();
2855
2950
  }
2951
+
2952
+ // packages/ui/src/lib/symbol-sets.ts
2953
+ var import_bapbong_contracts2 = require("@shadow-garden/bapbong-contracts");
2954
+ var g = (id, label, short, pairs) => ({
2955
+ id,
2956
+ label,
2957
+ short,
2958
+ entries: pairs.map(([char, name]) => ({ char, name }))
2959
+ });
2960
+ var SYMBOL_GROUPS = [
2961
+ g("checks", "Checkboxes & ticks", "Checks", [
2962
+ ["\u2610", "Ballot box"],
2963
+ ["\u2611", "Ballot box with check"],
2964
+ ["\u2612", "Ballot box with X"],
2965
+ ["\u2713", "Check mark"],
2966
+ ["\u2714", "Heavy check mark"],
2967
+ ["\u2717", "Ballot X"],
2968
+ ["\u2718", "Heavy ballot X"],
2969
+ ["\u2715", "Multiplication X"],
2970
+ ["\u2716", "Heavy multiplication X"],
2971
+ ["\u274F", "Lower right drop-shadowed white square"],
2972
+ ["\u2750", "Upper right drop-shadowed white square"],
2973
+ ["\u2751", "Lower right shadowed white square"],
2974
+ ["\u2752", "Upper right shadowed white square"],
2975
+ ["\u25A2", "White square with rounded corners"],
2976
+ ["\u25A3", "White square containing black small square"],
2977
+ ["\u22A0", "Squared times"],
2978
+ ["\u2297", "Circled times"],
2979
+ ["\u29BF", "Circled bullet"],
2980
+ ["\u25C9", "Fisheye"],
2981
+ ["\u25CB", "White circle"],
2982
+ ["\u25CF", "Black circle"],
2983
+ ["\u25EF", "Large circle"],
2984
+ ["\u25FB", "White medium square"],
2985
+ ["\u25FC", "Black medium square"]
2986
+ ]),
2987
+ g("bullets", "Bullets & shapes", "Bullets", [
2988
+ ["\u2022", "Bullet"],
2989
+ ["\u25E6", "White bullet"],
2990
+ ["\u25AA", "Black small square"],
2991
+ ["\u25AB", "White small square"],
2992
+ ["\u25A0", "Black square"],
2993
+ ["\u25A1", "White square"],
2994
+ ["\u25AC", "Black rectangle"],
2995
+ ["\u25C6", "Black diamond"],
2996
+ ["\u25C7", "White diamond"],
2997
+ ["\u25CA", "Lozenge"],
2998
+ ["\u2605", "Black star"],
2999
+ ["\u2606", "White star"],
3000
+ ["\u2726", "Black four pointed star"],
3001
+ ["\u2727", "White four pointed star"],
3002
+ ["\u25BA", "Black right-pointing pointer"],
3003
+ ["\u25B8", "Black right-pointing small triangle"],
3004
+ ["\u25B9", "White right-pointing small triangle"],
3005
+ ["\u25B2", "Black up-pointing triangle"],
3006
+ ["\u25B3", "White up-pointing triangle"],
3007
+ ["\u25BC", "Black down-pointing triangle"],
3008
+ ["\u25BD", "White down-pointing triangle"],
3009
+ ["\u25C4", "Black left-pointing pointer"],
3010
+ ["\u2756", "Black diamond minus white X"],
3011
+ ["\u273D", "Heavy teardrop-spoked asterisk"],
3012
+ ["\u273F", "Black florette"],
3013
+ ["\u2740", "White florette"],
3014
+ ["\u27A2", "Three-D top-lighted rightwards arrowhead"],
3015
+ ["\u27A4", "Black rightwards arrowhead"],
3016
+ ["\u27A3", "Three-D bottom-lighted rightwards arrowhead"],
3017
+ ["\u2713", "Check mark"]
3018
+ ]),
3019
+ g("arrows", "Arrows", "Arrows", [
3020
+ ["\u2190", "Leftwards arrow"],
3021
+ ["\u2191", "Upwards arrow"],
3022
+ ["\u2192", "Rightwards arrow"],
3023
+ ["\u2193", "Downwards arrow"],
3024
+ ["\u2194", "Left right arrow"],
3025
+ ["\u2195", "Up down arrow"],
3026
+ ["\u2196", "North west arrow"],
3027
+ ["\u2197", "North east arrow"],
3028
+ ["\u2198", "South east arrow"],
3029
+ ["\u2199", "South west arrow"],
3030
+ ["\u21D0", "Leftwards double arrow"],
3031
+ ["\u21D1", "Upwards double arrow"],
3032
+ ["\u21D2", "Rightwards double arrow"],
3033
+ ["\u21D3", "Downwards double arrow"],
3034
+ ["\u21D4", "Left right double arrow"],
3035
+ ["\u21D5", "Up down double arrow"],
3036
+ ["\u21A9", "Leftwards arrow with hook"],
3037
+ ["\u21AA", "Rightwards arrow with hook"],
3038
+ ["\u21B5", "Downwards arrow with corner leftwards"],
3039
+ ["\u21C4", "Rightwards arrow over leftwards arrow"],
3040
+ ["\u21C6", "Leftwards arrow over rightwards arrow"],
3041
+ ["\u2794", "Heavy wide-headed rightwards arrow"],
3042
+ ["\u279C", "Heavy round-tipped rightwards arrow"],
3043
+ ["\u279D", "Triangle-headed rightwards arrow"],
3044
+ ["\u279E", "Heavy triangle-headed rightwards arrow"],
3045
+ ["\u27A1", "Black rightwards arrow"],
3046
+ ["\u2B05", "Leftwards black arrow"],
3047
+ ["\u2B06", "Upwards black arrow"],
3048
+ ["\u2B07", "Downwards black arrow"],
3049
+ ["\u21BB", "Clockwise open circle arrow"],
3050
+ ["\u21BA", "Anticlockwise open circle arrow"],
3051
+ ["\u2934", "Arrow pointing rightwards then curving upwards"],
3052
+ ["\u2935", "Arrow pointing rightwards then curving downwards"]
3053
+ ]),
3054
+ g("math", "Math", "Math", [
3055
+ ["\xB1", "Plus-minus sign"],
3056
+ ["\xD7", "Multiplication sign"],
3057
+ ["\xF7", "Division sign"],
3058
+ ["\u2212", "Minus sign"],
3059
+ ["\u2264", "Less-than or equal to"],
3060
+ ["\u2265", "Greater-than or equal to"],
3061
+ ["\u2260", "Not equal to"],
3062
+ ["\u2248", "Almost equal to"],
3063
+ ["\u2261", "Identical to"],
3064
+ ["\u221E", "Infinity"],
3065
+ ["\u221A", "Square root"],
3066
+ ["\u2211", "N-ary summation"],
3067
+ ["\u220F", "N-ary product"],
3068
+ ["\u222B", "Integral"],
3069
+ ["\u2202", "Partial differential"],
3070
+ ["\u2206", "Increment"],
3071
+ ["\u2207", "Nabla"],
3072
+ ["\u2208", "Element of"],
3073
+ ["\u2209", "Not an element of"],
3074
+ ["\u2229", "Intersection"],
3075
+ ["\u222A", "Union"],
3076
+ ["\u2282", "Subset of"],
3077
+ ["\u2283", "Superset of"],
3078
+ ["\u2205", "Empty set"],
3079
+ ["\u2200", "For all"],
3080
+ ["\u2203", "There exists"],
3081
+ ["\xAC", "Not sign"],
3082
+ ["\u2227", "Logical and"],
3083
+ ["\u2228", "Logical or"],
3084
+ ["\xB0", "Degree sign"],
3085
+ ["\u2032", "Prime"],
3086
+ ["\u2033", "Double prime"],
3087
+ ["\u2030", "Per mille sign"],
3088
+ ["\xB5", "Micro sign"],
3089
+ ["\u03C0", "Greek small letter pi"],
3090
+ ["\u221D", "Proportional to"],
3091
+ ["\u2220", "Angle"],
3092
+ ["\u22A5", "Up tack"],
3093
+ ["\u2225", "Parallel to"],
3094
+ ["\xB9", "Superscript one"],
3095
+ ["\xB2", "Superscript two"],
3096
+ ["\xB3", "Superscript three"],
3097
+ ["\xBD", "Vulgar fraction one half"],
3098
+ ["\u2153", "Vulgar fraction one third"],
3099
+ ["\xBC", "Vulgar fraction one quarter"],
3100
+ ["\xBE", "Vulgar fraction three quarters"]
3101
+ ]),
3102
+ g("currency", "Currency", "Currency", [
3103
+ ["\u20AB", "Dong sign"],
3104
+ ["$", "Dollar sign"],
3105
+ ["\u20AC", "Euro sign"],
3106
+ ["\xA3", "Pound sign"],
3107
+ ["\xA5", "Yen sign"],
3108
+ ["\u20A9", "Won sign"],
3109
+ ["\u20B9", "Indian rupee sign"],
3110
+ ["\u20BD", "Ruble sign"],
3111
+ ["\u0E3F", "Thai currency symbol baht"],
3112
+ ["\u20B1", "Peso sign"],
3113
+ ["\xA2", "Cent sign"],
3114
+ ["\xA4", "Currency sign"],
3115
+ ["\u20BF", "Bitcoin sign"]
3116
+ ]),
3117
+ g("punct", "Punctuation & typography", "Punct.", [
3118
+ ["\u2013", "En dash"],
3119
+ ["\u2014", "Em dash"],
3120
+ ["\u2026", "Horizontal ellipsis"],
3121
+ ["\u2018", "Left single quotation mark"],
3122
+ ["\u2019", "Right single quotation mark"],
3123
+ ["\u201C", "Left double quotation mark"],
3124
+ ["\u201D", "Right double quotation mark"],
3125
+ ["\u201A", "Single low-9 quotation mark"],
3126
+ ["\u201E", "Double low-9 quotation mark"],
3127
+ ["\xAB", "Left-pointing double angle quotation mark"],
3128
+ ["\xBB", "Right-pointing double angle quotation mark"],
3129
+ ["\u2039", "Single left-pointing angle quotation mark"],
3130
+ ["\u203A", "Single right-pointing angle quotation mark"],
3131
+ ["\xA7", "Section sign"],
3132
+ ["\xB6", "Pilcrow sign"],
3133
+ ["\u2020", "Dagger"],
3134
+ ["\u2021", "Double dagger"],
3135
+ ["\u2022", "Bullet"],
3136
+ ["\xB7", "Middle dot"],
3137
+ ["\u2116", "Numero sign"],
3138
+ ["\xA9", "Copyright sign"],
3139
+ ["\xAE", "Registered sign"],
3140
+ ["\u2122", "Trade mark sign"],
3141
+ ["\u2120", "Service mark"],
3142
+ ["\xA1", "Inverted exclamation mark"],
3143
+ ["\xBF", "Inverted question mark"],
3144
+ ["\u203D", "Interrobang"],
3145
+ ["\u02C6", "Modifier letter circumflex accent"],
3146
+ ["\u02DC", "Small tilde"],
3147
+ ["\xA6", "Broken bar"]
3148
+ ]),
3149
+ g("greek", "Greek", "Greek", [
3150
+ ["\u03B1", "Alpha"],
3151
+ ["\u03B2", "Beta"],
3152
+ ["\u03B3", "Gamma"],
3153
+ ["\u03B4", "Delta"],
3154
+ ["\u03B5", "Epsilon"],
3155
+ ["\u03B6", "Zeta"],
3156
+ ["\u03B7", "Eta"],
3157
+ ["\u03B8", "Theta"],
3158
+ ["\u03B9", "Iota"],
3159
+ ["\u03BA", "Kappa"],
3160
+ ["\u03BB", "Lambda"],
3161
+ ["\u03BC", "Mu"],
3162
+ ["\u03BD", "Nu"],
3163
+ ["\u03BE", "Xi"],
3164
+ ["\u03BF", "Omicron"],
3165
+ ["\u03C0", "Pi"],
3166
+ ["\u03C1", "Rho"],
3167
+ ["\u03C3", "Sigma"],
3168
+ ["\u03C2", "Final sigma"],
3169
+ ["\u03C4", "Tau"],
3170
+ ["\u03C5", "Upsilon"],
3171
+ ["\u03C6", "Phi"],
3172
+ ["\u03C7", "Chi"],
3173
+ ["\u03C8", "Psi"],
3174
+ ["\u03C9", "Omega"],
3175
+ ["\u0391", "Capital alpha"],
3176
+ ["\u0392", "Capital beta"],
3177
+ ["\u0393", "Capital gamma"],
3178
+ ["\u0394", "Capital delta"],
3179
+ ["\u0395", "Capital epsilon"],
3180
+ ["\u0396", "Capital zeta"],
3181
+ ["\u0397", "Capital eta"],
3182
+ ["\u0398", "Capital theta"],
3183
+ ["\u0399", "Capital iota"],
3184
+ ["\u039A", "Capital kappa"],
3185
+ ["\u039B", "Capital lambda"],
3186
+ ["\u039C", "Capital mu"],
3187
+ ["\u039D", "Capital nu"],
3188
+ ["\u039E", "Capital xi"],
3189
+ ["\u039F", "Capital omicron"],
3190
+ ["\u03A0", "Capital pi"],
3191
+ ["\u03A1", "Capital rho"],
3192
+ ["\u03A3", "Capital sigma"],
3193
+ ["\u03A4", "Capital tau"],
3194
+ ["\u03A5", "Capital upsilon"],
3195
+ ["\u03A6", "Capital phi"],
3196
+ ["\u03A7", "Capital chi"],
3197
+ ["\u03A8", "Capital psi"],
3198
+ ["\u03A9", "Capital omega"]
3199
+ ]),
3200
+ g("latin", "Latin (Vietnamese & accents)", "Latin", [
3201
+ ["\u0110", "Capital D with stroke"],
3202
+ ["\u0111", "Small d with stroke"],
3203
+ ["\u0102", "Capital A with breve"],
3204
+ ["\u0103", "Small a with breve"],
3205
+ ["\xC2", "Capital A with circumflex"],
3206
+ ["\xE2", "Small a with circumflex"],
3207
+ ["\xCA", "Capital E with circumflex"],
3208
+ ["\xEA", "Small e with circumflex"],
3209
+ ["\xD4", "Capital O with circumflex"],
3210
+ ["\xF4", "Small o with circumflex"],
3211
+ ["\u01A0", "Capital O with horn"],
3212
+ ["\u01A1", "Small o with horn"],
3213
+ ["\u01AF", "Capital U with horn"],
3214
+ ["\u01B0", "Small u with horn"],
3215
+ ["\xC0", "Capital A with grave"],
3216
+ ["\xE0", "Small a with grave"],
3217
+ ["\xC1", "Capital A with acute"],
3218
+ ["\xE1", "Small a with acute"],
3219
+ ["\xC3", "Capital A with tilde"],
3220
+ ["\xE3", "Small a with tilde"],
3221
+ ["\u1EA2", "Capital A with hook above"],
3222
+ ["\u1EA3", "Small a with hook above"],
3223
+ ["\u1EA0", "Capital A with dot below"],
3224
+ ["\u1EA1", "Small a with dot below"],
3225
+ ["\xC8", "Capital E with grave"],
3226
+ ["\xE9", "Small e with acute"],
3227
+ ["\xCC", "Capital I with grave"],
3228
+ ["\xED", "Small i with acute"],
3229
+ ["\xD2", "Capital O with grave"],
3230
+ ["\xF3", "Small o with acute"],
3231
+ ["\xD9", "Capital U with grave"],
3232
+ ["\xFA", "Small u with acute"],
3233
+ ["\xDD", "Capital Y with acute"],
3234
+ ["\u1EF9", "Small y with tilde"],
3235
+ ["\xC7", "Capital C with cedilla"],
3236
+ ["\xE7", "Small c with cedilla"],
3237
+ ["\xD1", "Capital N with tilde"],
3238
+ ["\xF1", "Small n with tilde"],
3239
+ ["\xD6", "Capital O with diaeresis"],
3240
+ ["\xF6", "Small o with diaeresis"],
3241
+ ["\xDC", "Capital U with diaeresis"],
3242
+ ["\xFC", "Small u with diaeresis"],
3243
+ ["\xDF", "Small sharp s"],
3244
+ ["\xC6", "Capital AE"],
3245
+ ["\xE6", "Small ae"],
3246
+ ["\xD8", "Capital O with stroke"],
3247
+ ["\xF8", "Small o with stroke"],
3248
+ ["\u0152", "Capital ligature OE"],
3249
+ ["\u0153", "Small ligature oe"]
3250
+ ]),
3251
+ g("misc", "Misc", "Misc", [
3252
+ ["\u260E", "Black telephone"],
3253
+ ["\u2706", "Telephone location sign"],
3254
+ ["\u2709", "Envelope"],
3255
+ ["\u2702", "Black scissors"],
3256
+ ["\u270E", "Lower right pencil"],
3257
+ ["\u270F", "Pencil"],
3258
+ ["\u270D", "Writing hand"],
3259
+ ["\u263A", "White smiling face"],
3260
+ ["\u2639", "White frowning face"],
3261
+ ["\u2660", "Black spade suit"],
3262
+ ["\u2663", "Black club suit"],
3263
+ ["\u2665", "Black heart suit"],
3264
+ ["\u2666", "Black diamond suit"],
3265
+ ["\u266A", "Eighth note"],
3266
+ ["\u266B", "Beamed eighth notes"],
3267
+ ["\u2600", "Black sun with rays"],
3268
+ ["\u2601", "Cloud"],
3269
+ ["\u2602", "Umbrella"],
3270
+ ["\u2691", "Black flag"],
3271
+ ["\u2690", "White flag"],
3272
+ ["\u26A0", "Warning sign"],
3273
+ ["\u26D4", "No entry"],
3274
+ ["\u2640", "Female sign"],
3275
+ ["\u2642", "Male sign"],
3276
+ ["\u2318", "Place of interest sign"],
3277
+ ["\u2325", "Option key"],
3278
+ ["\u21E7", "Upwards white arrow"],
3279
+ ["\u23CE", "Return symbol"],
3280
+ ["\u232B", "Erase to the left"],
3281
+ ["\u23CF", "Eject symbol"],
3282
+ ["\u267B", "Black universal recycling symbol"],
3283
+ ["\u2708", "Airplane"],
3284
+ ["\u2696", "Scales"],
3285
+ ["\u2695", "Staff of aesculapius"]
3286
+ ])
3287
+ ];
3288
+ function autoCorrectHint(char) {
3289
+ return import_bapbong_contracts2.AUTOCORRECT_RULES.find((r) => r.to === char)?.seq ?? "";
3290
+ }
3291
+ var SPECIAL_CHARACTERS = [
3292
+ { char: "\u2014", name: "Em dash", hint: "" },
3293
+ { char: "\u2013", name: "En dash", hint: "" },
3294
+ { char: "\u2011", name: "Nonbreaking hyphen", hint: "" },
3295
+ { char: "\xAD", name: "Optional hyphen", hint: "" },
3296
+ { char: "\xA0", name: "Nonbreaking space", hint: "" },
3297
+ { char: "\u2003", name: "Em space", hint: "" },
3298
+ { char: "\u2002", name: "En space", hint: "" },
3299
+ { char: "\u2009", name: "Thin space", hint: "" },
3300
+ { char: "\xA9", name: "Copyright", hint: autoCorrectHint("\xA9") },
3301
+ { char: "\xAE", name: "Registered", hint: autoCorrectHint("\xAE") },
3302
+ { char: "\u2122", name: "Trademark", hint: autoCorrectHint("\u2122") },
3303
+ { char: "\xA7", name: "Section", hint: "" },
3304
+ { char: "\xB6", name: "Paragraph", hint: "" },
3305
+ { char: "\u2026", name: "Ellipsis", hint: autoCorrectHint("\u2026") },
3306
+ { char: "\u2018", name: "Single opening quote", hint: "" },
3307
+ { char: "\u2019", name: "Single closing quote", hint: "" },
3308
+ { char: "\u201C", name: "Double opening quote", hint: "" },
3309
+ { char: "\u201D", name: "Double closing quote", hint: "" },
3310
+ { char: "\u20AB", name: "Dong sign", hint: "" },
3311
+ { char: "\u2116", name: "Numero sign", hint: "" }
3312
+ ];
3313
+ var SYMBOL_NAMES = (() => {
3314
+ const m = /* @__PURE__ */ new Map();
3315
+ for (const grp of SYMBOL_GROUPS)
3316
+ for (const e of grp.entries) if (!m.has(e.char)) m.set(e.char, e.name);
3317
+ for (const e of SPECIAL_CHARACTERS) if (!m.has(e.char)) m.set(e.char, e.name);
3318
+ return m;
3319
+ })();
3320
+ function codePointLabel(char) {
3321
+ const cp = char.codePointAt(0) ?? 0;
3322
+ return `U+${cp.toString(16).toUpperCase().padStart(4, "0")}`;
3323
+ }
3324
+ function parseCodePoint(input) {
3325
+ const m = /^\s*(?:u\+|0x)?([0-9a-f]{1,6})\s*$/i.exec(input);
3326
+ if (!m) return null;
3327
+ const cp = parseInt(m[1], 16);
3328
+ if (cp > 1114111 || cp >= 55296 && cp <= 57343) return null;
3329
+ if (cp < 32 || cp >= 127 && cp < 160) return null;
3330
+ return String.fromCodePoint(cp);
3331
+ }
3332
+ var RECENT_MAX = 16;
3333
+ function pushRecent(recent, char, max = RECENT_MAX) {
3334
+ return [char, ...recent.filter((c) => c !== char)].slice(0, max);
3335
+ }
3336
+ function searchSymbols(query) {
3337
+ const q = query.trim().toLowerCase();
3338
+ if (!q) return [];
3339
+ const seen = /* @__PURE__ */ new Set();
3340
+ const ranked = [];
3341
+ for (const grp of SYMBOL_GROUPS) {
3342
+ const groupHit = grp.label.toLowerCase().includes(q);
3343
+ for (const e of grp.entries) {
3344
+ const name = e.name.toLowerCase();
3345
+ if (seen.has(e.char) || !name.includes(q)) continue;
3346
+ seen.add(e.char);
3347
+ ranked.push({ e, rank: groupHit ? 0 : name.startsWith(q) ? 1 : 2 });
3348
+ }
3349
+ }
3350
+ return ranked.sort((a, b) => a.rank - b.rank).map((r) => r.e);
3351
+ }
3352
+
3353
+ // packages/ui/src/lib/symbol-dialog.ts
3354
+ var STYLE13 = `
3355
+ .bb-sd{display:flex;flex-direction:column;gap:12px;width:412px;max-width:100%;color:var(--bb-ui-fg,#2c2c2a)}
3356
+ .bb-sd *{box-sizing:border-box}
3357
+ .bb-sd-tabs{display:inline-flex;border:0.5px solid var(--bb-ui-control-border,var(--bb-ui-border,#d8d6cf));border-radius:6px;overflow:hidden;align-self:flex-start}
3358
+ .bb-sd-tab{height:30px;padding:0 18px;border:0;border-right:0.5px solid var(--bb-ui-border,#e3e3e0);background:transparent;color:inherit;opacity:.65;font:inherit;font-size:13px;cursor:pointer}
3359
+ .bb-sd-tab:last-child{border-right:0}
3360
+ .bb-sd-tab[aria-selected="true"]{background:var(--bb-ui-active-bg,#e6f1fb);color:var(--bb-ui-active-fg,#0c447c);opacity:1}
3361
+ .bb-sd-pane{display:flex;flex-direction:column;gap:12px}
3362
+ .bb-sd-pane[hidden]{display:none}
3363
+ .bb-sd-lbl{font-size:12px;opacity:.7;margin-bottom:5px}
3364
+ .bb-sd-row{display:flex;gap:10px;align-items:flex-end}
3365
+ .bb-sd-ctl::placeholder{color:inherit;opacity:.45}
3366
+ .bb-sd-ctl{width:100%;height:32px;border:0.5px solid var(--bb-ui-control-border,var(--bb-ui-border,#d8d6cf));border-radius:6px;background:var(--bb-ui-control-bg,var(--bb-ui-bg,#fff));color:inherit;font:inherit;font-size:13px;padding:0 8px}
3367
+ .bb-sd-grid{display:grid;grid-template-columns:repeat(12,1fr);gap:2px;border:0.5px solid var(--bb-ui-control-border,var(--bb-ui-border,#d8d6cf));border-radius:8px;padding:6px;background:var(--bb-ui-control-bg,var(--bb-ui-bg,#fff));max-height:196px;overflow:auto}
3368
+ .bb-sd-grid.recent{grid-template-columns:repeat(16,1fr);max-height:none;border-color:transparent;padding:0;background:transparent;min-height:26px}
3369
+ .bb-sd-cell{aspect-ratio:1;display:grid;place-items:center;border:0;border-radius:5px;background:transparent;color:inherit;font-size:18px;line-height:1;cursor:pointer;padding:0;font-family:inherit,"Noto Sans Symbols 2"}
3370
+ .bb-sd-grid.recent .bb-sd-cell{border:0.5px solid var(--bb-ui-control-border,var(--bb-ui-border,#d8d6cf));background:var(--bb-ui-control-bg,var(--bb-ui-bg,#fff));font-size:15px}
3371
+ .bb-sd-cell:hover{background:var(--bb-ui-hover,#f1efe8)}
3372
+ .bb-sd-cell:focus{outline:none}
3373
+ .bb-sd-cell[aria-selected="true"]{background:var(--bb-ui-active-bg,#e6f1fb);box-shadow:inset 0 0 0 1.5px var(--bb-ui-active-border,#7fb2ec);color:var(--bb-ui-active-fg,#0c447c)}
3374
+ .bb-sd-empty{grid-column:1/-1;font-size:12px;opacity:.55;padding:6px 2px}
3375
+ .bb-sd-meta{display:grid;grid-template-columns:minmax(0,1fr) 140px;gap:10px;align-items:end}
3376
+ .bb-sd-info{min-width:0;border:0.5px solid var(--bb-ui-control-border,var(--bb-ui-border,#d8d6cf));border-radius:8px;padding:8px 10px;display:flex;gap:12px;align-items:center;min-height:56px}
3377
+ .bb-sd-big{font-size:30px;line-height:1;width:44px;text-align:center;font-family:inherit,"Noto Sans Symbols 2"}
3378
+ .bb-sd-name{font-size:13px}
3379
+ .bb-sd-cp{font-size:11px;opacity:.55;font-family:ui-monospace,Menlo,Consolas,monospace}
3380
+ .bb-sd-code{font-family:ui-monospace,Menlo,Consolas,monospace}
3381
+ .bb-sd-code[aria-invalid="true"]{border-color:#c0392b}
3382
+ .bb-sd-list{border:0.5px solid var(--bb-ui-control-border,var(--bb-ui-border,#d8d6cf));border-radius:8px;max-height:280px;overflow:auto}
3383
+ .bb-sd-list table{width:100%;border-collapse:collapse;font-size:13px}
3384
+ .bb-sd-list td{padding:6px 8px;border-bottom:0.5px solid var(--bb-ui-border,#e3e3e0)}
3385
+ .bb-sd-list tr:last-child td{border-bottom:0}
3386
+ .bb-sd-list tr{cursor:pointer}
3387
+ .bb-sd-list tr:hover td{background:var(--bb-ui-hover,#f1efe8)}
3388
+ .bb-sd-list tr[aria-selected="true"] td{background:var(--bb-ui-active-bg,#e6f1fb);color:var(--bb-ui-active-fg,#0c447c)}
3389
+ .bb-sd-list td.ch{width:36px;text-align:center;font-size:16px;font-family:inherit,"Noto Sans Symbols 2"}
3390
+ .bb-sd-list td.k{width:120px;font-size:11px;opacity:.6;font-family:ui-monospace,Menlo,Consolas,monospace;text-align:right}
3391
+ .bb-sd-foot{display:flex;justify-content:space-between;align-items:center;gap:8px}
3392
+ .bb-sd-hint{font-size:11px;opacity:.55}
3393
+ .bb-sd-btns{display:flex;gap:8px}
3394
+ .bb-sd-btn{height:31px;padding:0 17px;border:0.5px solid var(--bb-ui-control-border,var(--bb-ui-border,#d8d6cf));border-radius:6px;background:var(--bb-ui-control-bg,var(--bb-ui-bg,#fff));color:inherit;font:inherit;font-size:13px;cursor:pointer}
3395
+ .bb-sd-btn.primary{background:var(--bb-ui-active-bg,#e6f1fb);border-color:var(--bb-ui-active-border,#7fb2ec);color:var(--bb-ui-active-fg,#0c447c)}
3396
+ .bb-sd-btn:disabled{opacity:.5;cursor:default}
3397
+ `;
3398
+ var GRID_COLS = 12;
3399
+ var RECENT_COLS = 16;
3400
+ function el2(tag, cls, text) {
3401
+ const n = document.createElement(tag);
3402
+ if (cls) n.className = cls;
3403
+ if (text !== void 0) n.textContent = text;
3404
+ return n;
3405
+ }
3406
+ function glyphFor(char) {
3407
+ const cp = char.codePointAt(0) ?? 0;
3408
+ if (char === " " || char === "\xA0") return "\u2423";
3409
+ if (cp === 8194 || cp === 8195 || cp === 8201) return "\u2423";
3410
+ if (cp === 173) return "-";
3411
+ if (cp === 8209) return "\u2011";
3412
+ return char;
3413
+ }
3414
+ function createSymbolDialog(options) {
3415
+ injectStyle("bb-symbol-dialog", STYLE13);
3416
+ let recent = [...options.recent ?? []].slice(0, RECENT_MAX);
3417
+ let selected = null;
3418
+ const root = el2("div", "bb-sd");
3419
+ const tabs = el2("div", "bb-sd-tabs");
3420
+ tabs.setAttribute("role", "tablist");
3421
+ const symTab = el2("button", "bb-sd-tab", "Symbols");
3422
+ const spcTab = el2("button", "bb-sd-tab", "Special characters");
3423
+ for (const t of [symTab, spcTab]) {
3424
+ t.type = "button";
3425
+ t.setAttribute("role", "tab");
3426
+ tabs.append(t);
3427
+ }
3428
+ const symPane = el2("div", "bb-sd-pane");
3429
+ const spcPane = el2("div", "bb-sd-pane");
3430
+ const showTab = (special) => {
3431
+ symTab.setAttribute("aria-selected", String(!special));
3432
+ spcTab.setAttribute("aria-selected", String(special));
3433
+ symPane.hidden = special;
3434
+ spcPane.hidden = !special;
3435
+ };
3436
+ symTab.addEventListener("click", () => showTab(false));
3437
+ spcTab.addEventListener("click", () => showTab(true));
3438
+ const recentWrap = el2("div");
3439
+ recentWrap.append(el2("div", "bb-sd-lbl", "Recently used"));
3440
+ const recentGrid = el2("div", "bb-sd-grid recent");
3441
+ recentGrid.setAttribute("role", "grid");
3442
+ recentGrid.setAttribute("aria-label", "Recently used symbols");
3443
+ recentWrap.append(recentGrid);
3444
+ const controls = el2("div", "bb-sd-row");
3445
+ const groupCell = el2("div");
3446
+ groupCell.style.flex = "1";
3447
+ groupCell.append(el2("div", "bb-sd-lbl", "Group"));
3448
+ const groupSel = el2("select", "bb-sd-ctl");
3449
+ groupSel.setAttribute("aria-label", "Group");
3450
+ for (const grp of SYMBOL_GROUPS) {
3451
+ const o = el2("option", void 0, grp.label);
3452
+ o.value = grp.id;
3453
+ groupSel.append(o);
3454
+ }
3455
+ groupCell.append(groupSel);
3456
+ const searchCell = el2("div");
3457
+ searchCell.style.width = "160px";
3458
+ searchCell.append(el2("div", "bb-sd-lbl", "Search name"));
3459
+ const search = el2("input", "bb-sd-ctl");
3460
+ search.type = "search";
3461
+ search.placeholder = "e.g. ballot, arrow";
3462
+ search.setAttribute("aria-label", "Search symbols by name");
3463
+ searchCell.append(search);
3464
+ controls.append(groupCell, searchCell);
3465
+ const grid = el2("div", "bb-sd-grid");
3466
+ grid.setAttribute("role", "grid");
3467
+ grid.setAttribute("aria-label", "Symbols");
3468
+ const meta = el2("div", "bb-sd-meta");
3469
+ const info = el2("div", "bb-sd-info");
3470
+ const big = el2("div", "bb-sd-big", "");
3471
+ const infoText = el2("div");
3472
+ const nameEl = el2("div", "bb-sd-name", "Select a symbol");
3473
+ const cpEl = el2("div", "bb-sd-cp", "");
3474
+ infoText.append(nameEl, cpEl);
3475
+ info.append(big, infoText);
3476
+ const codeCell = el2("div");
3477
+ codeCell.append(el2("div", "bb-sd-lbl", "Character code (hex)"));
3478
+ const code = el2("input", "bb-sd-ctl bb-sd-code");
3479
+ code.placeholder = "e.g. 2611";
3480
+ code.setAttribute("aria-label", "Character code, hexadecimal");
3481
+ code.spellcheck = false;
3482
+ codeCell.append(code);
3483
+ meta.append(info, codeCell);
3484
+ symPane.append(recentWrap, controls, grid, meta);
3485
+ const list = el2("div", "bb-sd-list");
3486
+ const table = el2("table");
3487
+ const tbody = el2("tbody");
3488
+ const rows = [];
3489
+ for (const sc of SPECIAL_CHARACTERS) {
3490
+ const tr = el2("tr");
3491
+ tr.tabIndex = -1;
3492
+ tr.setAttribute("role", "option");
3493
+ const ch = el2("td", "ch", glyphFor(sc.char));
3494
+ const nm = el2("td", void 0, sc.name);
3495
+ const k = el2("td", "k", sc.hint);
3496
+ tr.append(ch, nm, k);
3497
+ tr.addEventListener("click", () => select2(sc.char, tr));
3498
+ tr.addEventListener("dblclick", () => insert(sc.char));
3499
+ tr.addEventListener("keydown", (e) => {
3500
+ const i = rows.indexOf(tr);
3501
+ if (e.key === "ArrowDown" && rows[i + 1]) {
3502
+ e.preventDefault();
3503
+ rows[i + 1].focus();
3504
+ select2(SPECIAL_CHARACTERS[i + 1].char, rows[i + 1]);
3505
+ } else if (e.key === "ArrowUp" && rows[i - 1]) {
3506
+ e.preventDefault();
3507
+ rows[i - 1].focus();
3508
+ select2(SPECIAL_CHARACTERS[i - 1].char, rows[i - 1]);
3509
+ } else if (e.key === "Enter") {
3510
+ e.preventDefault();
3511
+ insert(sc.char);
3512
+ }
3513
+ });
3514
+ tbody.append(tr);
3515
+ rows.push(tr);
3516
+ }
3517
+ table.append(tbody);
3518
+ list.append(table);
3519
+ list.setAttribute("role", "listbox");
3520
+ spcPane.append(list);
3521
+ const foot = el2("div", "bb-sd-foot");
3522
+ const hint = el2(
3523
+ "span",
3524
+ "bb-sd-hint",
3525
+ "Double-click a cell to insert \xB7 Enter inserts the selection"
3526
+ );
3527
+ const btns = el2("div", "bb-sd-btns");
3528
+ const closeBtn = el2("button", "bb-sd-btn", "Close");
3529
+ closeBtn.type = "button";
3530
+ const insertBtn = el2("button", "bb-sd-btn primary", "Insert");
3531
+ insertBtn.type = "button";
3532
+ insertBtn.disabled = true;
3533
+ btns.append(closeBtn, insertBtn);
3534
+ foot.append(hint, btns);
3535
+ root.append(tabs, symPane, spcPane, foot);
3536
+ const dialog = new Dialog({
3537
+ title: "Symbol",
3538
+ modal: false,
3539
+ anchor: options.anchor,
3540
+ className: "bb-symbol-dialog"
3541
+ });
3542
+ dialog.setContent(root);
3543
+ let selectedEl = null;
3544
+ function select2(char, cell) {
3545
+ selected = char;
3546
+ if (selectedEl) selectedEl.removeAttribute("aria-selected");
3547
+ selectedEl = cell ?? null;
3548
+ if (selectedEl) selectedEl.setAttribute("aria-selected", "true");
3549
+ big.textContent = glyphFor(char);
3550
+ nameEl.textContent = SYMBOL_NAMES.get(char) ?? "Character";
3551
+ cpEl.textContent = codePointLabel(char);
3552
+ if (document.activeElement !== code)
3553
+ code.value = codePointLabel(char).slice(2);
3554
+ code.removeAttribute("aria-invalid");
3555
+ insertBtn.disabled = false;
3556
+ }
3557
+ function insert(char) {
3558
+ const active = document.activeElement;
3559
+ options.onInsert(char);
3560
+ recent = pushRecent(recent, char);
3561
+ options.onRecentChange?.([...recent]);
3562
+ renderRecent();
3563
+ if (active instanceof HTMLElement && root.contains(active))
3564
+ queueMicrotask(() => {
3565
+ if (active.isConnected) active.focus();
3566
+ else search.focus();
3567
+ });
3568
+ }
3569
+ function cellFor(e) {
3570
+ const b = el2("button", "bb-sd-cell", glyphFor(e.char));
3571
+ b.type = "button";
3572
+ b.title = `${e.name} \xB7 ${codePointLabel(e.char)}`;
3573
+ b.setAttribute("aria-label", e.name);
3574
+ b.addEventListener("click", () => select2(e.char, b));
3575
+ b.addEventListener("dblclick", () => insert(e.char));
3576
+ b.addEventListener("keydown", (ev) => {
3577
+ if (ev.key === "Enter" || ev.key === " ") {
3578
+ ev.preventDefault();
3579
+ select2(e.char, b);
3580
+ insert(e.char);
3581
+ }
3582
+ });
3583
+ b.addEventListener("focus", () => select2(e.char, b));
3584
+ return b;
3585
+ }
3586
+ function fillGrid(target, entries, cols, empty) {
3587
+ target.replaceChildren();
3588
+ if (entries.length === 0) {
3589
+ target.append(el2("div", "bb-sd-empty", empty));
3590
+ return;
3591
+ }
3592
+ const cells = [];
3593
+ entries.forEach((e, i) => {
3594
+ const b = cellFor(e);
3595
+ target.append(b);
3596
+ if (i % cols === 0) cells.push([]);
3597
+ cells[cells.length - 1].push(b);
3598
+ });
3599
+ rovingGrid(cells);
3600
+ }
3601
+ function renderRecent() {
3602
+ fillGrid(
3603
+ recentGrid,
3604
+ recent.map((c) => ({
3605
+ char: c,
3606
+ name: SYMBOL_NAMES.get(c) ?? "Character"
3607
+ })),
3608
+ RECENT_COLS,
3609
+ "Nothing yet \u2014 inserted symbols appear here."
3610
+ );
3611
+ }
3612
+ function renderGroup() {
3613
+ const q = search.value.trim();
3614
+ const entries = q ? searchSymbols(q) : SYMBOL_GROUPS.find((g2) => g2.id === groupSel.value)?.entries ?? [];
3615
+ fillGrid(grid, entries, GRID_COLS, "No symbol matches that name.");
3616
+ const first = grid.querySelector(".bb-sd-cell");
3617
+ if (q && first && entries[0]) select2(entries[0].char, first);
3618
+ else clearSelection();
3619
+ }
3620
+ function clearSelection() {
3621
+ selected = null;
3622
+ selectedEl?.removeAttribute("aria-selected");
3623
+ selectedEl = null;
3624
+ big.textContent = "";
3625
+ nameEl.textContent = "Select a symbol";
3626
+ cpEl.textContent = "";
3627
+ if (document.activeElement !== code) code.value = "";
3628
+ insertBtn.disabled = true;
3629
+ }
3630
+ groupSel.addEventListener("change", () => {
3631
+ search.value = "";
3632
+ renderGroup();
3633
+ });
3634
+ search.addEventListener("input", renderGroup);
3635
+ search.addEventListener("keydown", (e) => {
3636
+ if (e.key === "Enter" && selected) {
3637
+ e.preventDefault();
3638
+ insert(selected);
3639
+ } else if (e.key === "ArrowDown") {
3640
+ e.preventDefault();
3641
+ grid.querySelector(".bb-sd-cell")?.focus();
3642
+ }
3643
+ });
3644
+ code.addEventListener("input", () => {
3645
+ const ch = parseCodePoint(code.value);
3646
+ if (ch) {
3647
+ code.removeAttribute("aria-invalid");
3648
+ const cell = Array.from(
3649
+ grid.querySelectorAll(".bb-sd-cell")
3650
+ ).find((c) => c.textContent === glyphFor(ch));
3651
+ select2(ch, cell ?? null);
3652
+ } else {
3653
+ code.setAttribute("aria-invalid", code.value.trim() ? "true" : "false");
3654
+ insertBtn.disabled = true;
3655
+ selected = null;
3656
+ }
3657
+ });
3658
+ code.addEventListener("keydown", (e) => {
3659
+ if (e.key === "Enter" && selected) {
3660
+ e.preventDefault();
3661
+ insert(selected);
3662
+ }
3663
+ });
3664
+ insertBtn.addEventListener("click", () => {
3665
+ if (selected) insert(selected);
3666
+ });
3667
+ closeBtn.addEventListener("click", () => dialog.close());
3668
+ showTab(false);
3669
+ renderRecent();
3670
+ renderGroup();
3671
+ return {
3672
+ open() {
3673
+ dialog.open();
3674
+ showTab(false);
3675
+ queueMicrotask(() => {
3676
+ search.focus();
3677
+ search.select();
3678
+ });
3679
+ },
3680
+ close() {
3681
+ dialog.close();
3682
+ },
3683
+ destroy() {
3684
+ dialog.destroy();
3685
+ },
3686
+ get isOpen() {
3687
+ return dialog.isOpen;
3688
+ }
3689
+ };
3690
+ }
3691
+
3692
+ // packages/ui/src/lib/symbol-popover.ts
3693
+ var STYLE14 = `
3694
+ .bb-spk{position:fixed;margin:0;max-width:none;max-height:none;z-index:1300;width:276px;padding:10px;background:var(--bb-ui-menu-bg,#fff);-webkit-backdrop-filter:var(--bb-ui-pop-filter,none);backdrop-filter:var(--bb-ui-pop-filter,none);color:var(--bb-ui-fg,#2c2c2a);border:1px solid var(--bb-ui-pop-border,var(--bb-ui-border,#e3e3e0));border-radius:10px;box-shadow:0 8px 28px rgba(0,0,0,.16);font-family:var(--bb-ui-font,system-ui,-apple-system,sans-serif);font-size:12px;box-sizing:border-box;display:flex;flex-direction:column;gap:8px}
3695
+ .bb-spk *{box-sizing:border-box}
3696
+ .bb-spk::backdrop{background:transparent}
3697
+ .bb-spk-search{width:100%;height:28px;border:0.5px solid var(--bb-ui-control-border,var(--bb-ui-border,#d8d6cf));border-radius:6px;background:var(--bb-ui-control-bg,var(--bb-ui-bg,#fff));color:inherit;font:inherit;font-size:12px;padding:0 8px}
3698
+ .bb-spk-search::placeholder{color:inherit;opacity:.45}
3699
+ .bb-spk-lbl{font-size:10.5px;opacity:.65;margin:0 0 4px}
3700
+ .bb-spk-recent{display:grid;grid-template-columns:repeat(8,1fr);gap:3px;min-height:26px}
3701
+ .bb-spk-recent .bb-spk-cell{border:0.5px solid var(--bb-ui-control-border,var(--bb-ui-border,#d8d6cf));background:var(--bb-ui-control-bg,var(--bb-ui-bg,#fff));font-size:14px}
3702
+ .bb-spk-empty{grid-column:1/-1;font-size:11px;opacity:.5;padding:4px 2px}
3703
+ .bb-spk-chips{display:flex;flex-wrap:wrap;gap:3px}
3704
+ .bb-spk-chip{padding:2px 6px;border-radius:10px;border:0.5px solid var(--bb-ui-control-border,var(--bb-ui-border,#d8d6cf));background:var(--bb-ui-control-bg,var(--bb-ui-bg,#fff));color:inherit;font:inherit;font-size:10px;opacity:.8;cursor:pointer}
3705
+ .bb-spk-chip:hover{background:var(--bb-ui-hover,#f1efe8)}
3706
+ .bb-spk-chip[aria-pressed="true"]{background:var(--bb-ui-active-bg,#e6f1fb);color:var(--bb-ui-active-fg,#0c447c);border-color:var(--bb-ui-active-border,#7fb2ec);opacity:1}
3707
+ .bb-spk-grid{display:grid;grid-template-columns:repeat(8,1fr);gap:2px;border:0.5px solid var(--bb-ui-control-border,var(--bb-ui-border,#d8d6cf));border-radius:7px;padding:4px;background:var(--bb-ui-control-bg,var(--bb-ui-bg,#fff));max-height:150px;overflow:auto}
3708
+ .bb-spk-cell{aspect-ratio:1;display:grid;place-items:center;border:0;border-radius:5px;background:transparent;color:inherit;font-size:16px;line-height:1;padding:0;cursor:pointer;font-family:inherit,"Noto Sans Symbols 2"}
3709
+ .bb-spk-cell:hover{background:var(--bb-ui-hover,#f1efe8)}
3710
+ .bb-spk-cell:focus{outline:none;box-shadow:inset 0 0 0 1.5px var(--bb-ui-active-border,#7fb2ec)}
3711
+ .bb-spk-cell[aria-selected="true"]{background:var(--bb-ui-active-bg,#e6f1fb);color:var(--bb-ui-active-fg,#0c447c)}
3712
+ .bb-spk-foot{display:flex;justify-content:space-between;align-items:center;gap:8px;font-size:11px}
3713
+ .bb-spk-name{opacity:.75;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
3714
+ .bb-spk-more{border:0;background:transparent;color:var(--bb-ui-active-fg,#0c447c);font:inherit;font-size:11px;cursor:pointer;padding:2px 4px;border-radius:4px;white-space:nowrap}
3715
+ .bb-spk-more:hover{background:var(--bb-ui-hover,#f1efe8)}
3716
+ `;
3717
+ var COLS = 8;
3718
+ var RECENT_ROW = COLS;
3719
+ function el3(tag, cls, text) {
3720
+ const n = document.createElement(tag);
3721
+ if (cls) n.className = cls;
3722
+ if (text !== void 0) n.textContent = text;
3723
+ return n;
3724
+ }
3725
+ function openSymbolPopover(options) {
3726
+ injectStyle("bb-ui-symbol-popover", STYLE14);
3727
+ const { anchor } = options;
3728
+ let recent = [...options.recent ?? []].slice(0, RECENT_MAX);
3729
+ let groupId = SYMBOL_GROUPS[0].id;
3730
+ let selected = null;
3731
+ const panel = document.createElement("dialog");
3732
+ panel.className = "bb-spk";
3733
+ panel.setAttribute("aria-label", "Insert symbol");
3734
+ let closed = false;
3735
+ const close = () => {
3736
+ if (closed) return;
3737
+ closed = true;
3738
+ window.removeEventListener("scroll", close, true);
3739
+ if (panel.open) panel.close();
3740
+ panel.remove();
3741
+ options.onClose?.();
3742
+ };
3743
+ const search = el3("input", "bb-spk-search");
3744
+ search.type = "search";
3745
+ search.placeholder = "Search name or code (U+2611)";
3746
+ search.setAttribute("aria-label", "Search symbols by name or code");
3747
+ search.spellcheck = false;
3748
+ const recentWrap = el3("div");
3749
+ recentWrap.append(el3("div", "bb-spk-lbl", "Recent"));
3750
+ const recentGrid = el3("div", "bb-spk-recent");
3751
+ recentGrid.setAttribute("role", "grid");
3752
+ recentGrid.setAttribute("aria-label", "Recently used symbols");
3753
+ recentWrap.append(recentGrid);
3754
+ const chips = el3("div", "bb-spk-chips");
3755
+ const chipEls = /* @__PURE__ */ new Map();
3756
+ for (const grp of SYMBOL_GROUPS) {
3757
+ const c = el3("button", "bb-spk-chip", grp.short);
3758
+ c.type = "button";
3759
+ c.title = grp.label;
3760
+ c.addEventListener("click", () => {
3761
+ groupId = grp.id;
3762
+ search.value = "";
3763
+ render();
3764
+ });
3765
+ chips.append(c);
3766
+ chipEls.set(grp.id, c);
3767
+ }
3768
+ const grid = el3("div", "bb-spk-grid");
3769
+ grid.setAttribute("role", "grid");
3770
+ grid.setAttribute("aria-label", "Symbols");
3771
+ const foot = el3("div", "bb-spk-foot");
3772
+ const nameEl = el3("span", "bb-spk-name", "");
3773
+ const more = el3("button", "bb-spk-more", "More symbols\u2026");
3774
+ more.type = "button";
3775
+ more.addEventListener("click", () => {
3776
+ close();
3777
+ options.onMore?.();
3778
+ });
3779
+ foot.append(nameEl, more);
3780
+ if (!options.onMore) more.hidden = true;
3781
+ panel.append(search, recentWrap, chips, grid, foot);
3782
+ let selectedEl = null;
3783
+ const select2 = (char, cell) => {
3784
+ selected = char;
3785
+ selectedEl?.removeAttribute("aria-selected");
3786
+ selectedEl = cell;
3787
+ selectedEl?.setAttribute("aria-selected", "true");
3788
+ nameEl.textContent = `${SYMBOL_NAMES.get(char) ?? "Character"} \xB7 ${codePointLabel(char)}`;
3789
+ };
3790
+ const insert = (char) => {
3791
+ const active = document.activeElement;
3792
+ options.onInsert(char);
3793
+ recent = pushRecent(recent, char);
3794
+ options.onRecentChange?.([...recent]);
3795
+ renderRecent();
3796
+ queueMicrotask(() => {
3797
+ if (active instanceof HTMLElement && active.isConnected) active.focus();
3798
+ else search.focus();
3799
+ });
3800
+ };
3801
+ const cellFor = (e) => {
3802
+ const b = el3("button", "bb-spk-cell", e.char);
3803
+ b.type = "button";
3804
+ b.title = `${e.name} \xB7 ${codePointLabel(e.char)}`;
3805
+ b.setAttribute("aria-label", e.name);
3806
+ b.addEventListener("focus", () => select2(e.char, b));
3807
+ b.addEventListener("click", () => {
3808
+ select2(e.char, b);
3809
+ insert(e.char);
3810
+ });
3811
+ b.addEventListener("keydown", (ev) => {
3812
+ if (ev.key === "Enter" || ev.key === " ") {
3813
+ ev.preventDefault();
3814
+ select2(e.char, b);
3815
+ insert(e.char);
3816
+ }
3817
+ });
3818
+ return b;
3819
+ };
3820
+ const fill = (target, entries, empty) => {
3821
+ target.replaceChildren();
3822
+ if (entries.length === 0) {
3823
+ target.append(el3("div", "bb-spk-empty", empty));
3824
+ return;
3825
+ }
3826
+ const cells = [];
3827
+ entries.forEach((e, i) => {
3828
+ const b = cellFor(e);
3829
+ target.append(b);
3830
+ if (i % COLS === 0) cells.push([]);
3831
+ cells[cells.length - 1].push(b);
3832
+ });
3833
+ rovingGrid(cells);
3834
+ };
3835
+ const renderRecent = () => fill(
3836
+ recentGrid,
3837
+ recent.slice(0, RECENT_ROW).map((c) => ({
3838
+ char: c,
3839
+ name: SYMBOL_NAMES.get(c) ?? "Character"
3840
+ })),
3841
+ "Nothing yet."
3842
+ );
3843
+ const render = () => {
3844
+ const q = search.value.trim();
3845
+ const byCode = q ? parseCodePoint(q) : null;
3846
+ const entries = q ? byCode && !searchSymbols(q).length ? [{ char: byCode, name: SYMBOL_NAMES.get(byCode) ?? "Character" }] : searchSymbols(q) : SYMBOL_GROUPS.find((g2) => g2.id === groupId)?.entries ?? [];
3847
+ for (const [id, c] of chipEls)
3848
+ c.setAttribute("aria-pressed", String(!q && id === groupId));
3849
+ fill(grid, entries, "No match.");
3850
+ const first = grid.querySelector(".bb-spk-cell");
3851
+ if (q && first && entries[0]) select2(entries[0].char, first);
3852
+ else {
3853
+ selected = null;
3854
+ selectedEl?.removeAttribute("aria-selected");
3855
+ selectedEl = null;
3856
+ nameEl.textContent = "";
3857
+ }
3858
+ };
3859
+ search.addEventListener("input", render);
3860
+ search.addEventListener("keydown", (e) => {
3861
+ if (e.key === "Enter" && selected) {
3862
+ e.preventDefault();
3863
+ insert(selected);
3864
+ } else if (e.key === "ArrowDown") {
3865
+ e.preventDefault();
3866
+ grid.querySelector(".bb-spk-cell")?.focus();
3867
+ }
3868
+ });
3869
+ document.body.append(panel);
3870
+ panel.showModal();
3871
+ const r = anchor.getBoundingClientRect();
3872
+ placeFloating(panel, { x: r.left, y: r.top, height: r.height });
3873
+ panel.addEventListener("close", close);
3874
+ panel.addEventListener("pointerdown", (e) => {
3875
+ const b = panel.getBoundingClientRect();
3876
+ const outside = e.clientX < b.left || e.clientX > b.right || e.clientY < b.top || e.clientY > b.bottom;
3877
+ if (outside) close();
3878
+ });
3879
+ window.addEventListener("scroll", close, true);
3880
+ renderRecent();
3881
+ render();
3882
+ search.focus();
3883
+ return { close };
3884
+ }
3885
+
3886
+ // packages/ui/src/lib/shortcuts-dialog.ts
3887
+ var import_bapbong_contracts3 = require("@shadow-garden/bapbong-contracts");
3888
+ var STYLE15 = `
3889
+ .bb-ks{display:flex;flex-direction:column;gap:10px;width:600px;max-width:100%;color:var(--bb-ui-fg,#2c2c2a);font-size:12px}
3890
+ .bb-ks *{box-sizing:border-box}
3891
+ .bb-ks-top{display:flex;gap:8px;align-items:center}
3892
+ .bb-ks-search{flex:1;height:30px;border:0.5px solid var(--bb-ui-control-border,var(--bb-ui-border,#d8d6cf));border-radius:6px;background:var(--bb-ui-control-bg,var(--bb-ui-bg,#fff));color:inherit;font:inherit;font-size:12px;padding:0 8px}
3893
+ .bb-ks-search::placeholder{color:inherit;opacity:.45}
3894
+ .bb-ks-count{font-size:11px;opacity:.6;white-space:nowrap}
3895
+ /* No box around the list: a bordered, tinted scroller turns the scrollbar's
3896
+ gutter into a blank column at its edge. Flat on the dialog surface, the
3897
+ rail reads like the app's own (idle-hidden overlay on a plain ground). */
3898
+ .bb-ks-wrap{overflow:auto;max-height:min(60vh,420px);margin:0 -12px;padding:0 12px;scrollbar-gutter:stable}
3899
+ .bb-ks table{width:100%;border-collapse:collapse}
3900
+ .bb-ks th{position:sticky;top:0;z-index:1;text-align:left;font-size:11px;font-weight:600;padding:7px 10px;border-bottom:0.5px solid var(--bb-ui-control-border,var(--bb-ui-border,#d8d6cf));cursor:pointer;user-select:none;white-space:nowrap;background-color:var(--bb-ui-bg,#fff);background-image:linear-gradient(var(--bb-ui-dialog-bg,transparent),var(--bb-ui-dialog-bg,transparent))}
3901
+ /* Opaque surface UNDER the dialog's own tint: a sticky header on the glass
3902
+ alone let the rows scrolling beneath show through it. The dimming goes on
3903
+ the text, not the cell, for the same reason. */
3904
+ .bb-ks th span{opacity:.75}
3905
+ .bb-ks th[aria-sort]{color:var(--bb-ui-active-fg,#0c447c)}
3906
+ .bb-ks th[aria-sort] span{opacity:1}
3907
+ .bb-ks td{padding:7px 10px;border-bottom:0.5px solid var(--bb-ui-border,#e3e3e0);vertical-align:middle}
3908
+ .bb-ks tr:last-child td{border-bottom:0}
3909
+ .bb-ks tbody tr:hover td{background:var(--bb-ui-hover,#f1efe8)}
3910
+ .bb-ks tbody tr:hover td:first-child{border-radius:6px 0 0 6px}
3911
+ .bb-ks tbody tr:hover td:last-child{border-radius:0 6px 6px 0}
3912
+ .bb-ks td.cmd{width:42%}
3913
+ .bb-ks td.cmd small{display:block;font-size:10px;opacity:.5;font-family:ui-monospace,Menlo,Consolas,monospace}
3914
+ .bb-ks kbd{display:inline-block;min-width:20px;padding:1px 6px;margin-right:3px;border:0.5px solid var(--bb-ui-control-border,var(--bb-ui-border,#d8d6cf));border-bottom-width:1.5px;border-radius:5px;background:var(--bb-ui-bg,#fff);font:inherit;font-size:11px;text-align:center}
3915
+ .bb-ks .or{opacity:.5;margin:0 4px 0 1px;font-size:11px}
3916
+ .bb-ks td.when{font-size:11px;opacity:.7;width:24%}
3917
+ .bb-ks td.src{width:12%}
3918
+ .bb-ks-chip{display:inline-block;padding:1px 7px;border-radius:9px;font-size:10px;border:0.5px solid var(--bb-ui-control-border,var(--bb-ui-border,#d8d6cf));background:var(--bb-ui-control-bg,var(--bb-ui-bg,#fff));opacity:.85;white-space:nowrap}
3919
+ .bb-ks-chip.core{background:var(--bb-ui-active-bg,#e6f1fb);border-color:var(--bb-ui-active-border,#7fb2ec);color:var(--bb-ui-active-fg,#0c447c);opacity:1}
3920
+ .bb-ks-empty{padding:16px;text-align:center;font-size:12px;opacity:.55}
3921
+ .bb-ks-foot{display:flex;justify-content:space-between;font-size:11px;opacity:.6}
3922
+ .bb-ks mark{background:transparent;color:inherit;font-weight:600;text-decoration:underline;text-decoration-color:var(--bb-ui-active-border,#7fb2ec);text-underline-offset:2px}
3923
+ `;
3924
+ function el4(tag, cls, text) {
3925
+ const n = document.createElement(tag);
3926
+ if (cls) n.className = cls;
3927
+ if (text !== void 0) n.textContent = text;
3928
+ return n;
3929
+ }
3930
+ function highlight(text, q) {
3931
+ if (!q) return [text];
3932
+ const out = [];
3933
+ const lower = text.toLowerCase();
3934
+ let i = 0;
3935
+ for (; ; ) {
3936
+ const j = lower.indexOf(q, i);
3937
+ if (j < 0) break;
3938
+ if (j > i) out.push(text.slice(i, j));
3939
+ out.push(el4("mark", void 0, text.slice(j, j + q.length)));
3940
+ i = j + q.length;
3941
+ }
3942
+ if (i < text.length) out.push(text.slice(i));
3943
+ return out;
3944
+ }
3945
+ function openKeyboardShortcutsDialog(options) {
3946
+ injectStyle("bb-ui-shortcuts-dialog", STYLE15);
3947
+ const dialog = new Dialog({
3948
+ title: options.title ?? "Keyboard shortcuts",
3949
+ modal: true,
3950
+ wide: true
3951
+ });
3952
+ const root = el4("div", "bb-ks");
3953
+ const top = el4("div", "bb-ks-top");
3954
+ const search = el4("input", "bb-ks-search");
3955
+ search.type = "search";
3956
+ search.placeholder = "Search command, key, when or source\u2026";
3957
+ search.setAttribute("aria-label", "Search shortcuts");
3958
+ const count = el4("span", "bb-ks-count", "");
3959
+ top.append(search, count);
3960
+ const wrap = el4("div", "bb-ks-wrap");
3961
+ const table = el4("table");
3962
+ const thead = el4("thead");
3963
+ const headRow = el4("tr");
3964
+ const columns = [
3965
+ { key: "title", label: "Command" },
3966
+ { key: "keys", label: "Keybinding" },
3967
+ { key: "when", label: "When" },
3968
+ { key: "source", label: "Source" }
3969
+ ];
3970
+ let sortKey = "title";
3971
+ let sortAsc = true;
3972
+ const ths = /* @__PURE__ */ new Map();
3973
+ for (const c of columns) {
3974
+ const th = el4("th");
3975
+ th.append(el4("span", void 0, c.label));
3976
+ th.addEventListener("click", () => {
3977
+ if (sortKey === c.key) sortAsc = !sortAsc;
3978
+ else {
3979
+ sortKey = c.key;
3980
+ sortAsc = true;
3981
+ }
3982
+ render();
3983
+ });
3984
+ headRow.append(th);
3985
+ ths.set(c.key, th);
3986
+ }
3987
+ thead.append(headRow);
3988
+ const tbody = el4("tbody");
3989
+ table.append(thead, tbody);
3990
+ wrap.append(table);
3991
+ const foot = el4("div", "bb-ks-foot");
3992
+ const footL = el4(
3993
+ "span",
3994
+ void 0,
3995
+ "Sorted by command \xB7 click a header to sort"
3996
+ );
3997
+ const footR = el4("span", void 0, "Esc closes");
3998
+ foot.append(footL, footR);
3999
+ root.append(top, wrap, foot);
4000
+ dialog.setContent(root);
4001
+ const allRows = () => {
4002
+ const rows = [];
4003
+ for (const src of options.sources)
4004
+ rows.push(
4005
+ ...(0, import_bapbong_contracts3.keybindingRows)(src.keybindings, (c) => src.commands.get(c)?.title)
4006
+ );
4007
+ return rows;
4008
+ };
4009
+ const kbdCell = (keys) => {
4010
+ const td = el4("td");
4011
+ keys.forEach((k, i) => {
4012
+ if (i > 0) td.append(el4("span", "or", "or"));
4013
+ for (const part of (0, import_bapbong_contracts3.formatKey)(k, IS_MAC))
4014
+ td.append(el4("kbd", void 0, part));
4015
+ });
4016
+ return td;
4017
+ };
4018
+ const sortRows = (rows) => {
4019
+ const val = (r) => sortKey === "title" ? r.title : sortKey === "keys" ? r.keys.map((k) => (0, import_bapbong_contracts3.formatKey)(k, IS_MAC).join("")).join(" ") : sortKey === "when" ? r.when ?? "" : r.source;
4020
+ const sorted = [...rows].sort(
4021
+ (a, b) => val(a).localeCompare(val(b), void 0, { sensitivity: "base" })
4022
+ );
4023
+ return sortAsc ? sorted : sorted.reverse();
4024
+ };
4025
+ const render = () => {
4026
+ const q = search.value.trim().toLowerCase();
4027
+ const rows = allRows();
4028
+ const shown = sortRows((0, import_bapbong_contracts3.filterKeybindingRows)(rows, q, IS_MAC));
4029
+ for (const [key, th] of ths) {
4030
+ if (key === sortKey)
4031
+ th.setAttribute("aria-sort", sortAsc ? "ascending" : "descending");
4032
+ else th.removeAttribute("aria-sort");
4033
+ }
4034
+ tbody.replaceChildren();
4035
+ if (shown.length === 0) {
4036
+ const tr = el4("tr");
4037
+ const td = el4("td", "bb-ks-empty", "No shortcut matches.");
4038
+ td.colSpan = 4;
4039
+ tr.append(td);
4040
+ tbody.append(tr);
4041
+ }
4042
+ for (const r of shown) {
4043
+ const tr = el4("tr");
4044
+ const cmd = el4("td", "cmd");
4045
+ cmd.append(...highlight(r.title, q));
4046
+ const small = el4("small");
4047
+ small.append(...highlight(r.command, q));
4048
+ cmd.append(small);
4049
+ const when = el4("td", "when");
4050
+ when.append(...highlight(r.when ?? "", q));
4051
+ const src = el4("td", "src");
4052
+ const chip = el4(
4053
+ "span",
4054
+ "bb-ks-chip" + (r.source === "core" ? " core" : "")
4055
+ );
4056
+ chip.append(...highlight(r.source, q));
4057
+ src.append(chip);
4058
+ tr.append(cmd, kbdCell(r.keys), when, src);
4059
+ tbody.append(tr);
4060
+ }
4061
+ count.textContent = shown.length === rows.length ? `${rows.length} shortcuts` : `${shown.length} of ${rows.length}`;
4062
+ footL.textContent = `Sorted by ${columns.find((c) => c.key === sortKey)?.label.toLowerCase()} \xB7 click a header to sort`;
4063
+ };
4064
+ search.addEventListener("input", render);
4065
+ dialog.onClose(() => dialog.destroy());
4066
+ render();
4067
+ dialog.open();
4068
+ search.focus();
4069
+ }
4070
+
4071
+ // packages/ui/src/lib/scrollbars.ts
4072
+ var STYLE16 = `
4073
+ @property --bb-scroll-thumb-now {
4074
+ syntax: '<color>';
4075
+ /* MUST inherit: the thumb pseudo-element never matches .bb-scrolling
4076
+ itself \u2014 it only ever INHERITS the value from its owner. */
4077
+ inherits: true;
4078
+ initial-value: transparent;
4079
+ }
4080
+ * { transition: --bb-scroll-thumb-now 0.35s ease; }
4081
+ .bb-scrolling { --bb-scroll-thumb-now: var(--bb-ui-scroll-thumb, rgba(64,52,28,.28)); }
4082
+ ::-webkit-scrollbar { width: 13px; height: 13px; background: transparent; }
4083
+ ::-webkit-scrollbar-track { background: transparent; }
4084
+ ::-webkit-scrollbar-corner { background: transparent; }
4085
+ ::-webkit-scrollbar-thumb {
4086
+ background-color: var(--bb-scroll-thumb-now);
4087
+ background-clip: content-box;
4088
+ border: 3px solid transparent;
4089
+ border-radius: 99px;
4090
+ min-height: 40px; /* a 277-page doc must not shrink the thumb to a dot */
4091
+ }
4092
+ ::-webkit-scrollbar-thumb:hover,
4093
+ ::-webkit-scrollbar-thumb:active {
4094
+ background-color: var(--bb-ui-scroll-thumb-hover, rgba(64,52,28,.45));
4095
+ }
4096
+ @supports not selector(::-webkit-scrollbar) {
4097
+ /* non-WebKit (Firefox): native thin overlay, same colours */
4098
+ * { scrollbar-width: thin; scrollbar-color: var(--bb-ui-scroll-thumb, rgba(64,52,28,.28)) transparent; }
4099
+ }
4100
+ `;
4101
+ var installed = null;
4102
+ function installScrollbars(root = document) {
4103
+ if (installed) return installed;
4104
+ injectStyle("bb-ui-scrollbars", STYLE16);
4105
+ const quiet = /* @__PURE__ */ new WeakMap();
4106
+ const onScroll = (ev) => {
4107
+ const el5 = ev.target instanceof HTMLElement ? ev.target : root.documentElement;
4108
+ el5.classList.add("bb-scrolling");
4109
+ clearTimeout(quiet.get(el5));
4110
+ quiet.set(
4111
+ el5,
4112
+ window.setTimeout(() => el5.classList.remove("bb-scrolling"), 1e3)
4113
+ );
4114
+ };
4115
+ root.addEventListener("scroll", onScroll, { capture: true, passive: true });
4116
+ installed = () => {
4117
+ root.removeEventListener("scroll", onScroll, { capture: true });
4118
+ installed = null;
4119
+ };
4120
+ return installed;
4121
+ }
2856
4122
  // Annotate the CommonJS export names for ESM import in node:
2857
4123
  0 && (module.exports = {
2858
4124
  COLOR_PALETTE,
2859
4125
  Dialog,
4126
+ RECENT_MAX,
4127
+ SPECIAL_CHARACTERS,
4128
+ SYMBOL_GROUPS,
4129
+ SYMBOL_NAMES,
2860
4130
  cmToPx,
4131
+ codePointLabel,
2861
4132
  colorButton,
2862
4133
  createFindDialog,
2863
4134
  createSectionChip,
4135
+ createSymbolDialog,
2864
4136
  defaultMenus,
2865
4137
  defaultToolbarGroups,
4138
+ installScrollbars,
2866
4139
  marginPresetPicker,
2867
4140
  mountMenubar,
2868
4141
  mountToolbar,
2869
4142
  openCellProperties,
2870
4143
  openColorPicker,
2871
4144
  openFontDialog,
4145
+ openKeyboardShortcutsDialog,
2872
4146
  openMarginsDialog,
2873
4147
  openPageSizeDialog,
4148
+ openSymbolPopover,
2874
4149
  orientationPicker,
2875
4150
  pageNumberPicker,
2876
4151
  pageSizePicker,
4152
+ panelAnchor,
4153
+ parseCodePoint,
2877
4154
  promptDialog,
4155
+ pushRecent,
2878
4156
  pxToCm,
4157
+ searchSymbols,
2879
4158
  sectionPaperPanel,
2880
4159
  showContextMenu,
2881
4160
  showLinkPanel,