@shadow-garden/bapbong-ui 0.7.0 → 0.8.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
@@ -20,8 +20,10 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
20
20
  // packages/ui/src/index.ts
21
21
  var index_exports = {};
22
22
  __export(index_exports, {
23
+ COLOR_PALETTE: () => COLOR_PALETTE,
23
24
  Dialog: () => Dialog,
24
25
  cmToPx: () => cmToPx,
26
+ colorButton: () => colorButton,
25
27
  createFindDialog: () => createFindDialog,
26
28
  defaultMenus: () => defaultMenus,
27
29
  defaultToolbarGroups: () => defaultToolbarGroups,
@@ -29,6 +31,8 @@ __export(index_exports, {
29
31
  mountMenubar: () => mountMenubar,
30
32
  mountToolbar: () => mountToolbar,
31
33
  openCellProperties: () => openCellProperties,
34
+ openColorPicker: () => openColorPicker,
35
+ openFontDialog: () => openFontDialog,
32
36
  openMarginsDialog: () => openMarginsDialog,
33
37
  openPageSizeDialog: () => openPageSizeDialog,
34
38
  orientationPicker: () => orientationPicker,
@@ -44,10 +48,24 @@ module.exports = __toCommonJS(index_exports);
44
48
  // packages/ui/src/lib/internal.ts
45
49
  function injectStyle(id, css) {
46
50
  if (document.getElementById(id)) return;
47
- const el = document.createElement("style");
48
- el.id = id;
49
- el.textContent = css;
50
- document.head.appendChild(el);
51
+ const el2 = document.createElement("style");
52
+ el2.id = id;
53
+ el2.textContent = css;
54
+ document.head.appendChild(el2);
55
+ }
56
+ function placeFloating(el2, anchor) {
57
+ const r = el2.getBoundingClientRect();
58
+ const pad = 6;
59
+ const gap = 6;
60
+ const x = Math.max(
61
+ pad,
62
+ Math.min(anchor.x, window.innerWidth - r.width - pad)
63
+ );
64
+ const below = anchor.y + anchor.height + gap;
65
+ const above = anchor.y - gap - r.height;
66
+ const y = below + r.height <= window.innerHeight - pad || above < pad ? Math.min(below, window.innerHeight - r.height - pad) : above;
67
+ el2.style.left = `${x}px`;
68
+ el2.style.top = `${y}px`;
51
69
  }
52
70
 
53
71
  // packages/ui/src/lib/dialog.ts
@@ -63,9 +81,9 @@ var STYLE = `
63
81
  .bb-dialog-close:hover{opacity:1;background:var(--bb-ui-hover,#f1efe8)}
64
82
  .bb-dialog-body{padding:12px}
65
83
  .bb-prompt{display:flex;flex-direction:column;gap:10px;min-width:280px}
66
- .bb-prompt-input{height:30px;padding:0 9px;border:1px solid var(--bb-ui-border,#d8d6cf);border-radius:6px;font:inherit;font-size:13px}
84
+ .bb-prompt-input{height:30px;padding:0 9px;border:1px solid var(--bb-ui-control-border,var(--bb-ui-border,#d8d6cf));border-radius:6px;font:inherit;font-size:13px}
67
85
  .bb-prompt-actions{display:flex;justify-content:flex-end;gap:8px}
68
- .bb-prompt-btn{height:30px;padding:0 14px;border:1px solid var(--bb-ui-border,#d8d6cf);border-radius:6px;background:var(--bb-ui-bg,#fff);color:inherit;font:inherit;font-size:13px;cursor:pointer}
86
+ .bb-prompt-btn{height:30px;padding:0 14px;border:1px 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}
69
87
  .bb-prompt-btn[data-primary]{background:var(--bb-ui-active-bg,#e6f1fb);border-color:var(--bb-ui-active-border,#b5d4f4);color:var(--bb-ui-active-fg,#0c447c)}
70
88
  .bb-prompt-btn:hover{filter:brightness(.97)}
71
89
  `;
@@ -89,8 +107,8 @@ var Dialog = class {
89
107
  injectStyle("bb-ui-dialog-styles", STYLE);
90
108
  this.modal = options.modal ?? false;
91
109
  this.anchor = options.anchor;
92
- const el = document.createElement("dialog");
93
- el.className = `bb-dialog ${this.modal ? "bb-dialog-modal" : "bb-dialog-float"}` + (options.className ? ` ${options.className}` : "");
110
+ const el2 = document.createElement("dialog");
111
+ el2.className = `bb-dialog ${this.modal ? "bb-dialog-modal" : "bb-dialog-float"}` + (options.className ? ` ${options.className}` : "");
94
112
  const header = document.createElement("div");
95
113
  header.className = "bb-dialog-header";
96
114
  this.title = document.createElement("span");
@@ -105,18 +123,18 @@ var Dialog = class {
105
123
  header.append(this.title, close);
106
124
  this.body = document.createElement("div");
107
125
  this.body.className = "bb-dialog-body";
108
- el.append(header, this.body);
109
- document.body.appendChild(el);
110
- el.addEventListener("close", () => {
126
+ el2.append(header, this.body);
127
+ document.body.appendChild(el2);
128
+ el2.addEventListener("close", () => {
111
129
  this.detachReposition();
112
130
  this.closeListeners.forEach((cb) => cb());
113
131
  });
114
132
  if (!this.modal) {
115
- el.addEventListener("keydown", (e) => {
133
+ el2.addEventListener("keydown", (e) => {
116
134
  if (e.key === "Escape") this.close();
117
135
  });
118
136
  }
119
- this.el = el;
137
+ this.el = el2;
120
138
  }
121
139
  attachReposition() {
122
140
  window.addEventListener("scroll", this.reposition, true);
@@ -201,6 +219,327 @@ function promptDialog(options) {
201
219
  });
202
220
  }
203
221
 
222
+ // packages/ui/src/lib/color-picker.ts
223
+ var COLOR_PALETTE = [
224
+ [
225
+ "#000000",
226
+ "#434343",
227
+ "#666666",
228
+ "#999999",
229
+ "#B7B7B7",
230
+ "#CCCCCC",
231
+ "#D9D9D9",
232
+ "#EFEFEF",
233
+ "#F3F3F3",
234
+ "#FFFFFF"
235
+ ],
236
+ [
237
+ "#980000",
238
+ "#FF0000",
239
+ "#FF9900",
240
+ "#FFFF00",
241
+ "#00FF00",
242
+ "#00FFFF",
243
+ "#4A86E8",
244
+ "#0000FF",
245
+ "#9900FF",
246
+ "#FF00FF"
247
+ ],
248
+ [
249
+ "#E6B8AF",
250
+ "#F4CCCC",
251
+ "#FCE5CD",
252
+ "#FFF2CC",
253
+ "#D9EAD3",
254
+ "#D0E0E3",
255
+ "#C9DAF8",
256
+ "#CFE2F3",
257
+ "#D9D2E9",
258
+ "#EAD1DC"
259
+ ],
260
+ [
261
+ "#DD7E6B",
262
+ "#EA9999",
263
+ "#F9CB9C",
264
+ "#FFE599",
265
+ "#B6D7A8",
266
+ "#A2C4C9",
267
+ "#A4C2F4",
268
+ "#9FC5E8",
269
+ "#B4A7D6",
270
+ "#D5A6BD"
271
+ ],
272
+ [
273
+ "#CC4125",
274
+ "#E06666",
275
+ "#F6B26B",
276
+ "#FFD966",
277
+ "#93C47D",
278
+ "#76A5AF",
279
+ "#6D9EEB",
280
+ "#6FA8DC",
281
+ "#8E7CC3",
282
+ "#C27BA0"
283
+ ],
284
+ [
285
+ "#A61C00",
286
+ "#CC0000",
287
+ "#E69138",
288
+ "#F1C232",
289
+ "#6AA84F",
290
+ "#45818E",
291
+ "#3C78D8",
292
+ "#3D85C6",
293
+ "#674EA7",
294
+ "#A64D79"
295
+ ],
296
+ [
297
+ "#85200C",
298
+ "#990000",
299
+ "#B45F06",
300
+ "#BF9000",
301
+ "#38761D",
302
+ "#134F5C",
303
+ "#1155CC",
304
+ "#0B5394",
305
+ "#351C75",
306
+ "#741B47"
307
+ ],
308
+ [
309
+ "#5B0F00",
310
+ "#660000",
311
+ "#783F04",
312
+ "#7F6000",
313
+ "#274E13",
314
+ "#0C343D",
315
+ "#1C4587",
316
+ "#073763",
317
+ "#20124D",
318
+ "#4C1130"
319
+ ]
320
+ ];
321
+ var customColors = [];
322
+ var MAX_CUSTOM = 8;
323
+ function remember(color) {
324
+ const at = customColors.indexOf(color);
325
+ if (at !== -1) customColors.splice(at, 1);
326
+ customColors.unshift(color);
327
+ if (customColors.length > MAX_CUSTOM) customColors.length = MAX_CUSTOM;
328
+ }
329
+ var isHex = (v) => /^#[0-9a-fA-F]{6}$/.test(v);
330
+ var STYLE2 = `
331
+ .bb-cpk{position:fixed;margin:0;max-width:none;max-height:none;z-index:1300;padding:11px;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);box-sizing:border-box}
332
+ .bb-cpk *{box-sizing:border-box}
333
+ .bb-cpk-grid{display:grid;grid-template-columns:repeat(10,20px);gap:5px}
334
+ .bb-cpk-sw{width:20px;height:20px;padding:0;border-radius:50%;border:1px solid rgba(128,128,128,.35);cursor:pointer}
335
+ .bb-cpk-sw:focus-visible{outline:none}
336
+ .bb-cpk-sw.on,.bb-cpk-sw:focus-visible{box-shadow:0 0 0 2px var(--bb-ui-menu-bg,#fff),0 0 0 4px var(--bb-ui-active-fg,#0c447c)}
337
+ .bb-cpk-cap{font-size:11px;letter-spacing:.04em;opacity:.6;margin:13px 0 7px}
338
+ .bb-cpk-row{display:flex;gap:6px;align-items:center;flex-wrap:wrap}
339
+ .bb-cpk-none{width:100%;display:flex;align-items:center;gap:8px;height:28px;padding:0 7px;margin-bottom:10px;border:0;border-radius:6px;background:transparent;color:inherit;font:inherit;font-size:13px;text-align:left;cursor:pointer}
340
+ .bb-cpk-none:hover,.bb-cpk-none:focus-visible{background:var(--bb-ui-hover,#f1efe8);outline:none}
341
+ .bb-cpk-none-chip{width:18px;height:18px;border-radius:50%;border:1px solid var(--bb-ui-control-border,var(--bb-ui-border,#d8d6cf));display:flex;align-items:center;justify-content:center;font-size:12px;opacity:.6;flex:none}
342
+ .bb-cpk-plus{width:20px;height:20px;padding:0;border-radius:50%;border:1px dashed var(--bb-ui-border,#d8d6cf);background:transparent;color:inherit;font:inherit;font-size:13px;line-height:1;cursor:pointer;opacity:.75;position:relative;overflow:hidden}
343
+ .bb-cpk-plus input{position:absolute;inset:0;opacity:0;cursor:pointer;border:0;padding:0}
344
+ .bb-cpk-hex{height:26px;width:96px;border:1px 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 7px;font-family:var(--bb-ui-mono,ui-monospace,monospace)}
345
+
346
+ .bb-cpk-btn{min-width:30px;height:30px;display:inline-flex;align-items:center;justify-content:center;gap:5px;border:1px 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 7px;cursor:pointer}
347
+ .bb-cpk-btn-chip{width:14px;height:14px;border-radius:50%;border:1px solid rgba(128,128,128,.4);flex:none}
348
+ .bb-cpk-btn-chip.none{background:var(--bb-ui-control-bg,var(--bb-ui-bg,#fff));display:flex;align-items:center;justify-content:center;font-size:10px;opacity:.6}
349
+ .bb-cpk-btn-caret{opacity:.5;font-size:10px}
350
+ /* Toolbar shape: a glyph with the colour as a bar beneath it. */
351
+ .bb-cpk-btn.glyphed{flex-direction:column;gap:1px;min-width:30px;padding:0 6px;border-color:transparent;background:transparent}
352
+ .bb-cpk-btn.glyphed:hover{background:var(--bb-ui-hover,#f1efe8)}
353
+ .bb-cpk-btn-glyph{font-size:13px;line-height:1}
354
+ .bb-cpk-btn-bar{width:16px;height:4px;border-radius:1px;border:0.5px solid rgba(128,128,128,.35)}
355
+ .bb-cpk::backdrop{background:transparent}
356
+ `;
357
+ function openColorPicker({
358
+ anchor,
359
+ value,
360
+ clearLabel,
361
+ onPick
362
+ }) {
363
+ injectStyle("bb-ui-colorpicker-styles", STYLE2);
364
+ const panel = document.createElement("dialog");
365
+ panel.className = "bb-cpk";
366
+ panel.setAttribute("aria-label", "Colour");
367
+ let closed = false;
368
+ const close = () => {
369
+ if (closed) return;
370
+ closed = true;
371
+ window.removeEventListener("scroll", close, true);
372
+ if (panel.open) panel.close();
373
+ panel.remove();
374
+ anchor.focus();
375
+ };
376
+ const pick = (c) => {
377
+ if (c && !COLOR_PALETTE.some((row) => row.includes(c))) remember(c);
378
+ onPick(c);
379
+ close();
380
+ };
381
+ if (clearLabel !== void 0) {
382
+ const none = document.createElement("button");
383
+ none.type = "button";
384
+ none.className = "bb-cpk-none";
385
+ const chip = document.createElement("span");
386
+ chip.className = "bb-cpk-none-chip";
387
+ chip.textContent = "\u29B8";
388
+ none.append(chip, document.createTextNode(clearLabel));
389
+ none.addEventListener("click", () => pick(null));
390
+ panel.append(none);
391
+ }
392
+ const grid = document.createElement("div");
393
+ grid.className = "bb-cpk-grid";
394
+ grid.setAttribute("role", "grid");
395
+ const cells = [];
396
+ COLOR_PALETTE.forEach((row, r2) => {
397
+ const rowCells = [];
398
+ row.forEach((color, c) => {
399
+ const sw = document.createElement("button");
400
+ sw.type = "button";
401
+ sw.className = "bb-cpk-sw";
402
+ sw.style.background = color;
403
+ sw.title = color;
404
+ sw.setAttribute("role", "gridcell");
405
+ sw.setAttribute("aria-label", color);
406
+ sw.tabIndex = -1;
407
+ if (color.toUpperCase() === value?.toUpperCase()) sw.classList.add("on");
408
+ sw.addEventListener("click", () => pick(color));
409
+ sw.addEventListener("keydown", (e) => onGridKey(e, r2, c));
410
+ grid.append(sw);
411
+ rowCells.push(sw);
412
+ });
413
+ cells.push(rowCells);
414
+ });
415
+ panel.append(grid);
416
+ function onGridKey(e, r2, c) {
417
+ const delta = {
418
+ ArrowRight: [0, 1],
419
+ ArrowLeft: [0, -1],
420
+ ArrowDown: [1, 0],
421
+ ArrowUp: [-1, 0]
422
+ };
423
+ const d = delta[e.key];
424
+ if (!d) return;
425
+ e.preventDefault();
426
+ const rows = cells.length;
427
+ const cols = cells[0].length;
428
+ const nr = Math.min(rows - 1, Math.max(0, r2 + d[0]));
429
+ const nc = Math.min(cols - 1, Math.max(0, c + d[1]));
430
+ cells[r2][c].tabIndex = -1;
431
+ const next = cells[nr][nc];
432
+ next.tabIndex = 0;
433
+ next.focus();
434
+ }
435
+ panel.append(
436
+ Object.assign(document.createElement("div"), {
437
+ className: "bb-cpk-cap",
438
+ textContent: "CUSTOM"
439
+ })
440
+ );
441
+ const customRow2 = document.createElement("div");
442
+ customRow2.className = "bb-cpk-row";
443
+ for (const c of customColors) {
444
+ const sw = document.createElement("button");
445
+ sw.type = "button";
446
+ sw.className = "bb-cpk-sw";
447
+ sw.style.background = c;
448
+ sw.title = c;
449
+ sw.setAttribute("aria-label", c);
450
+ if (c.toUpperCase() === value?.toUpperCase()) sw.classList.add("on");
451
+ sw.addEventListener("click", () => pick(c));
452
+ customRow2.append(sw);
453
+ }
454
+ const plus = document.createElement("button");
455
+ plus.type = "button";
456
+ plus.className = "bb-cpk-plus";
457
+ plus.title = "Custom colour";
458
+ plus.append(document.createTextNode("+"));
459
+ const native = document.createElement("input");
460
+ native.type = "color";
461
+ native.value = value && isHex(value) ? value : "#000000";
462
+ native.addEventListener("input", () => pick(native.value.toUpperCase()));
463
+ plus.append(native);
464
+ customRow2.append(plus);
465
+ const hex = document.createElement("input");
466
+ hex.type = "text";
467
+ hex.className = "bb-cpk-hex";
468
+ hex.placeholder = "#RRGGBB";
469
+ hex.spellcheck = false;
470
+ hex.value = value ?? "";
471
+ hex.addEventListener("keydown", (e) => {
472
+ if (e.key !== "Enter") return;
473
+ e.preventDefault();
474
+ const v = hex.value.trim();
475
+ if (isHex(v)) pick(v.toUpperCase());
476
+ });
477
+ customRow2.append(hex);
478
+ panel.append(customRow2);
479
+ document.body.append(panel);
480
+ panel.showModal();
481
+ const r = anchor.getBoundingClientRect();
482
+ placeFloating(panel, { x: r.left, y: r.top, height: r.height });
483
+ panel.addEventListener("close", close);
484
+ panel.addEventListener("pointerdown", (e) => {
485
+ const b = panel.getBoundingClientRect();
486
+ const outside = e.clientX < b.left || e.clientX > b.right || e.clientY < b.top || e.clientY > b.bottom;
487
+ if (outside) close();
488
+ });
489
+ window.addEventListener("scroll", close, true);
490
+ const start = cells.flat().find((el2) => el2.classList.contains("on")) ?? cells[0][0];
491
+ start.tabIndex = 0;
492
+ start.focus();
493
+ return { close };
494
+ }
495
+ function colorButton(o) {
496
+ injectStyle("bb-ui-colorpicker-styles", STYLE2);
497
+ const el2 = document.createElement("button");
498
+ el2.type = "button";
499
+ el2.className = `bb-cpk-btn${o.glyph ? " glyphed" : ""}`;
500
+ el2.title = o.title;
501
+ el2.setAttribute("aria-label", o.title);
502
+ el2.setAttribute("aria-haspopup", "dialog");
503
+ let chip;
504
+ if (o.glyph) {
505
+ const g = document.createElement("span");
506
+ g.className = "bb-cpk-btn-glyph";
507
+ g.innerHTML = o.glyph;
508
+ chip = document.createElement("span");
509
+ chip.className = "bb-cpk-btn-bar";
510
+ el2.append(g, chip);
511
+ } else {
512
+ chip = document.createElement("span");
513
+ chip.className = "bb-cpk-btn-chip";
514
+ el2.append(chip);
515
+ if (o.label) el2.append(document.createTextNode(o.label));
516
+ const caret = document.createElement("span");
517
+ caret.className = "bb-cpk-btn-caret";
518
+ caret.textContent = "\u25BE";
519
+ el2.append(caret);
520
+ }
521
+ function sync() {
522
+ const v = o.value();
523
+ chip.style.background = v ?? "transparent";
524
+ chip.classList.toggle("none", !v && !o.glyph);
525
+ chip.textContent = !v && !o.glyph ? "\u29B8" : "";
526
+ }
527
+ el2.addEventListener("mousedown", (e) => e.preventDefault());
528
+ el2.addEventListener("click", () => {
529
+ openColorPicker({
530
+ anchor: el2,
531
+ value: o.value(),
532
+ clearLabel: o.clearLabel,
533
+ onPick: (c) => {
534
+ o.onPick(c);
535
+ sync();
536
+ }
537
+ });
538
+ });
539
+ sync();
540
+ return { el: el2, sync };
541
+ }
542
+
204
543
  // packages/ui/src/lib/toolbar.ts
205
544
  var alignSvg = (spans) => `<svg width="15" height="15" viewBox="0 0 16 16" aria-hidden="true"><g stroke="currentColor" stroke-width="1.6" stroke-linecap="round">` + spans.map(
206
545
  ([x1, x2], i) => `<line x1="${x1}" y1="${4 + i * 4}" x2="${x2}" y2="${4 + i * 4}"/>`
@@ -271,9 +610,9 @@ var DEFAULT_ITEMS = {
271
610
  svg: '<svg viewBox="0 0 16 16" width="16" height="16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"><path d="M6 4h8M6 8h8M6 12h8"/><text x="0.5" y="5.6" font-size="5.5" fill="currentColor" stroke="none">1</text><text x="0.5" y="9.6" font-size="5.5" fill="currentColor" stroke="none">2</text><text x="0.5" y="13.6" font-size="5.5" fill="currentColor" stroke="none">3</text></svg>'
272
611
  }
273
612
  };
274
- var STYLE2 = `
613
+ var STYLE3 = `
275
614
  .bb-toolbar-wrap{position:relative}
276
- .bb-toolbar{display:flex;gap:10px;align-items:center;flex-wrap:nowrap;overflow:hidden;padding:6px 8px;font-family:var(--bb-ui-font,system-ui,-apple-system,sans-serif);color:var(--bb-ui-fg,#2c2c2a);background:var(--bb-ui-bg,#fff);border-bottom:1px solid var(--bb-ui-border,#e3e3e0);box-sizing:border-box}
615
+ .bb-toolbar{display:flex;gap:10px;align-items:center;flex-wrap:nowrap;overflow:hidden;padding:6px 8px;font-family:var(--bb-ui-font,system-ui,-apple-system,sans-serif);color:var(--bb-ui-fg,#2c2c2a);background:var(--bb-ui-control-bg,var(--bb-ui-bg,#fff));border-bottom:1px solid var(--bb-ui-border,#e3e3e0);box-sizing:border-box}
277
616
  .bb-toolbar *{box-sizing:border-box}
278
617
  .bb-toolbar-group{display:flex;gap:2px;flex:none}
279
618
  .bb-toolbar-group+.bb-toolbar-group{padding-left:10px;border-left:1px solid var(--bb-ui-border,#e3e3e0)}
@@ -284,16 +623,8 @@ var STYLE2 = `
284
623
  .bb-toolbar-btn:hover{background:var(--bb-ui-hover,#f1efe8)}
285
624
  .bb-toolbar-btn.is-active{background:var(--bb-ui-active-bg,#e6f1fb);color:var(--bb-ui-active-fg,#0c447c);border-color:var(--bb-ui-active-border,#b5d4f4)}
286
625
  .bb-toolbar-btn:disabled{opacity:.38;cursor:default}
287
- .bb-toolbar-select{height:30px;border:1px solid var(--bb-ui-border,#e3e3e0);border-radius:6px;background:var(--bb-ui-bg,#fff);color:inherit;font-family:inherit;font-size:13px;padding:0 6px;cursor:pointer}
626
+ .bb-toolbar-select{height:30px;border:1px solid var(--bb-ui-control-border,var(--bb-ui-border,#e3e3e0));border-radius:6px;background:var(--bb-ui-control-bg,var(--bb-ui-bg,#fff));color:inherit;font-family:inherit;font-size:13px;padding:0 6px;cursor:pointer}
288
627
  .bb-toolbar-select:hover{background:var(--bb-ui-hover,#f1efe8)}
289
- .bb-toolbar-color{position:relative}
290
- .bb-toolbar-color .bb-color-glyph{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:1px;line-height:1;font-size:13px}
291
- .bb-toolbar-color .bb-color-bar{width:16px;height:3px;border-radius:1px;background:currentColor}
292
- .bb-color-pop{position:absolute;z-index:1200;display:grid;grid-template-columns:repeat(8,16px);gap:4px;padding:8px;background:var(--bb-ui-menu-bg,#fff);-webkit-backdrop-filter:var(--bb-ui-pop-filter,none);backdrop-filter:var(--bb-ui-pop-filter,none);border:1px solid var(--bb-ui-pop-border,var(--bb-ui-border,#e3e3e0));border-radius:8px;box-shadow:0 8px 28px rgba(0,0,0,.16)}
293
- .bb-color-pop[hidden]{display:none}
294
- .bb-color-swatch{width:16px;height:16px;padding:0;border:1px solid rgba(0,0,0,.18);border-radius:3px;cursor:pointer}
295
- .bb-color-none{grid-column:1/-1;font:inherit;font-size:12px;padding:3px 0;border:1px solid var(--bb-ui-border,#e3e3e0);border-radius:5px;background:var(--bb-ui-bg,#fff);cursor:pointer}
296
- .bb-color-none:hover{background:var(--bb-ui-hover,#f1efe8)}
297
628
  .bb-i-bold{font-weight:700}.bb-i-italic{font-style:italic}.bb-i-underline{text-decoration:underline}.bb-i-strike{text-decoration:line-through}
298
629
  .bb-toolbar-split{display:flex;align-items:center;gap:0}
299
630
  .bb-toolbar-split .bb-toolbar-btn{min-width:26px;padding:0 5px}
@@ -301,7 +632,7 @@ var STYLE2 = `
301
632
  .bb-split-arrow svg{display:block}
302
633
  .bb-split-pop{position:absolute;z-index:1200;display:grid;grid-template-columns:repeat(3,auto);gap:8px;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);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)}
303
634
  .bb-split-pop[hidden]{display:none}
304
- .bb-split-card{display:flex;flex-direction:column;gap:6px;width:76px;padding:9px 8px;border:1px solid var(--bb-ui-border,#e3e3e0);border-radius:6px;background:transparent;cursor:pointer;font-family:inherit}
635
+ .bb-split-card{display:flex;flex-direction:column;gap:6px;width:76px;padding:9px 8px;border:1px solid var(--bb-ui-control-border,var(--bb-ui-border,#e3e3e0));border-radius:6px;background:transparent;cursor:pointer;font-family:inherit}
305
636
  .bb-split-card:hover{background:var(--bb-ui-hover,#f1efe8)}
306
637
  .bb-split-card.is-selected{background:var(--bb-ui-active-bg,#e6f1fb);border-color:var(--bb-ui-active-border,#b5d4f4)}
307
638
  .bb-split-row{display:flex;align-items:center;gap:4px}
@@ -317,7 +648,7 @@ function defaultToolbarGroups(commands) {
317
648
  return [rest, aligns].filter((g) => g.length > 0);
318
649
  }
319
650
  function mountToolbar(host, editor, options = {}) {
320
- injectStyle("bb-ui-toolbar-styles", STYLE2);
651
+ injectStyle("bb-ui-toolbar-styles", STYLE3);
321
652
  const items = { ...DEFAULT_ITEMS, ...options.items ?? {} };
322
653
  const groups = options.groups ?? defaultToolbarGroups(editor.commands);
323
654
  const wrap = document.createElement("div");
@@ -360,77 +691,20 @@ function mountToolbar(host, editor, options = {}) {
360
691
  continue;
361
692
  }
362
693
  if (typeof entry !== "string" && entry.kind === "color") {
363
- const colorWrap = document.createElement("div");
364
- colorWrap.className = "bb-toolbar-color";
365
- const btn2 = document.createElement("button");
366
- btn2.type = "button";
367
- btn2.className = "bb-toolbar-btn";
368
- btn2.title = entry.title;
369
- btn2.setAttribute("aria-label", entry.title);
370
- const glyph = document.createElement("span");
371
- glyph.className = "bb-color-glyph";
372
- glyph.innerHTML = entry.glyph;
373
- const bar = document.createElement("span");
374
- bar.className = "bb-color-bar";
375
- glyph.appendChild(bar);
376
- btn2.appendChild(glyph);
377
- const pop2 = document.createElement("div");
378
- pop2.className = "bb-color-pop";
379
- pop2.hidden = true;
380
- const pick = (color) => {
381
- entry.onSelect(color);
382
- pop2.hidden = true;
383
- editor.focus();
384
- };
385
- for (const c of entry.swatches) {
386
- const sw = document.createElement("button");
387
- sw.type = "button";
388
- sw.className = "bb-color-swatch";
389
- sw.style.background = c;
390
- sw.title = c;
391
- sw.addEventListener("mousedown", (e) => e.preventDefault());
392
- sw.addEventListener("click", () => pick(c));
393
- pop2.appendChild(sw);
394
- }
395
- if (entry.allowNone) {
396
- const none = document.createElement("button");
397
- none.type = "button";
398
- none.className = "bb-color-none";
399
- none.textContent = "None";
400
- none.addEventListener("mousedown", (e) => e.preventDefault());
401
- none.addEventListener("click", () => pick(null));
402
- pop2.appendChild(none);
403
- }
404
- btn2.addEventListener("mousedown", (e) => e.preventDefault());
405
- btn2.addEventListener("click", () => {
406
- const open = pop2.hidden;
407
- wrap.querySelectorAll(".bb-color-pop,.bb-split-pop").forEach((p) => p.hidden = true);
408
- pop2.hidden = !open;
409
- if (!pop2.hidden) {
410
- const wrapRect = wrap.getBoundingClientRect();
411
- const btnRect = btn2.getBoundingClientRect();
412
- pop2.style.top = `${btnRect.bottom - wrapRect.top + 4}px`;
413
- const left = Math.max(
414
- 0,
415
- Math.min(
416
- btnRect.left - wrapRect.left,
417
- wrap.clientWidth - pop2.offsetWidth - 4
418
- )
419
- );
420
- pop2.style.left = `${left}px`;
421
- const onDoc = (ev) => {
422
- if (!colorWrap.contains(ev.target) && !pop2.contains(ev.target)) {
423
- pop2.hidden = true;
424
- document.removeEventListener("pointerdown", onDoc, true);
425
- }
426
- };
427
- document.addEventListener("pointerdown", onDoc, true);
694
+ const ctl = colorButton({
695
+ title: entry.title,
696
+ glyph: entry.glyph,
697
+ clearLabel: entry.clearLabel,
698
+ // `latest`, not `editor.state`: the toolbar mounts before any
699
+ // document exists, and reading state then throws.
700
+ value: () => latest ? entry.value(latest) : null,
701
+ onPick: (color) => {
702
+ entry.onSelect(color);
703
+ editor.focus();
428
704
  }
429
705
  });
430
- colorWrap.appendChild(btn2);
431
- wrap.appendChild(pop2);
432
- groupEl.appendChild(colorWrap);
433
- colors.push({ spec: entry, bar });
706
+ groupEl.appendChild(ctl.el);
707
+ colors.push(ctl);
434
708
  continue;
435
709
  }
436
710
  if (typeof entry !== "string" && entry.kind === "split") {
@@ -492,7 +766,7 @@ function mountToolbar(host, editor, options = {}) {
492
766
  arrow.addEventListener("mousedown", (e) => e.preventDefault());
493
767
  arrow.addEventListener("click", () => {
494
768
  const open = pop2.hidden;
495
- wrap.querySelectorAll(".bb-split-pop,.bb-color-pop").forEach((p) => p.hidden = true);
769
+ wrap.querySelectorAll(".bb-split-pop").forEach((p) => p.hidden = true);
496
770
  pop2.hidden = !open;
497
771
  if (!pop2.hidden) {
498
772
  const wrapRect = wrap.getBoundingClientRect();
@@ -572,11 +846,11 @@ function mountToolbar(host, editor, options = {}) {
572
846
  while (pop.firstChild) root.insertBefore(pop.firstChild, moreBtn);
573
847
  moreBtn.style.display = "none";
574
848
  closePop();
575
- wrap.querySelectorAll(".bb-color-pop,.bb-split-pop").forEach((p) => p.hidden = true);
849
+ wrap.querySelectorAll(".bb-split-pop").forEach((p) => p.hidden = true);
576
850
  if (fits()) return;
577
851
  moreBtn.style.display = "";
578
852
  const groupEls = Array.from(root.children).filter(
579
- (el) => el.classList.contains("bb-toolbar-group")
853
+ (el2) => el2.classList.contains("bb-toolbar-group")
580
854
  );
581
855
  for (let i = groupEls.length - 1; i >= 0 && !fits(); i--) {
582
856
  pop.insertBefore(groupEls[i], pop.firstChild);
@@ -591,17 +865,16 @@ function mountToolbar(host, editor, options = {}) {
591
865
  layout();
592
866
  const refresh = (state) => {
593
867
  latest = state;
594
- for (const { name, el } of buttons) {
868
+ for (const { name, el: el2 } of buttons) {
595
869
  const cmd = editor.commands.get(name);
596
870
  if (!cmd) continue;
597
871
  const active = cmd.isActive?.(state) ?? false;
598
- el.classList.toggle("is-active", active);
599
- el.setAttribute("aria-pressed", String(active));
600
- el.disabled = cmd.isEnabled ? !cmd.isEnabled(state) : false;
872
+ el2.classList.toggle("is-active", active);
873
+ el2.setAttribute("aria-pressed", String(active));
874
+ el2.disabled = cmd.isEnabled ? !cmd.isEnabled(state) : false;
601
875
  }
602
- for (const { spec, el } of selects) el.value = spec.value(state);
603
- for (const { spec, bar } of colors)
604
- bar.style.background = spec.value(state) ?? "";
876
+ for (const { spec, el: el2 } of selects) el2.value = spec.value(state);
877
+ for (const c of colors) c.sync();
605
878
  for (const { spec, cards } of splits) {
606
879
  const selected = spec.value(state);
607
880
  for (const card of cards)
@@ -635,6 +908,8 @@ var DEFAULT_LABELS = {
635
908
  strike: "Strikethrough",
636
909
  superscript: "Superscript",
637
910
  subscript: "Subscript",
911
+ "small-caps": "Small caps",
912
+ "double-strike": "Double strikethrough",
638
913
  "align-left": "Align left",
639
914
  "align-center": "Center",
640
915
  "align-right": "Align right",
@@ -651,8 +926,8 @@ var DEFAULT_LABELS = {
651
926
  redo: "Redo",
652
927
  "page-break": "Page break"
653
928
  };
654
- var STYLE3 = `
655
- .bb-menubar{display:flex;gap:2px;align-items:center;padding:2px 6px;font-family:var(--bb-ui-font,system-ui,-apple-system,sans-serif);color:var(--bb-ui-fg,#2c2c2a);background:var(--bb-ui-bg,#fff);border-bottom:1px solid var(--bb-ui-border,#e3e3e0);box-sizing:border-box}
929
+ var STYLE4 = `
930
+ .bb-menubar{display:flex;gap:2px;align-items:center;padding:2px 6px;font-family:var(--bb-ui-font,system-ui,-apple-system,sans-serif);color:var(--bb-ui-fg,#2c2c2a);background:var(--bb-ui-control-bg,var(--bb-ui-bg,#fff));border-bottom:1px solid var(--bb-ui-border,#e3e3e0);box-sizing:border-box}
656
931
  .bb-menubar *{box-sizing:border-box}
657
932
  .bb-menubar-menu{position:relative}
658
933
  .bb-menubar-title{height:28px;padding:0 10px;border:0;border-radius:6px;background:transparent;color:inherit;font:inherit;font-size:13px;cursor:pointer}
@@ -689,7 +964,7 @@ function defaultMenus(commands) {
689
964
  return entries.length ? [{ label: "Format", entries }] : [];
690
965
  }
691
966
  function mountMenubar(host, editor, options = {}) {
692
- injectStyle("bb-ui-menubar-styles", STYLE3);
967
+ injectStyle("bb-ui-menubar-styles", STYLE4);
693
968
  const labels = { ...DEFAULT_LABELS, ...options.labels ?? {} };
694
969
  const menus = options.menus ?? defaultMenus(editor.commands);
695
970
  const vertical = options.orientation === "vertical";
@@ -885,18 +1160,18 @@ var DEFAULT_LABELS2 = {
885
1160
  replaceOne: "Replace",
886
1161
  replaceAll: "Replace all"
887
1162
  };
888
- var STYLE4 = `
1163
+ var STYLE5 = `
889
1164
  .bb-find{display:flex;flex-direction:column;gap:8px;min-width:300px}
890
1165
  .bb-find *{box-sizing:border-box}
891
1166
  .bb-find-row{display:flex;gap:6px;align-items:center}
892
- .bb-find-input{flex:1 1 auto;height:30px;padding:0 9px;border:1px solid var(--bb-ui-border,#d8d6cf);border-radius:6px;font:inherit;font-size:13px;background:var(--bb-ui-bg,#fff);color:inherit}
1167
+ .bb-find-input{flex:1 1 auto;height:30px;padding:0 9px;border:1px solid var(--bb-ui-control-border,var(--bb-ui-border,#d8d6cf));border-radius:6px;font:inherit;font-size:13px;background:var(--bb-ui-control-bg,var(--bb-ui-bg,#fff));color:inherit}
893
1168
  .bb-find-count{min-width:44px;text-align:center;font-size:12px;opacity:.65;font-variant-numeric:tabular-nums}
894
- .bb-find-btn{height:30px;min-width:30px;padding:0 10px;border:1px solid var(--bb-ui-border,#d8d6cf);border-radius:6px;background:var(--bb-ui-bg,#fff);color:inherit;font:inherit;font-size:13px;cursor:pointer}
1169
+ .bb-find-btn{height:30px;min-width:30px;padding:0 10px;border:1px 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}
895
1170
  .bb-find-btn:hover:not(:disabled){background:var(--bb-ui-hover,#f1efe8)}
896
1171
  .bb-find-btn:disabled{opacity:.4;cursor:default}
897
1172
  `;
898
1173
  function createFindDialog(handle, options = {}) {
899
- injectStyle("bb-ui-find-styles", STYLE4);
1174
+ injectStyle("bb-ui-find-styles", STYLE5);
900
1175
  const find = typeof handle === "function" ? new Proxy({}, {
901
1176
  get: (_t, prop) => handle()[prop]
902
1177
  }) : handle;
@@ -997,7 +1272,7 @@ function createFindDialog(handle, options = {}) {
997
1272
  }
998
1273
 
999
1274
  // packages/ui/src/lib/context-menu.ts
1000
- var STYLE5 = `
1275
+ var STYLE6 = `
1001
1276
  .bb-ctx{position:fixed;z-index:1200;min-width:208px;padding:4px;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:8px;box-shadow:0 8px 28px rgba(0,0,0,.16);font-family:var(--bb-ui-font,system-ui,-apple-system,sans-serif)}
1002
1277
  .bb-ctx *{box-sizing:border-box}
1003
1278
  .bb-ctx-item{display:flex;align-items:center;gap:16px;width:100%;height:30px;padding:0 10px;border:0;border-radius:5px;background:transparent;color:inherit;font:inherit;font-size:13px;text-align:left;white-space:nowrap;cursor:pointer}
@@ -1016,17 +1291,17 @@ function closeCurrent() {
1016
1291
  }
1017
1292
  }
1018
1293
  function showContextMenu(entries, at) {
1019
- injectStyle("bb-ui-contextmenu-styles", STYLE5);
1294
+ injectStyle("bb-ui-contextmenu-styles", STYLE6);
1020
1295
  closeCurrent();
1021
- const el = document.createElement("div");
1022
- el.className = "bb-ctx";
1023
- el.setAttribute("role", "menu");
1296
+ const el2 = document.createElement("div");
1297
+ el2.className = "bb-ctx";
1298
+ el2.setAttribute("role", "menu");
1024
1299
  for (const entry of entries) {
1025
1300
  if (entry === "separator") {
1026
1301
  const sep = document.createElement("div");
1027
1302
  sep.className = "bb-ctx-sep";
1028
1303
  sep.setAttribute("role", "separator");
1029
- el.appendChild(sep);
1304
+ el2.appendChild(sep);
1030
1305
  continue;
1031
1306
  }
1032
1307
  const item = document.createElement("button");
@@ -1049,22 +1324,22 @@ function showContextMenu(entries, at) {
1049
1324
  closeCurrent();
1050
1325
  entry.run();
1051
1326
  });
1052
- el.appendChild(item);
1327
+ el2.appendChild(item);
1053
1328
  }
1054
- document.body.appendChild(el);
1055
- const r = el.getBoundingClientRect();
1329
+ document.body.appendChild(el2);
1330
+ const r = el2.getBoundingClientRect();
1056
1331
  const pad = 4;
1057
1332
  const fitsX = (v) => v >= pad && v + r.width <= window.innerWidth - pad;
1058
1333
  const fitsY = (v) => v >= pad && v + r.height <= window.innerHeight - pad;
1059
1334
  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));
1060
1335
  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));
1061
- el.style.left = `${x}px`;
1062
- el.style.top = `${y}px`;
1336
+ el2.style.left = `${x}px`;
1337
+ el2.style.top = `${y}px`;
1063
1338
  const items = () => Array.from(
1064
- el.querySelectorAll(".bb-ctx-item:not(:disabled)")
1339
+ el2.querySelectorAll(".bb-ctx-item:not(:disabled)")
1065
1340
  );
1066
1341
  const onDown = (e) => {
1067
- if (!el.contains(e.target)) closeCurrent();
1342
+ if (!el2.contains(e.target)) closeCurrent();
1068
1343
  };
1069
1344
  const onKey = (e) => {
1070
1345
  if (e.key === "Escape") return closeCurrent();
@@ -1086,19 +1361,19 @@ function showContextMenu(entries, at) {
1086
1361
  document.removeEventListener("pointerdown", onDown, true);
1087
1362
  document.removeEventListener("keydown", onKey, true);
1088
1363
  window.removeEventListener("scroll", onScroll, true);
1089
- el.remove();
1364
+ el2.remove();
1090
1365
  };
1091
1366
  items()[0]?.focus();
1092
1367
  return { close: closeCurrent };
1093
1368
  }
1094
1369
 
1095
1370
  // packages/ui/src/lib/link-panel.ts
1096
- var STYLE6 = `
1371
+ var STYLE7 = `
1097
1372
  .bb-linkpanel{position:fixed;z-index:1200;width:300px;padding:8px;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);box-sizing:border-box}
1098
1373
  .bb-linkpanel *{box-sizing:border-box}
1099
1374
  .bb-linkpanel-row{display:flex;align-items:center;gap:8px}
1100
1375
  .bb-linkpanel-form{display:flex;flex-direction:column;gap:8px}
1101
- .bb-linkpanel-field{display:flex;align-items:center;gap:7px;border:1px solid var(--bb-ui-border,#e3e3e0);border-radius:7px;padding:0 9px;background:var(--bb-ui-bg,#fff)}
1376
+ .bb-linkpanel-field{display:flex;align-items:center;gap:7px;border:1px solid var(--bb-ui-control-border,var(--bb-ui-border,#e3e3e0));border-radius:7px;padding:0 9px;background:var(--bb-ui-control-bg,var(--bb-ui-bg,#fff))}
1102
1377
  .bb-linkpanel-field:focus-within{border-color:var(--bb-ui-accent,#d85a30)}
1103
1378
  .bb-linkpanel-field svg{flex:none;opacity:.55}
1104
1379
  .bb-linkpanel-input{flex:1;min-width:0;height:30px;border:0;background:transparent;color:inherit;font:inherit;font-size:13px;outline:none}
@@ -1140,31 +1415,17 @@ function closeCurrent2() {
1140
1415
  dispose();
1141
1416
  }
1142
1417
  }
1143
- function place(el, anchor) {
1144
- const r = el.getBoundingClientRect();
1145
- const pad = 6;
1146
- const gap = 6;
1147
- const x = Math.max(
1148
- pad,
1149
- Math.min(anchor.x, window.innerWidth - r.width - pad)
1150
- );
1151
- const below = anchor.y + anchor.height + gap;
1152
- const above = anchor.y - gap - r.height;
1153
- const y = below + r.height <= window.innerHeight - pad || above < pad ? Math.min(below, window.innerHeight - r.height - pad) : above;
1154
- el.style.left = `${x}px`;
1155
- el.style.top = `${y}px`;
1156
- }
1157
1418
  function showLinkPanel(opts) {
1158
- injectStyle("bb-ui-linkpanel-styles", STYLE6);
1419
+ injectStyle("bb-ui-linkpanel-styles", STYLE7);
1159
1420
  if (opts.key && current2 && currentKey === opts.key && currentHandle) {
1160
1421
  cancelPendingClose();
1161
1422
  return currentHandle;
1162
1423
  }
1163
1424
  closeCurrent2();
1164
- const el = document.createElement("div");
1165
- el.className = "bb-linkpanel";
1166
- el.setAttribute("role", "dialog");
1167
- el.setAttribute("aria-label", "Link");
1425
+ const el2 = document.createElement("div");
1426
+ el2.className = "bb-linkpanel";
1427
+ el2.setAttribute("role", "dialog");
1428
+ el2.setAttribute("aria-label", "Link");
1168
1429
  const iconBtn = (svg, label, onClick) => {
1169
1430
  const b = document.createElement("button");
1170
1431
  b.type = "button";
@@ -1191,7 +1452,7 @@ function showLinkPanel(opts) {
1191
1452
  return { wrap, input };
1192
1453
  };
1193
1454
  const renderView = (existing) => {
1194
- el.textContent = "";
1455
+ el2.textContent = "";
1195
1456
  const row = document.createElement("div");
1196
1457
  row.className = "bb-linkpanel-row";
1197
1458
  const internal = opts.internal;
@@ -1238,11 +1499,11 @@ function showLinkPanel(opts) {
1238
1499
  })
1239
1500
  );
1240
1501
  }
1241
- el.appendChild(row);
1242
- place(el, opts.anchor);
1502
+ el2.appendChild(row);
1503
+ placeFloating(el2, opts.anchor);
1243
1504
  };
1244
1505
  const renderForm = (prefill) => {
1245
- el.textContent = "";
1506
+ el2.textContent = "";
1246
1507
  const form = document.createElement("div");
1247
1508
  form.className = "bb-linkpanel-form";
1248
1509
  const withText = !opts.hasSelection;
@@ -1283,16 +1544,16 @@ function showLinkPanel(opts) {
1283
1544
  }
1284
1545
  row.appendChild(apply);
1285
1546
  form.appendChild(row);
1286
- el.appendChild(form);
1287
- place(el, opts.anchor);
1547
+ el2.appendChild(form);
1548
+ placeFloating(el2, opts.anchor);
1288
1549
  u.input.focus();
1289
1550
  u.input.select();
1290
1551
  };
1291
- document.body.appendChild(el);
1552
+ document.body.appendChild(el2);
1292
1553
  if (opts.existing) renderView(opts.existing);
1293
1554
  else renderForm(null);
1294
1555
  const onDown = (e) => {
1295
- if (el.contains(e.target)) return;
1556
+ if (el2.contains(e.target)) return;
1296
1557
  if (opts.key) {
1297
1558
  cancelPendingClose();
1298
1559
  pendingClose = setTimeout(closeCurrent2, 80);
@@ -1313,7 +1574,7 @@ function showLinkPanel(opts) {
1313
1574
  document.removeEventListener("pointerdown", onDown, true);
1314
1575
  document.removeEventListener("keydown", onKey, true);
1315
1576
  window.removeEventListener("scroll", onScroll, true);
1316
- el.remove();
1577
+ el2.remove();
1317
1578
  };
1318
1579
  current2 = dispose;
1319
1580
  currentKey = opts.key ?? null;
@@ -1328,8 +1589,6 @@ function showLinkPanel(opts) {
1328
1589
  }
1329
1590
 
1330
1591
  // packages/ui/src/lib/cell-properties.ts
1331
- var FILL_PRESETS = ["#FCEBEB", "#FAEEDA", "#EAF3DE", "#E6F1FB", "#EEEDFE", "#FBEAF0", "#E1F5EE", "#F1EFE8", "#D3D1C7"];
1332
- var PEN_COLORS = ["#000000", "#5F5E5A", "#B0B0B0", "#E24B4A", "#185FA5", "#1D9E75", "#BA7517"];
1333
1592
  var PT_WIDTHS = [0.5, 0.75, 1, 1.5, 2.25, 3];
1334
1593
  var STYLES = [
1335
1594
  { key: "solid", label: "Solid" },
@@ -1343,43 +1602,53 @@ var VALIGNS = [
1343
1602
  { key: "bottom", label: "Bottom" }
1344
1603
  ];
1345
1604
  var PRESETS = [
1346
- { key: "all", label: "All borders", sides: [1, 1, 1, 1, 1, 1], inside: false },
1605
+ {
1606
+ key: "all",
1607
+ label: "All borders",
1608
+ sides: [1, 1, 1, 1, 1, 1],
1609
+ inside: false
1610
+ },
1347
1611
  { key: "none", label: "No border", sides: [0, 0, 0, 0, 0, 0], inside: false },
1348
- { key: "outside", label: "Outside", sides: [1, 1, 1, 1, 0, 0], inside: false },
1612
+ {
1613
+ key: "outside",
1614
+ label: "Outside",
1615
+ sides: [1, 1, 1, 1, 0, 0],
1616
+ inside: false
1617
+ },
1349
1618
  { key: "inside", label: "Inside", sides: [0, 0, 0, 0, 1, 1], inside: true },
1350
1619
  { key: "top", label: "Top", sides: [1, 0, 0, 0, 0, 0], inside: false },
1351
1620
  { key: "bottom", label: "Bottom", sides: [0, 0, 1, 0, 0, 0], inside: false },
1352
1621
  { key: "left", label: "Left", sides: [0, 0, 0, 1, 0, 0], inside: false },
1353
1622
  { key: "right", label: "Right", sides: [0, 1, 0, 0, 0, 0], inside: false },
1354
- { key: "insideH", label: "Inside horizontal", sides: [0, 0, 0, 0, 1, 0], inside: true },
1355
- { key: "insideV", label: "Inside vertical", sides: [0, 0, 0, 0, 0, 1], inside: true }
1623
+ {
1624
+ key: "insideH",
1625
+ label: "Inside horizontal",
1626
+ sides: [0, 0, 0, 0, 1, 0],
1627
+ inside: true
1628
+ },
1629
+ {
1630
+ key: "insideV",
1631
+ label: "Inside vertical",
1632
+ sides: [0, 0, 0, 0, 0, 1],
1633
+ inside: true
1634
+ }
1356
1635
  ];
1357
- var STYLE7 = `
1636
+ var STYLE8 = `
1358
1637
  .bb-cp{display:flex;flex-direction:column;gap:16px;min-width:320px;color:var(--bb-ui-fg,#2c2c2a)}
1359
1638
  .bb-cp *{box-sizing:border-box}
1360
1639
  .bb-cp-label{font-size:12px;opacity:.7;margin-bottom:8px}
1361
- .bb-cp-swatches{display:flex;flex-wrap:wrap;gap:7px}
1362
- .bb-cp-swatch{width:26px;height:26px;border-radius:6px;border:0.5px solid var(--bb-ui-border,#d8d6cf);padding:0;cursor:pointer}
1363
- .bb-cp-swatch.on{box-shadow:0 0 0 2px var(--bb-ui-active-fg,#0c447c)}
1364
- .bb-cp-none{display:flex;align-items:center;justify-content:center;background:var(--bb-ui-bg,#fff);font-size:15px;color:#b4b2a9}
1365
- .bb-cp-hexrow{display:flex;align-items:center;gap:8px;margin-top:10px}
1366
- .bb-cp-hexpreview{width:26px;height:26px;border-radius:6px;border:0.5px solid var(--bb-ui-border,#d8d6cf);flex:none}
1367
- .bb-cp-hex{height:30px;width:120px;font-size:13px;padding:0 8px;border:0.5px solid var(--bb-ui-border,#d8d6cf);border-radius:6px;background:var(--bb-ui-bg,#fff);color:inherit}
1368
- .bb-cp-seg{display:inline-flex;border:0.5px solid var(--bb-ui-border,#d8d6cf);border-radius:6px;overflow:hidden}
1640
+ .bb-cp-seg{display:inline-flex;border:0.5px solid var(--bb-ui-control-border,var(--bb-ui-border,#d8d6cf));border-radius:6px;overflow:hidden}
1369
1641
  .bb-cp-segbtn{height:32px;padding:0 16px;border:0;border-right:0.5px solid var(--bb-ui-border,#e3e3e0);background:transparent;color:inherit;font:inherit;font-size:13px;cursor:pointer}
1370
1642
  .bb-cp-segbtn:last-child{border-right:0}
1371
1643
  .bb-cp-segbtn.on{background:var(--bb-ui-active-bg,#e6f1fb);color:var(--bb-ui-active-fg,#0c447c)}
1372
1644
  .bb-cp-presets{display:grid;grid-template-columns:repeat(5,1fr);gap:6px;margin-bottom:12px}
1373
- .bb-cp-preset{display:flex;align-items:center;justify-content:center;height:32px;border:0.5px solid var(--bb-ui-border,#d8d6cf);border-radius:6px;background:var(--bb-ui-bg,#fff);cursor:pointer;padding:0}
1645
+ .bb-cp-preset{display:flex;align-items:center;justify-content:center;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));cursor:pointer;padding:0}
1374
1646
  .bb-cp-preset.on{border-color:var(--bb-ui-active-border,#7fb2ec);background:var(--bb-ui-active-bg,#e6f1fb)}
1375
1647
  .bb-cp-preset:disabled{opacity:.35;cursor:default}
1376
1648
  .bb-cp-penrow{display:flex;gap:10px;margin-bottom:10px}
1377
- .bb-cp-select{flex:1;height:32px;border:0.5px solid var(--bb-ui-border,#d8d6cf);border-radius:6px;background:var(--bb-ui-bg,#fff);color:inherit;font:inherit;font-size:13px;padding:0 8px}
1378
- .bb-cp-colors{display:flex;gap:7px}
1379
- .bb-cp-color{width:24px;height:24px;border-radius:6px;border:0.5px solid var(--bb-ui-border,#d8d6cf);padding:0;cursor:pointer}
1380
- .bb-cp-color.on{box-shadow:0 0 0 2px var(--bb-ui-active-fg,#0c447c)}
1649
+ .bb-cp-select{flex:1;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}
1381
1650
  .bb-cp-footer{display:flex;justify-content:flex-end;gap:8px;margin-top:2px}
1382
- .bb-cp-btn{height:32px;padding:0 16px;border:0.5px solid var(--bb-ui-border,#d8d6cf);border-radius:6px;background:var(--bb-ui-bg,#fff);color:inherit;font:inherit;font-size:13px;cursor:pointer}
1651
+ .bb-cp-btn{height:32px;padding:0 16px;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}
1383
1652
  .bb-cp-primary{background:var(--bb-ui-active-bg,#e6f1fb);border-color:var(--bb-ui-active-border,#b5d4f4);color:var(--bb-ui-active-fg,#0c447c)}
1384
1653
  `;
1385
1654
  var ACTIVE = "#378ADD";
@@ -1394,8 +1663,12 @@ var section = (label) => {
1394
1663
  sec.appendChild(l);
1395
1664
  return sec;
1396
1665
  };
1397
- function openCellProperties({ initial, singleCell, onApply }) {
1398
- injectStyle("bb-ui-cellprops-styles", STYLE7);
1666
+ function openCellProperties({
1667
+ initial,
1668
+ singleCell,
1669
+ onApply
1670
+ }) {
1671
+ injectStyle("bb-ui-cellprops-styles", STYLE8);
1399
1672
  let background = initial.background;
1400
1673
  let vAlign = initial.vAlign;
1401
1674
  let penWidthPt = 1;
@@ -1405,48 +1678,16 @@ function openCellProperties({ initial, singleCell, onApply }) {
1405
1678
  const root = document.createElement("div");
1406
1679
  root.className = "bb-cp";
1407
1680
  const fill = section("Fill");
1408
- const swatches = document.createElement("div");
1409
- swatches.className = "bb-cp-swatches";
1410
- const swatchEls = [];
1411
- const addSwatch = (el, color) => {
1412
- el.addEventListener("click", () => {
1413
- background = color;
1414
- syncFill();
1415
- });
1416
- swatches.appendChild(el);
1417
- swatchEls.push({ el, color });
1418
- };
1419
- const none = document.createElement("button");
1420
- none.type = "button";
1421
- none.className = "bb-cp-swatch bb-cp-none";
1422
- none.title = "No fill";
1423
- none.textContent = "\u29B8";
1424
- addSwatch(none, null);
1425
- for (const color of FILL_PRESETS) {
1426
- const sw = document.createElement("button");
1427
- sw.type = "button";
1428
- sw.className = "bb-cp-swatch";
1429
- sw.style.background = color;
1430
- sw.title = color;
1431
- addSwatch(sw, color);
1432
- }
1433
- const hexRow = document.createElement("div");
1434
- hexRow.className = "bb-cp-hexrow";
1435
- const hexPreview = document.createElement("span");
1436
- hexPreview.className = "bb-cp-hexpreview";
1437
- const hex = document.createElement("input");
1438
- hex.type = "text";
1439
- hex.className = "bb-cp-hex";
1440
- hex.placeholder = "#RRGGBB";
1441
- hex.addEventListener("input", () => {
1442
- const v = hex.value.trim();
1443
- if (/^#[0-9a-fA-F]{6}$/.test(v)) {
1444
- background = v;
1445
- syncFill();
1681
+ const fillBtn = colorButton({
1682
+ title: "Cell fill",
1683
+ label: "Fill",
1684
+ clearLabel: "No fill",
1685
+ value: () => background,
1686
+ onPick: (c) => {
1687
+ background = c;
1446
1688
  }
1447
1689
  });
1448
- hexRow.append(hexPreview, hex);
1449
- fill.append(swatches, hexRow);
1690
+ fill.append(fillBtn.el);
1450
1691
  const valign = section("Vertical alignment");
1451
1692
  const seg = document.createElement("div");
1452
1693
  seg.className = "bb-cp-seg";
@@ -1476,7 +1717,10 @@ function openCellProperties({ initial, singleCell, onApply }) {
1476
1717
  if (pt === penWidthPt) o.selected = true;
1477
1718
  widthSel.appendChild(o);
1478
1719
  }
1479
- widthSel.addEventListener("change", () => penWidthPt = Number(widthSel.value));
1720
+ widthSel.addEventListener(
1721
+ "change",
1722
+ () => penWidthPt = Number(widthSel.value)
1723
+ );
1480
1724
  const styleSel = document.createElement("select");
1481
1725
  styleSel.className = "bb-cp-select";
1482
1726
  for (const { key, label } of STYLES) {
@@ -1485,24 +1729,19 @@ function openCellProperties({ initial, singleCell, onApply }) {
1485
1729
  o.textContent = label;
1486
1730
  styleSel.appendChild(o);
1487
1731
  }
1488
- styleSel.addEventListener("change", () => penStyle = styleSel.value);
1489
- penRow.append(widthSel, styleSel);
1490
- const colors = document.createElement("div");
1491
- colors.className = "bb-cp-colors";
1492
- const colorEls = [];
1493
- for (const color of PEN_COLORS) {
1494
- const c = document.createElement("button");
1495
- c.type = "button";
1496
- c.className = "bb-cp-color";
1497
- c.style.background = color;
1498
- c.title = color;
1499
- c.addEventListener("click", () => {
1500
- penColor = color;
1501
- syncColors();
1502
- });
1503
- colors.appendChild(c);
1504
- colorEls.push({ el: c, color });
1505
- }
1732
+ styleSel.addEventListener(
1733
+ "change",
1734
+ () => penStyle = styleSel.value
1735
+ );
1736
+ const penBtn = colorButton({
1737
+ title: "Border colour",
1738
+ label: "Colour",
1739
+ value: () => penColor,
1740
+ onPick: (c) => {
1741
+ if (c) penColor = c;
1742
+ }
1743
+ });
1744
+ penRow.append(widthSel, styleSel, penBtn.el);
1506
1745
  const grid = document.createElement("div");
1507
1746
  grid.className = "bb-cp-presets";
1508
1747
  const presetEls = [];
@@ -1521,7 +1760,7 @@ function openCellProperties({ initial, singleCell, onApply }) {
1521
1760
  grid.appendChild(btn);
1522
1761
  presetEls.push({ el: btn, key: p.key });
1523
1762
  }
1524
- borders.append(grid, penRow, colors);
1763
+ borders.append(grid, penRow);
1525
1764
  const footer = document.createElement("div");
1526
1765
  footer.className = "bb-cp-footer";
1527
1766
  const cancel = document.createElement("button");
@@ -1534,23 +1773,14 @@ function openCellProperties({ initial, singleCell, onApply }) {
1534
1773
  apply.textContent = "Apply";
1535
1774
  footer.append(cancel, apply);
1536
1775
  root.append(fill, valign, borders, footer);
1537
- function syncFill() {
1538
- for (const { el, color } of swatchEls) el.classList.toggle("on", color === background);
1539
- hexPreview.style.background = background ?? "transparent";
1540
- if (document.activeElement !== hex) hex.value = background ?? "";
1541
- }
1542
1776
  function syncVAlign() {
1543
1777
  for (const [key, b] of vaBtns) b.classList.toggle("on", vAlign === key);
1544
1778
  }
1545
- function syncColors() {
1546
- for (const { el, color } of colorEls) el.classList.toggle("on", color === penColor);
1547
- }
1548
1779
  function syncPresets() {
1549
- for (const { el, key } of presetEls) el.classList.toggle("on", preset === key);
1780
+ for (const { el: el2, key } of presetEls)
1781
+ el2.classList.toggle("on", preset === key);
1550
1782
  }
1551
- syncFill();
1552
1783
  syncVAlign();
1553
- syncColors();
1554
1784
  syncPresets();
1555
1785
  const dialog = new Dialog({ title: "Cell properties", modal: true });
1556
1786
  dialog.setContent(root);
@@ -1559,7 +1789,12 @@ function openCellProperties({ initial, singleCell, onApply }) {
1559
1789
  apply.addEventListener("click", () => {
1560
1790
  const result = { background, vAlign };
1561
1791
  if (preset) {
1562
- result.border = { preset, width: penWidthPt * 96 / 72, style: penStyle, color: penColor };
1792
+ result.border = {
1793
+ preset,
1794
+ width: penWidthPt * 96 / 72,
1795
+ style: penStyle,
1796
+ color: penColor
1797
+ };
1563
1798
  }
1564
1799
  onApply(result);
1565
1800
  dialog.close();
@@ -1568,15 +1803,15 @@ function openCellProperties({ initial, singleCell, onApply }) {
1568
1803
  }
1569
1804
 
1570
1805
  // packages/ui/src/lib/table-grid.ts
1571
- var STYLE8 = `
1806
+ var STYLE9 = `
1572
1807
  .bb-grid{display:flex;flex-direction:column;gap:6px;user-select:none}
1573
1808
  .bb-grid-cells{display:grid;gap:2px}
1574
- .bb-grid-cell{width:15px;height:15px;border:1px solid var(--bb-ui-border,#d8d6cf);border-radius:2px;background:var(--bb-ui-bg,#fff);cursor:pointer;padding:0}
1809
+ .bb-grid-cell{width:15px;height:15px;border:1px solid var(--bb-ui-control-border,var(--bb-ui-border,#d8d6cf));border-radius:2px;background:var(--bb-ui-bg,#fff);cursor:pointer;padding:0}
1575
1810
  .bb-grid-cell.on{background:var(--bb-ui-active-bg,#e6f1fb);border-color:var(--bb-ui-active-border,#7fb2ec)}
1576
1811
  .bb-grid-label{font-size:12px;text-align:center;opacity:.7}
1577
1812
  `;
1578
1813
  function tableGridPicker(options) {
1579
- injectStyle("bb-ui-grid-styles", STYLE8);
1814
+ injectStyle("bb-ui-grid-styles", STYLE9);
1580
1815
  const maxRows = options.maxRows ?? 8;
1581
1816
  const maxCols = options.maxCols ?? 10;
1582
1817
  const root = document.createElement("div");
@@ -1619,7 +1854,7 @@ var cmToPx = (cm) => Math.round(cm * PX_PER_CM);
1619
1854
  function fmtCm(cm) {
1620
1855
  return `${Number(cm.toFixed(3))} cm`;
1621
1856
  }
1622
- var STYLE9 = `
1857
+ var STYLE10 = `
1623
1858
  .bb-ps{display:flex;flex-direction:column;min-width:250px;max-height:min(70vh,460px);overflow-y:auto}
1624
1859
  .bb-ps-row{display:flex;align-items:center;gap:11px;width:100%;padding:7px 10px;border:0;border-radius:6px;background:transparent;color:inherit;font:inherit;text-align:left;cursor:pointer}
1625
1860
  .bb-ps-row:hover,.bb-ps-row:focus{background:var(--bb-ui-hover,#f1efe8);outline:none}
@@ -1634,15 +1869,15 @@ var STYLE9 = `
1634
1869
  .bb-pd-fields{display:grid;gap:10px 18px}
1635
1870
  .bb-pd-field{display:flex;align-items:center;gap:8px}
1636
1871
  .bb-pd-label{flex:0 0 74px;font-size:13px}
1637
- .bb-pd-input{flex:1 1 auto;min-width:0;width:100%;height:30px;padding:0 8px;border:1px solid var(--bb-ui-border,#d8d6cf);border-radius:6px;background:var(--bb-ui-bg,#fff);color:inherit;font:inherit;font-size:13px}
1872
+ .bb-pd-input{flex:1 1 auto;min-width:0;width:100%;height:30px;padding:0 8px;border:1px 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}
1638
1873
  .bb-pd-input:focus{outline:2px solid var(--bb-ui-active-border,#7fb2ec);outline-offset:-1px}
1639
1874
  .bb-pd-actions{display:flex;justify-content:flex-end;gap:8px}
1640
1875
  `;
1641
1876
  var SVG_NS = "http://www.w3.org/2000/svg";
1642
1877
  function svgEl(name, attrs) {
1643
- const el = document.createElementNS(SVG_NS, name);
1644
- for (const [k, v] of Object.entries(attrs)) el.setAttribute(k, String(v));
1645
- return el;
1878
+ const el2 = document.createElementNS(SVG_NS, name);
1879
+ for (const [k, v] of Object.entries(attrs)) el2.setAttribute(k, String(v));
1880
+ return el2;
1646
1881
  }
1647
1882
  function pagePreview(ratio) {
1648
1883
  const svg = svgEl("svg", { viewBox: "0 0 34 42", class: "bb-ps-icon" });
@@ -1709,11 +1944,11 @@ function presetRow(icon, name, caption, active, onPick) {
1709
1944
  return row;
1710
1945
  }
1711
1946
  function captionLine(text) {
1712
- const el = document.createElement("span");
1713
- el.className = "bb-ps-dims";
1714
- el.style.display = "block";
1715
- el.textContent = text;
1716
- return el;
1947
+ const el2 = document.createElement("span");
1948
+ el2.className = "bb-ps-dims";
1949
+ el2.style.display = "block";
1950
+ el2.textContent = text;
1951
+ return el2;
1717
1952
  }
1718
1953
  function customRow(icon, title, description, onPick) {
1719
1954
  icon.appendChild(
@@ -1734,7 +1969,7 @@ function customRow(icon, title, description, onPick) {
1734
1969
  return presetRow(icon, title, captionLine(description), false, onPick);
1735
1970
  }
1736
1971
  function pageSizePicker(options) {
1737
- injectStyle("bb-ui-pagesetup-styles", STYLE9);
1972
+ injectStyle("bb-ui-pagesetup-styles", STYLE10);
1738
1973
  const root = document.createElement("div");
1739
1974
  root.className = "bb-ps";
1740
1975
  root.setAttribute("role", "menu");
@@ -1763,7 +1998,7 @@ function pageSizePicker(options) {
1763
1998
  return root;
1764
1999
  }
1765
2000
  function orientationPicker(options) {
1766
- injectStyle("bb-ui-pagesetup-styles", STYLE9);
2001
+ injectStyle("bb-ui-pagesetup-styles", STYLE10);
1767
2002
  const root = document.createElement("div");
1768
2003
  root.className = "bb-ps";
1769
2004
  root.setAttribute("role", "menu");
@@ -1786,7 +2021,7 @@ function orientationPicker(options) {
1786
2021
  return root;
1787
2022
  }
1788
2023
  function marginPresetPicker(options) {
1789
- injectStyle("bb-ui-pagesetup-styles", STYLE9);
2024
+ injectStyle("bb-ui-pagesetup-styles", STYLE10);
1790
2025
  const root = document.createElement("div");
1791
2026
  root.className = "bb-ps";
1792
2027
  root.setAttribute("role", "menu");
@@ -1842,8 +2077,8 @@ function cmField(label, valuePx, min) {
1842
2077
  return { row, input };
1843
2078
  }
1844
2079
  function dialogActions() {
1845
- const el = document.createElement("div");
1846
- el.className = "bb-pd-actions";
2080
+ const el2 = document.createElement("div");
2081
+ el2.className = "bb-pd-actions";
1847
2082
  const cancel = document.createElement("button");
1848
2083
  cancel.type = "button";
1849
2084
  cancel.className = "bb-prompt-btn";
@@ -1853,15 +2088,15 @@ function dialogActions() {
1853
2088
  ok.className = "bb-prompt-btn";
1854
2089
  ok.dataset["primary"] = "true";
1855
2090
  ok.textContent = "OK";
1856
- el.append(cancel, ok);
1857
- return { el, ok, cancel };
2091
+ el2.append(cancel, ok);
2092
+ return { el: el2, ok, cancel };
1858
2093
  }
1859
2094
  function readPx(input, fallbackPx) {
1860
2095
  const n = Number(input.value);
1861
2096
  return Number.isFinite(n) && n > 0 ? cmToPx(n) : fallbackPx;
1862
2097
  }
1863
2098
  function openPageSizeDialog(options) {
1864
- injectStyle("bb-ui-pagesetup-styles", STYLE9);
2099
+ injectStyle("bb-ui-pagesetup-styles", STYLE10);
1865
2100
  const dialog = new Dialog({ title: "Paper size", modal: true });
1866
2101
  const form = document.createElement("form");
1867
2102
  form.className = "bb-pd";
@@ -1888,7 +2123,7 @@ function openPageSizeDialog(options) {
1888
2123
  width.input.select();
1889
2124
  }
1890
2125
  function openMarginsDialog(options) {
1891
- injectStyle("bb-ui-pagesetup-styles", STYLE9);
2126
+ injectStyle("bb-ui-pagesetup-styles", STYLE10);
1892
2127
  const dialog = new Dialog({ title: "Margins", modal: true });
1893
2128
  const form = document.createElement("form");
1894
2129
  form.className = "bb-pd";
@@ -1923,10 +2158,341 @@ function openMarginsDialog(options) {
1923
2158
  top.input.focus();
1924
2159
  top.input.select();
1925
2160
  }
2161
+
2162
+ // packages/ui/src/lib/font-dialog.ts
2163
+ var twipsToPt = (tw) => tw / 20;
2164
+ var ptToTwips = (pt) => Math.round(pt * 20);
2165
+ var halfToPt = (hp) => hp / 2;
2166
+ var ptToHalf = (pt) => Math.round(pt * 2);
2167
+ var SCALE_PRESETS = [200, 150, 100, 90, 80, 66, 50, 33];
2168
+ var SUPERSUB_SCALE = 0.66;
2169
+ var STYLE11 = `
2170
+ .bb-fd{display:flex;flex-direction:column;gap:15px;min-width:396px;max-width:430px;color:var(--bb-ui-fg,#2c2c2a)}
2171
+ .bb-fd *{box-sizing:border-box}
2172
+ /* A segmented control, not a "tab joined to its pane": that pattern needs the
2173
+ active tab's background to equal the pane's, and this dialog floats on
2174
+ frosted glass whose colour the widget cannot know. Same shape cell-properties
2175
+ already uses, so the two dialogs read as one product. */
2176
+ .bb-fd-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}
2177
+ .bb-fd-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}
2178
+ .bb-fd-tab:last-child{border-right:0}
2179
+ .bb-fd-tab[aria-selected="true"]{background:var(--bb-ui-active-bg,#e6f1fb);color:var(--bb-ui-active-fg,#0c447c);opacity:1}
2180
+ .bb-fd-pane{display:flex;flex-direction:column;gap:13px}
2181
+ /* A class-level display beats the UA's [hidden]{display:none}; without this
2182
+ both tabs render at once. */
2183
+ .bb-fd-pane[hidden]{display:none}
2184
+ .bb-fd-lbl{font-size:12px;opacity:.7;margin-bottom:5px}
2185
+ .bb-fd-sec{font-size:12px;opacity:.7;margin-bottom:9px}
2186
+ .bb-fd-grid{display:grid;gap:10px}
2187
+ .bb-fd-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}
2188
+ .bb-fd-row{display:grid;grid-template-columns:96px 1fr 88px;gap:10px;align-items:center;margin-bottom:10px}
2189
+ .bb-fd-row label{font-size:13px}
2190
+ .bb-fd-state{font-size:12px;color:var(--bb-ui-active-fg,#0c447c)}
2191
+ .bb-fd-state.off{color:inherit;opacity:.5}
2192
+ .bb-fd-seg{display:inline-flex;border:0.5px solid var(--bb-ui-control-border,var(--bb-ui-border,#d8d6cf));border-radius:6px;overflow:hidden;height:32px}
2193
+ .bb-fd-segbtn{width:40px;border:0;border-right:0.5px solid var(--bb-ui-border,#e3e3e0);background:transparent;color:inherit;font:inherit;font-size:13px;cursor:pointer}
2194
+ .bb-fd-segbtn:last-child{border-right:0}
2195
+ .bb-fd-segbtn[aria-pressed="true"]{background:var(--bb-ui-active-bg,#e6f1fb);color:var(--bb-ui-active-fg,#0c447c)}
2196
+ .bb-fd-segbtn[aria-pressed="mixed"]{background:repeating-linear-gradient(135deg,transparent 0 4px,var(--bb-ui-active-bg,#e6f1fb) 4px 8px)}
2197
+ .bb-fd-swatch-custom{background:conic-gradient(#c0392b,#ba7517,#1d9e75,#185fa5,#7f77dd,#c0392b)}
2198
+ .bb-fd-swatch-custom::-webkit-color-swatch-wrapper{padding:0}
2199
+ .bb-fd-swatch-custom::-webkit-color-swatch{border:0;border-radius:5px;opacity:0}
2200
+ .bb-fd-eff{display:grid;grid-template-columns:1fr 1fr;gap:8px 14px}
2201
+ .bb-fd-cb{display:flex;align-items:center;gap:7px;font-size:13px;cursor:pointer}
2202
+ .bb-fd-prev{border:0.5px solid var(--bb-ui-control-border,var(--bb-ui-border,#d8d6cf));border-radius:8px;background:var(--bb-ui-control-bg,var(--bb-ui-bg,#fff));padding:13px 14px;overflow:hidden}
2203
+ .bb-fd-prev-cap{font-size:11px;opacity:.55;margin-bottom:9px;text-align:center}
2204
+ /* The surrounding sentence stays at a fixed size and weight \u2014 it is the ruler
2205
+ the sample is read against, so it must not move when the sample does. */
2206
+ .bb-fd-prev-line{font-size:15px;line-height:2;text-align:center}
2207
+ .bb-fd-prev-txt{display:inline-block}
2208
+ .bb-fd-dim{opacity:.4}
2209
+ .bb-fd-note{font-size:11px;opacity:.55;margin-top:5px}
2210
+ .bb-fd-foot{display:flex;justify-content:flex-end;gap:8px}
2211
+ .bb-fd-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}
2212
+ .bb-fd-btn.primary{background:var(--bb-ui-active-bg,#e6f1fb);border-color:var(--bb-ui-active-border,#7fb2ec);color:var(--bb-ui-active-fg,#0c447c)}
2213
+ `;
2214
+ function el(tag, cls, text) {
2215
+ const n = document.createElement(tag);
2216
+ if (cls) n.className = cls;
2217
+ if (text) n.textContent = text;
2218
+ return n;
2219
+ }
2220
+ function select(options, value) {
2221
+ const s = el("select", "bb-fd-ctl");
2222
+ for (const [v, label] of options) {
2223
+ const o = document.createElement("option");
2224
+ o.value = v;
2225
+ o.textContent = label;
2226
+ s.append(o);
2227
+ }
2228
+ s.value = value;
2229
+ return s;
2230
+ }
2231
+ function checkbox(label, on) {
2232
+ const wrap = el("label", "bb-fd-cb");
2233
+ const input = document.createElement("input");
2234
+ input.type = "checkbox";
2235
+ input.checked = on === true;
2236
+ input.indeterminate = on === void 0;
2237
+ input.addEventListener("change", () => {
2238
+ input.indeterminate = false;
2239
+ });
2240
+ wrap.append(input, document.createTextNode(label));
2241
+ input.wrap = wrap;
2242
+ return input;
2243
+ }
2244
+ var wrapOf = (i) => i.wrap ?? i;
2245
+ function openFontDialog({
2246
+ initial,
2247
+ families,
2248
+ sizes,
2249
+ onApply
2250
+ }) {
2251
+ injectStyle("bb-font-dialog", STYLE11);
2252
+ const dialog = new Dialog({ title: "Font", modal: true });
2253
+ const root = el("div", "bb-fd");
2254
+ const tabs = el("div", "bb-fd-tabs");
2255
+ tabs.setAttribute("role", "tablist");
2256
+ const fontTab = el("button", "bb-fd-tab", "Font");
2257
+ const advTab = el("button", "bb-fd-tab", "Advanced");
2258
+ for (const t of [fontTab, advTab]) {
2259
+ t.type = "button";
2260
+ t.setAttribute("role", "tab");
2261
+ tabs.append(t);
2262
+ }
2263
+ const fontPane = el("div", "bb-fd-pane");
2264
+ const advPane = el("div", "bb-fd-pane");
2265
+ const showTab = (advanced) => {
2266
+ fontTab.setAttribute("aria-selected", String(!advanced));
2267
+ advTab.setAttribute("aria-selected", String(advanced));
2268
+ fontPane.hidden = advanced;
2269
+ advPane.hidden = !advanced;
2270
+ };
2271
+ fontTab.addEventListener("click", () => showTab(false));
2272
+ advTab.addEventListener("click", () => showTab(true));
2273
+ const famList = families.includes(initial.family ?? "") ? families : initial.family ? [initial.family, ...families] : families;
2274
+ const familySel = select(
2275
+ [["", "Font"], ...famList.map((f) => [f, f])],
2276
+ initial.family ?? ""
2277
+ );
2278
+ const sizeSel = select(
2279
+ [["", "Size"], ...sizes.map((n) => [String(n), String(n)])],
2280
+ initial.sizePt == null ? "" : String(initial.sizePt)
2281
+ );
2282
+ const topGrid = el("div", "bb-fd-grid");
2283
+ topGrid.style.gridTemplateColumns = "1fr 100px";
2284
+ const famCell = el("div");
2285
+ famCell.append(el("div", "bb-fd-lbl", "Font"), familySel);
2286
+ const sizeCell = el("div");
2287
+ sizeCell.append(el("div", "bb-fd-lbl", "Size"), sizeSel);
2288
+ topGrid.append(famCell, sizeCell);
2289
+ const seg = el("div", "bb-fd-seg");
2290
+ const mkSeg = (text, on, style) => {
2291
+ const b = el("button", "bb-fd-segbtn", text);
2292
+ b.type = "button";
2293
+ b.setAttribute("aria-pressed", on === void 0 ? "mixed" : String(on));
2294
+ if (style) b.setAttribute("style", style);
2295
+ b.addEventListener("click", () => {
2296
+ b.setAttribute(
2297
+ "aria-pressed",
2298
+ b.getAttribute("aria-pressed") === "true" ? "false" : "true"
2299
+ );
2300
+ paint();
2301
+ });
2302
+ seg.append(b);
2303
+ return b;
2304
+ };
2305
+ const boldBtn = mkSeg("B", initial.bold, "font-weight:600");
2306
+ const italicBtn = mkSeg("I", initial.italic, "font-style:italic");
2307
+ const underBtn = mkSeg("U", initial.underline, "text-decoration:underline");
2308
+ let color = initial.color ?? null;
2309
+ let highlight = initial.highlight ?? null;
2310
+ const colorBtn = colorButton({
2311
+ title: "Text color",
2312
+ label: "Text color",
2313
+ clearLabel: "Automatic",
2314
+ value: () => color,
2315
+ onPick: (c) => {
2316
+ color = c;
2317
+ paint();
2318
+ }
2319
+ });
2320
+ const hlBtn = colorButton({
2321
+ title: "Highlight",
2322
+ label: "Highlight",
2323
+ clearLabel: "No highlight",
2324
+ value: () => highlight,
2325
+ onPick: (c) => {
2326
+ highlight = c;
2327
+ paint();
2328
+ }
2329
+ });
2330
+ const styleRow = el("div", "bb-fd-grid");
2331
+ styleRow.style.gridTemplateColumns = "auto 1fr 1fr";
2332
+ styleRow.style.alignItems = "end";
2333
+ const styleCell = el("div");
2334
+ styleCell.append(el("div", "bb-fd-lbl", "Style"), seg);
2335
+ const colorCell = el("div");
2336
+ colorCell.append(el("div", "bb-fd-lbl", "Colour"), colorBtn.el);
2337
+ const hlCell = el("div");
2338
+ hlCell.append(el("div", "bb-fd-lbl", "Highlight"), hlBtn.el);
2339
+ styleRow.append(styleCell, colorCell, hlCell);
2340
+ const effWrap = el("div");
2341
+ effWrap.append(el("div", "bb-fd-sec", "Effects"));
2342
+ const effGrid = el("div", "bb-fd-eff");
2343
+ const strikeCb = checkbox("Strikethrough", initial.strike);
2344
+ const dstrikeCb = checkbox("Double strikethrough", initial.doubleStrike);
2345
+ const smallCapsCb = checkbox("Small caps", initial.smallCaps);
2346
+ const supCb = checkbox("Superscript", initial.vertAlign === "super");
2347
+ const subCb = checkbox("Subscript", initial.vertAlign === "sub");
2348
+ const exclusive = (a, b) => {
2349
+ a.addEventListener("change", () => {
2350
+ if (a.checked) b.checked = false;
2351
+ paint();
2352
+ });
2353
+ };
2354
+ exclusive(strikeCb, dstrikeCb);
2355
+ exclusive(dstrikeCb, strikeCb);
2356
+ exclusive(supCb, subCb);
2357
+ exclusive(subCb, supCb);
2358
+ smallCapsCb.addEventListener("change", () => paint());
2359
+ effGrid.append(
2360
+ wrapOf(strikeCb),
2361
+ wrapOf(dstrikeCb),
2362
+ wrapOf(supCb),
2363
+ wrapOf(subCb),
2364
+ wrapOf(smallCapsCb)
2365
+ );
2366
+ effWrap.append(effGrid);
2367
+ fontPane.append(topGrid, styleRow, effWrap);
2368
+ const advWrap = el("div");
2369
+ advWrap.append(el("div", "bb-fd-sec", "Character spacing"));
2370
+ const mkRow = (label, control, stateText, dim = false) => {
2371
+ const row = el("div", "bb-fd-row");
2372
+ if (dim) row.classList.add("bb-fd-dim");
2373
+ const l = el("label", void 0, label);
2374
+ const st = el("span", "bb-fd-state off", stateText);
2375
+ row.append(l, control, st);
2376
+ advWrap.append(row);
2377
+ return st;
2378
+ };
2379
+ const scaleSel = select(
2380
+ SCALE_PRESETS.map((n) => [String(n), `${n}%`]),
2381
+ String(initial.scalePercent)
2382
+ );
2383
+ const scaleState = mkRow("Scale", scaleSel, "");
2384
+ const spacingInput = el("input", "bb-fd-ctl");
2385
+ spacingInput.type = "number";
2386
+ spacingInput.step = "0.05";
2387
+ spacingInput.value = String(twipsToPt(initial.letterSpacingTwips));
2388
+ const spacingState = mkRow("Spacing", spacingInput, "");
2389
+ const positionInput = el("input", "bb-fd-ctl");
2390
+ positionInput.type = "number";
2391
+ positionInput.step = "0.5";
2392
+ positionInput.value = String(halfToPt(initial.positionHalfPoints));
2393
+ const positionState = mkRow("Position", positionInput, "");
2394
+ const kernInput = el("input", "bb-fd-ctl");
2395
+ kernInput.type = "number";
2396
+ kernInput.value = "12";
2397
+ kernInput.disabled = true;
2398
+ mkRow("Kerning", kernInput, "not supported", true);
2399
+ const kernNote = el(
2400
+ "div",
2401
+ "bb-fd-note",
2402
+ "Kerning is read from the file but not reproduced when drawing, so it is shown here rather than silently dropped."
2403
+ );
2404
+ advWrap.append(kernNote);
2405
+ advPane.append(advWrap);
2406
+ const prev = el("div", "bb-fd-prev");
2407
+ const prevLine = el("div", "bb-fd-prev-line");
2408
+ const prevTxt = el("span", "bb-fd-prev-txt", "The quick brown fox");
2409
+ prevLine.append(
2410
+ document.createTextNode("Text before "),
2411
+ prevTxt,
2412
+ document.createTextNode(" and text after.")
2413
+ );
2414
+ prev.append(el("div", "bb-fd-prev-cap", "Preview"), prevLine);
2415
+ const num = (i) => {
2416
+ const v = Number(i.value);
2417
+ return Number.isFinite(v) ? v : 0;
2418
+ };
2419
+ const pressed = (b) => b.getAttribute("aria-pressed") === "true";
2420
+ const pressedOrMixed = (b) => b.getAttribute("aria-pressed") === "mixed" ? void 0 : pressed(b);
2421
+ const checkedOrMixed = (i) => i.indeterminate ? void 0 : i.checked;
2422
+ function paint() {
2423
+ const spacingPt = num(spacingInput);
2424
+ const positionPt = num(positionInput);
2425
+ const scale = Number(scaleSel.value) || 100;
2426
+ scaleState.textContent = scale === 100 ? "normal" : `${scale}%`;
2427
+ scaleState.classList.toggle("off", scale === 100);
2428
+ spacingState.textContent = spacingPt === 0 ? "normal" : spacingPt > 0 ? "expanded" : "condensed";
2429
+ spacingState.classList.toggle("off", spacingPt === 0);
2430
+ positionState.textContent = positionPt === 0 ? "on baseline" : positionPt > 0 ? "raised" : "lowered";
2431
+ positionState.classList.toggle("off", positionPt === 0);
2432
+ const deco = [
2433
+ pressed(underBtn) ? "underline" : "",
2434
+ strikeCb.checked || dstrikeCb.checked ? "line-through" : ""
2435
+ ].filter(Boolean).join(" ");
2436
+ const s = prevTxt.style;
2437
+ s.fontFamily = familySel.value || "inherit";
2438
+ const shrink = supCb.checked || subCb.checked ? SUPERSUB_SCALE : 1;
2439
+ s.fontSize = sizeSel.value ? `${Number(sizeSel.value) * shrink}pt` : `${15 * shrink}px`;
2440
+ s.fontWeight = pressed(boldBtn) ? "700" : "400";
2441
+ s.fontStyle = pressed(italicBtn) ? "italic" : "normal";
2442
+ s.textDecoration = deco || "none";
2443
+ s.textDecorationStyle = dstrikeCb.checked ? "double" : "solid";
2444
+ s.fontVariant = smallCapsCb.checked ? "small-caps" : "normal";
2445
+ s.color = color ?? "inherit";
2446
+ s.background = highlight ?? "transparent";
2447
+ s.letterSpacing = `${spacingPt}pt`;
2448
+ s.transform = scale === 100 ? "none" : `scaleX(${scale / 100})`;
2449
+ s.verticalAlign = supCb.checked ? "super" : subCb.checked ? "sub" : `${positionPt}pt`;
2450
+ }
2451
+ for (const c of [familySel, sizeSel, scaleSel])
2452
+ c.addEventListener("change", paint);
2453
+ for (const i of [spacingInput, positionInput])
2454
+ i.addEventListener("input", paint);
2455
+ const foot = el("div", "bb-fd-foot");
2456
+ const cancel = el("button", "bb-fd-btn", "Cancel");
2457
+ const ok = el("button", "bb-fd-btn primary", "OK");
2458
+ cancel.type = ok.type = "button";
2459
+ cancel.addEventListener("click", () => dialog.close());
2460
+ ok.addEventListener("click", () => {
2461
+ const spacingPt = num(spacingInput);
2462
+ const positionPt = num(positionInput);
2463
+ const scale = Number(scaleSel.value) || 100;
2464
+ onApply({
2465
+ family: familySel.value || void 0,
2466
+ sizePt: sizeSel.value ? Number(sizeSel.value) : void 0,
2467
+ bold: pressedOrMixed(boldBtn),
2468
+ italic: pressedOrMixed(italicBtn),
2469
+ underline: pressedOrMixed(underBtn),
2470
+ strike: checkedOrMixed(strikeCb),
2471
+ doubleStrike: checkedOrMixed(dstrikeCb),
2472
+ smallCaps: checkedOrMixed(smallCapsCb),
2473
+ vertAlign: supCb.checked ? "super" : subCb.checked ? "sub" : null,
2474
+ color,
2475
+ highlight,
2476
+ scalePercent: scale,
2477
+ letterSpacingTwips: ptToTwips(spacingPt),
2478
+ positionHalfPoints: ptToHalf(positionPt)
2479
+ });
2480
+ dialog.close();
2481
+ });
2482
+ foot.append(cancel, ok);
2483
+ root.append(tabs, fontPane, advPane, prev, foot);
2484
+ dialog.body.append(root);
2485
+ dialog.onClose(() => dialog.destroy());
2486
+ showTab(false);
2487
+ paint();
2488
+ dialog.open();
2489
+ }
1926
2490
  // Annotate the CommonJS export names for ESM import in node:
1927
2491
  0 && (module.exports = {
2492
+ COLOR_PALETTE,
1928
2493
  Dialog,
1929
2494
  cmToPx,
2495
+ colorButton,
1930
2496
  createFindDialog,
1931
2497
  defaultMenus,
1932
2498
  defaultToolbarGroups,
@@ -1934,6 +2500,8 @@ function openMarginsDialog(options) {
1934
2500
  mountMenubar,
1935
2501
  mountToolbar,
1936
2502
  openCellProperties,
2503
+ openColorPicker,
2504
+ openFontDialog,
1937
2505
  openMarginsDialog,
1938
2506
  openPageSizeDialog,
1939
2507
  orientationPicker,