@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.js CHANGED
@@ -1,10 +1,24 @@
1
1
  // packages/ui/src/lib/internal.ts
2
2
  function injectStyle(id, css) {
3
3
  if (document.getElementById(id)) return;
4
- const el = document.createElement("style");
5
- el.id = id;
6
- el.textContent = css;
7
- document.head.appendChild(el);
4
+ const el2 = document.createElement("style");
5
+ el2.id = id;
6
+ el2.textContent = css;
7
+ document.head.appendChild(el2);
8
+ }
9
+ function placeFloating(el2, anchor) {
10
+ const r = el2.getBoundingClientRect();
11
+ const pad = 6;
12
+ const gap = 6;
13
+ const x = Math.max(
14
+ pad,
15
+ Math.min(anchor.x, window.innerWidth - r.width - pad)
16
+ );
17
+ const below = anchor.y + anchor.height + gap;
18
+ const above = anchor.y - gap - r.height;
19
+ const y = below + r.height <= window.innerHeight - pad || above < pad ? Math.min(below, window.innerHeight - r.height - pad) : above;
20
+ el2.style.left = `${x}px`;
21
+ el2.style.top = `${y}px`;
8
22
  }
9
23
 
10
24
  // packages/ui/src/lib/dialog.ts
@@ -20,9 +34,9 @@ var STYLE = `
20
34
  .bb-dialog-close:hover{opacity:1;background:var(--bb-ui-hover,#f1efe8)}
21
35
  .bb-dialog-body{padding:12px}
22
36
  .bb-prompt{display:flex;flex-direction:column;gap:10px;min-width:280px}
23
- .bb-prompt-input{height:30px;padding:0 9px;border:1px solid var(--bb-ui-border,#d8d6cf);border-radius:6px;font:inherit;font-size:13px}
37
+ .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}
24
38
  .bb-prompt-actions{display:flex;justify-content:flex-end;gap:8px}
25
- .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}
39
+ .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}
26
40
  .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)}
27
41
  .bb-prompt-btn:hover{filter:brightness(.97)}
28
42
  `;
@@ -46,8 +60,8 @@ var Dialog = class {
46
60
  injectStyle("bb-ui-dialog-styles", STYLE);
47
61
  this.modal = options.modal ?? false;
48
62
  this.anchor = options.anchor;
49
- const el = document.createElement("dialog");
50
- el.className = `bb-dialog ${this.modal ? "bb-dialog-modal" : "bb-dialog-float"}` + (options.className ? ` ${options.className}` : "");
63
+ const el2 = document.createElement("dialog");
64
+ el2.className = `bb-dialog ${this.modal ? "bb-dialog-modal" : "bb-dialog-float"}` + (options.className ? ` ${options.className}` : "");
51
65
  const header = document.createElement("div");
52
66
  header.className = "bb-dialog-header";
53
67
  this.title = document.createElement("span");
@@ -62,18 +76,18 @@ var Dialog = class {
62
76
  header.append(this.title, close);
63
77
  this.body = document.createElement("div");
64
78
  this.body.className = "bb-dialog-body";
65
- el.append(header, this.body);
66
- document.body.appendChild(el);
67
- el.addEventListener("close", () => {
79
+ el2.append(header, this.body);
80
+ document.body.appendChild(el2);
81
+ el2.addEventListener("close", () => {
68
82
  this.detachReposition();
69
83
  this.closeListeners.forEach((cb) => cb());
70
84
  });
71
85
  if (!this.modal) {
72
- el.addEventListener("keydown", (e) => {
86
+ el2.addEventListener("keydown", (e) => {
73
87
  if (e.key === "Escape") this.close();
74
88
  });
75
89
  }
76
- this.el = el;
90
+ this.el = el2;
77
91
  }
78
92
  attachReposition() {
79
93
  window.addEventListener("scroll", this.reposition, true);
@@ -158,6 +172,327 @@ function promptDialog(options) {
158
172
  });
159
173
  }
160
174
 
175
+ // packages/ui/src/lib/color-picker.ts
176
+ var COLOR_PALETTE = [
177
+ [
178
+ "#000000",
179
+ "#434343",
180
+ "#666666",
181
+ "#999999",
182
+ "#B7B7B7",
183
+ "#CCCCCC",
184
+ "#D9D9D9",
185
+ "#EFEFEF",
186
+ "#F3F3F3",
187
+ "#FFFFFF"
188
+ ],
189
+ [
190
+ "#980000",
191
+ "#FF0000",
192
+ "#FF9900",
193
+ "#FFFF00",
194
+ "#00FF00",
195
+ "#00FFFF",
196
+ "#4A86E8",
197
+ "#0000FF",
198
+ "#9900FF",
199
+ "#FF00FF"
200
+ ],
201
+ [
202
+ "#E6B8AF",
203
+ "#F4CCCC",
204
+ "#FCE5CD",
205
+ "#FFF2CC",
206
+ "#D9EAD3",
207
+ "#D0E0E3",
208
+ "#C9DAF8",
209
+ "#CFE2F3",
210
+ "#D9D2E9",
211
+ "#EAD1DC"
212
+ ],
213
+ [
214
+ "#DD7E6B",
215
+ "#EA9999",
216
+ "#F9CB9C",
217
+ "#FFE599",
218
+ "#B6D7A8",
219
+ "#A2C4C9",
220
+ "#A4C2F4",
221
+ "#9FC5E8",
222
+ "#B4A7D6",
223
+ "#D5A6BD"
224
+ ],
225
+ [
226
+ "#CC4125",
227
+ "#E06666",
228
+ "#F6B26B",
229
+ "#FFD966",
230
+ "#93C47D",
231
+ "#76A5AF",
232
+ "#6D9EEB",
233
+ "#6FA8DC",
234
+ "#8E7CC3",
235
+ "#C27BA0"
236
+ ],
237
+ [
238
+ "#A61C00",
239
+ "#CC0000",
240
+ "#E69138",
241
+ "#F1C232",
242
+ "#6AA84F",
243
+ "#45818E",
244
+ "#3C78D8",
245
+ "#3D85C6",
246
+ "#674EA7",
247
+ "#A64D79"
248
+ ],
249
+ [
250
+ "#85200C",
251
+ "#990000",
252
+ "#B45F06",
253
+ "#BF9000",
254
+ "#38761D",
255
+ "#134F5C",
256
+ "#1155CC",
257
+ "#0B5394",
258
+ "#351C75",
259
+ "#741B47"
260
+ ],
261
+ [
262
+ "#5B0F00",
263
+ "#660000",
264
+ "#783F04",
265
+ "#7F6000",
266
+ "#274E13",
267
+ "#0C343D",
268
+ "#1C4587",
269
+ "#073763",
270
+ "#20124D",
271
+ "#4C1130"
272
+ ]
273
+ ];
274
+ var customColors = [];
275
+ var MAX_CUSTOM = 8;
276
+ function remember(color) {
277
+ const at = customColors.indexOf(color);
278
+ if (at !== -1) customColors.splice(at, 1);
279
+ customColors.unshift(color);
280
+ if (customColors.length > MAX_CUSTOM) customColors.length = MAX_CUSTOM;
281
+ }
282
+ var isHex = (v) => /^#[0-9a-fA-F]{6}$/.test(v);
283
+ var STYLE2 = `
284
+ .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}
285
+ .bb-cpk *{box-sizing:border-box}
286
+ .bb-cpk-grid{display:grid;grid-template-columns:repeat(10,20px);gap:5px}
287
+ .bb-cpk-sw{width:20px;height:20px;padding:0;border-radius:50%;border:1px solid rgba(128,128,128,.35);cursor:pointer}
288
+ .bb-cpk-sw:focus-visible{outline:none}
289
+ .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)}
290
+ .bb-cpk-cap{font-size:11px;letter-spacing:.04em;opacity:.6;margin:13px 0 7px}
291
+ .bb-cpk-row{display:flex;gap:6px;align-items:center;flex-wrap:wrap}
292
+ .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}
293
+ .bb-cpk-none:hover,.bb-cpk-none:focus-visible{background:var(--bb-ui-hover,#f1efe8);outline:none}
294
+ .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}
295
+ .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}
296
+ .bb-cpk-plus input{position:absolute;inset:0;opacity:0;cursor:pointer;border:0;padding:0}
297
+ .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)}
298
+
299
+ .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}
300
+ .bb-cpk-btn-chip{width:14px;height:14px;border-radius:50%;border:1px solid rgba(128,128,128,.4);flex:none}
301
+ .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}
302
+ .bb-cpk-btn-caret{opacity:.5;font-size:10px}
303
+ /* Toolbar shape: a glyph with the colour as a bar beneath it. */
304
+ .bb-cpk-btn.glyphed{flex-direction:column;gap:1px;min-width:30px;padding:0 6px;border-color:transparent;background:transparent}
305
+ .bb-cpk-btn.glyphed:hover{background:var(--bb-ui-hover,#f1efe8)}
306
+ .bb-cpk-btn-glyph{font-size:13px;line-height:1}
307
+ .bb-cpk-btn-bar{width:16px;height:4px;border-radius:1px;border:0.5px solid rgba(128,128,128,.35)}
308
+ .bb-cpk::backdrop{background:transparent}
309
+ `;
310
+ function openColorPicker({
311
+ anchor,
312
+ value,
313
+ clearLabel,
314
+ onPick
315
+ }) {
316
+ injectStyle("bb-ui-colorpicker-styles", STYLE2);
317
+ const panel = document.createElement("dialog");
318
+ panel.className = "bb-cpk";
319
+ panel.setAttribute("aria-label", "Colour");
320
+ let closed = false;
321
+ const close = () => {
322
+ if (closed) return;
323
+ closed = true;
324
+ window.removeEventListener("scroll", close, true);
325
+ if (panel.open) panel.close();
326
+ panel.remove();
327
+ anchor.focus();
328
+ };
329
+ const pick = (c) => {
330
+ if (c && !COLOR_PALETTE.some((row) => row.includes(c))) remember(c);
331
+ onPick(c);
332
+ close();
333
+ };
334
+ if (clearLabel !== void 0) {
335
+ const none = document.createElement("button");
336
+ none.type = "button";
337
+ none.className = "bb-cpk-none";
338
+ const chip = document.createElement("span");
339
+ chip.className = "bb-cpk-none-chip";
340
+ chip.textContent = "\u29B8";
341
+ none.append(chip, document.createTextNode(clearLabel));
342
+ none.addEventListener("click", () => pick(null));
343
+ panel.append(none);
344
+ }
345
+ const grid = document.createElement("div");
346
+ grid.className = "bb-cpk-grid";
347
+ grid.setAttribute("role", "grid");
348
+ const cells = [];
349
+ COLOR_PALETTE.forEach((row, r2) => {
350
+ const rowCells = [];
351
+ row.forEach((color, c) => {
352
+ const sw = document.createElement("button");
353
+ sw.type = "button";
354
+ sw.className = "bb-cpk-sw";
355
+ sw.style.background = color;
356
+ sw.title = color;
357
+ sw.setAttribute("role", "gridcell");
358
+ sw.setAttribute("aria-label", color);
359
+ sw.tabIndex = -1;
360
+ if (color.toUpperCase() === value?.toUpperCase()) sw.classList.add("on");
361
+ sw.addEventListener("click", () => pick(color));
362
+ sw.addEventListener("keydown", (e) => onGridKey(e, r2, c));
363
+ grid.append(sw);
364
+ rowCells.push(sw);
365
+ });
366
+ cells.push(rowCells);
367
+ });
368
+ panel.append(grid);
369
+ function onGridKey(e, r2, c) {
370
+ const delta = {
371
+ ArrowRight: [0, 1],
372
+ ArrowLeft: [0, -1],
373
+ ArrowDown: [1, 0],
374
+ ArrowUp: [-1, 0]
375
+ };
376
+ const d = delta[e.key];
377
+ if (!d) return;
378
+ e.preventDefault();
379
+ const rows = cells.length;
380
+ const cols = cells[0].length;
381
+ const nr = Math.min(rows - 1, Math.max(0, r2 + d[0]));
382
+ const nc = Math.min(cols - 1, Math.max(0, c + d[1]));
383
+ cells[r2][c].tabIndex = -1;
384
+ const next = cells[nr][nc];
385
+ next.tabIndex = 0;
386
+ next.focus();
387
+ }
388
+ panel.append(
389
+ Object.assign(document.createElement("div"), {
390
+ className: "bb-cpk-cap",
391
+ textContent: "CUSTOM"
392
+ })
393
+ );
394
+ const customRow2 = document.createElement("div");
395
+ customRow2.className = "bb-cpk-row";
396
+ for (const c of customColors) {
397
+ const sw = document.createElement("button");
398
+ sw.type = "button";
399
+ sw.className = "bb-cpk-sw";
400
+ sw.style.background = c;
401
+ sw.title = c;
402
+ sw.setAttribute("aria-label", c);
403
+ if (c.toUpperCase() === value?.toUpperCase()) sw.classList.add("on");
404
+ sw.addEventListener("click", () => pick(c));
405
+ customRow2.append(sw);
406
+ }
407
+ const plus = document.createElement("button");
408
+ plus.type = "button";
409
+ plus.className = "bb-cpk-plus";
410
+ plus.title = "Custom colour";
411
+ plus.append(document.createTextNode("+"));
412
+ const native = document.createElement("input");
413
+ native.type = "color";
414
+ native.value = value && isHex(value) ? value : "#000000";
415
+ native.addEventListener("input", () => pick(native.value.toUpperCase()));
416
+ plus.append(native);
417
+ customRow2.append(plus);
418
+ const hex = document.createElement("input");
419
+ hex.type = "text";
420
+ hex.className = "bb-cpk-hex";
421
+ hex.placeholder = "#RRGGBB";
422
+ hex.spellcheck = false;
423
+ hex.value = value ?? "";
424
+ hex.addEventListener("keydown", (e) => {
425
+ if (e.key !== "Enter") return;
426
+ e.preventDefault();
427
+ const v = hex.value.trim();
428
+ if (isHex(v)) pick(v.toUpperCase());
429
+ });
430
+ customRow2.append(hex);
431
+ panel.append(customRow2);
432
+ document.body.append(panel);
433
+ panel.showModal();
434
+ const r = anchor.getBoundingClientRect();
435
+ placeFloating(panel, { x: r.left, y: r.top, height: r.height });
436
+ panel.addEventListener("close", close);
437
+ panel.addEventListener("pointerdown", (e) => {
438
+ const b = panel.getBoundingClientRect();
439
+ const outside = e.clientX < b.left || e.clientX > b.right || e.clientY < b.top || e.clientY > b.bottom;
440
+ if (outside) close();
441
+ });
442
+ window.addEventListener("scroll", close, true);
443
+ const start = cells.flat().find((el2) => el2.classList.contains("on")) ?? cells[0][0];
444
+ start.tabIndex = 0;
445
+ start.focus();
446
+ return { close };
447
+ }
448
+ function colorButton(o) {
449
+ injectStyle("bb-ui-colorpicker-styles", STYLE2);
450
+ const el2 = document.createElement("button");
451
+ el2.type = "button";
452
+ el2.className = `bb-cpk-btn${o.glyph ? " glyphed" : ""}`;
453
+ el2.title = o.title;
454
+ el2.setAttribute("aria-label", o.title);
455
+ el2.setAttribute("aria-haspopup", "dialog");
456
+ let chip;
457
+ if (o.glyph) {
458
+ const g = document.createElement("span");
459
+ g.className = "bb-cpk-btn-glyph";
460
+ g.innerHTML = o.glyph;
461
+ chip = document.createElement("span");
462
+ chip.className = "bb-cpk-btn-bar";
463
+ el2.append(g, chip);
464
+ } else {
465
+ chip = document.createElement("span");
466
+ chip.className = "bb-cpk-btn-chip";
467
+ el2.append(chip);
468
+ if (o.label) el2.append(document.createTextNode(o.label));
469
+ const caret = document.createElement("span");
470
+ caret.className = "bb-cpk-btn-caret";
471
+ caret.textContent = "\u25BE";
472
+ el2.append(caret);
473
+ }
474
+ function sync() {
475
+ const v = o.value();
476
+ chip.style.background = v ?? "transparent";
477
+ chip.classList.toggle("none", !v && !o.glyph);
478
+ chip.textContent = !v && !o.glyph ? "\u29B8" : "";
479
+ }
480
+ el2.addEventListener("mousedown", (e) => e.preventDefault());
481
+ el2.addEventListener("click", () => {
482
+ openColorPicker({
483
+ anchor: el2,
484
+ value: o.value(),
485
+ clearLabel: o.clearLabel,
486
+ onPick: (c) => {
487
+ o.onPick(c);
488
+ sync();
489
+ }
490
+ });
491
+ });
492
+ sync();
493
+ return { el: el2, sync };
494
+ }
495
+
161
496
  // packages/ui/src/lib/toolbar.ts
162
497
  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(
163
498
  ([x1, x2], i) => `<line x1="${x1}" y1="${4 + i * 4}" x2="${x2}" y2="${4 + i * 4}"/>`
@@ -228,9 +563,9 @@ var DEFAULT_ITEMS = {
228
563
  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>'
229
564
  }
230
565
  };
231
- var STYLE2 = `
566
+ var STYLE3 = `
232
567
  .bb-toolbar-wrap{position:relative}
233
- .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}
568
+ .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}
234
569
  .bb-toolbar *{box-sizing:border-box}
235
570
  .bb-toolbar-group{display:flex;gap:2px;flex:none}
236
571
  .bb-toolbar-group+.bb-toolbar-group{padding-left:10px;border-left:1px solid var(--bb-ui-border,#e3e3e0)}
@@ -241,16 +576,8 @@ var STYLE2 = `
241
576
  .bb-toolbar-btn:hover{background:var(--bb-ui-hover,#f1efe8)}
242
577
  .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)}
243
578
  .bb-toolbar-btn:disabled{opacity:.38;cursor:default}
244
- .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}
579
+ .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}
245
580
  .bb-toolbar-select:hover{background:var(--bb-ui-hover,#f1efe8)}
246
- .bb-toolbar-color{position:relative}
247
- .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}
248
- .bb-toolbar-color .bb-color-bar{width:16px;height:3px;border-radius:1px;background:currentColor}
249
- .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)}
250
- .bb-color-pop[hidden]{display:none}
251
- .bb-color-swatch{width:16px;height:16px;padding:0;border:1px solid rgba(0,0,0,.18);border-radius:3px;cursor:pointer}
252
- .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}
253
- .bb-color-none:hover{background:var(--bb-ui-hover,#f1efe8)}
254
581
  .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}
255
582
  .bb-toolbar-split{display:flex;align-items:center;gap:0}
256
583
  .bb-toolbar-split .bb-toolbar-btn{min-width:26px;padding:0 5px}
@@ -258,7 +585,7 @@ var STYLE2 = `
258
585
  .bb-split-arrow svg{display:block}
259
586
  .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)}
260
587
  .bb-split-pop[hidden]{display:none}
261
- .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}
588
+ .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}
262
589
  .bb-split-card:hover{background:var(--bb-ui-hover,#f1efe8)}
263
590
  .bb-split-card.is-selected{background:var(--bb-ui-active-bg,#e6f1fb);border-color:var(--bb-ui-active-border,#b5d4f4)}
264
591
  .bb-split-row{display:flex;align-items:center;gap:4px}
@@ -274,7 +601,7 @@ function defaultToolbarGroups(commands) {
274
601
  return [rest, aligns].filter((g) => g.length > 0);
275
602
  }
276
603
  function mountToolbar(host, editor, options = {}) {
277
- injectStyle("bb-ui-toolbar-styles", STYLE2);
604
+ injectStyle("bb-ui-toolbar-styles", STYLE3);
278
605
  const items = { ...DEFAULT_ITEMS, ...options.items ?? {} };
279
606
  const groups = options.groups ?? defaultToolbarGroups(editor.commands);
280
607
  const wrap = document.createElement("div");
@@ -317,77 +644,20 @@ function mountToolbar(host, editor, options = {}) {
317
644
  continue;
318
645
  }
319
646
  if (typeof entry !== "string" && entry.kind === "color") {
320
- const colorWrap = document.createElement("div");
321
- colorWrap.className = "bb-toolbar-color";
322
- const btn2 = document.createElement("button");
323
- btn2.type = "button";
324
- btn2.className = "bb-toolbar-btn";
325
- btn2.title = entry.title;
326
- btn2.setAttribute("aria-label", entry.title);
327
- const glyph = document.createElement("span");
328
- glyph.className = "bb-color-glyph";
329
- glyph.innerHTML = entry.glyph;
330
- const bar = document.createElement("span");
331
- bar.className = "bb-color-bar";
332
- glyph.appendChild(bar);
333
- btn2.appendChild(glyph);
334
- const pop2 = document.createElement("div");
335
- pop2.className = "bb-color-pop";
336
- pop2.hidden = true;
337
- const pick = (color) => {
338
- entry.onSelect(color);
339
- pop2.hidden = true;
340
- editor.focus();
341
- };
342
- for (const c of entry.swatches) {
343
- const sw = document.createElement("button");
344
- sw.type = "button";
345
- sw.className = "bb-color-swatch";
346
- sw.style.background = c;
347
- sw.title = c;
348
- sw.addEventListener("mousedown", (e) => e.preventDefault());
349
- sw.addEventListener("click", () => pick(c));
350
- pop2.appendChild(sw);
351
- }
352
- if (entry.allowNone) {
353
- const none = document.createElement("button");
354
- none.type = "button";
355
- none.className = "bb-color-none";
356
- none.textContent = "None";
357
- none.addEventListener("mousedown", (e) => e.preventDefault());
358
- none.addEventListener("click", () => pick(null));
359
- pop2.appendChild(none);
360
- }
361
- btn2.addEventListener("mousedown", (e) => e.preventDefault());
362
- btn2.addEventListener("click", () => {
363
- const open = pop2.hidden;
364
- wrap.querySelectorAll(".bb-color-pop,.bb-split-pop").forEach((p) => p.hidden = true);
365
- pop2.hidden = !open;
366
- if (!pop2.hidden) {
367
- const wrapRect = wrap.getBoundingClientRect();
368
- const btnRect = btn2.getBoundingClientRect();
369
- pop2.style.top = `${btnRect.bottom - wrapRect.top + 4}px`;
370
- const left = Math.max(
371
- 0,
372
- Math.min(
373
- btnRect.left - wrapRect.left,
374
- wrap.clientWidth - pop2.offsetWidth - 4
375
- )
376
- );
377
- pop2.style.left = `${left}px`;
378
- const onDoc = (ev) => {
379
- if (!colorWrap.contains(ev.target) && !pop2.contains(ev.target)) {
380
- pop2.hidden = true;
381
- document.removeEventListener("pointerdown", onDoc, true);
382
- }
383
- };
384
- document.addEventListener("pointerdown", onDoc, true);
647
+ const ctl = colorButton({
648
+ title: entry.title,
649
+ glyph: entry.glyph,
650
+ clearLabel: entry.clearLabel,
651
+ // `latest`, not `editor.state`: the toolbar mounts before any
652
+ // document exists, and reading state then throws.
653
+ value: () => latest ? entry.value(latest) : null,
654
+ onPick: (color) => {
655
+ entry.onSelect(color);
656
+ editor.focus();
385
657
  }
386
658
  });
387
- colorWrap.appendChild(btn2);
388
- wrap.appendChild(pop2);
389
- groupEl.appendChild(colorWrap);
390
- colors.push({ spec: entry, bar });
659
+ groupEl.appendChild(ctl.el);
660
+ colors.push(ctl);
391
661
  continue;
392
662
  }
393
663
  if (typeof entry !== "string" && entry.kind === "split") {
@@ -449,7 +719,7 @@ function mountToolbar(host, editor, options = {}) {
449
719
  arrow.addEventListener("mousedown", (e) => e.preventDefault());
450
720
  arrow.addEventListener("click", () => {
451
721
  const open = pop2.hidden;
452
- wrap.querySelectorAll(".bb-split-pop,.bb-color-pop").forEach((p) => p.hidden = true);
722
+ wrap.querySelectorAll(".bb-split-pop").forEach((p) => p.hidden = true);
453
723
  pop2.hidden = !open;
454
724
  if (!pop2.hidden) {
455
725
  const wrapRect = wrap.getBoundingClientRect();
@@ -529,11 +799,11 @@ function mountToolbar(host, editor, options = {}) {
529
799
  while (pop.firstChild) root.insertBefore(pop.firstChild, moreBtn);
530
800
  moreBtn.style.display = "none";
531
801
  closePop();
532
- wrap.querySelectorAll(".bb-color-pop,.bb-split-pop").forEach((p) => p.hidden = true);
802
+ wrap.querySelectorAll(".bb-split-pop").forEach((p) => p.hidden = true);
533
803
  if (fits()) return;
534
804
  moreBtn.style.display = "";
535
805
  const groupEls = Array.from(root.children).filter(
536
- (el) => el.classList.contains("bb-toolbar-group")
806
+ (el2) => el2.classList.contains("bb-toolbar-group")
537
807
  );
538
808
  for (let i = groupEls.length - 1; i >= 0 && !fits(); i--) {
539
809
  pop.insertBefore(groupEls[i], pop.firstChild);
@@ -548,17 +818,16 @@ function mountToolbar(host, editor, options = {}) {
548
818
  layout();
549
819
  const refresh = (state) => {
550
820
  latest = state;
551
- for (const { name, el } of buttons) {
821
+ for (const { name, el: el2 } of buttons) {
552
822
  const cmd = editor.commands.get(name);
553
823
  if (!cmd) continue;
554
824
  const active = cmd.isActive?.(state) ?? false;
555
- el.classList.toggle("is-active", active);
556
- el.setAttribute("aria-pressed", String(active));
557
- el.disabled = cmd.isEnabled ? !cmd.isEnabled(state) : false;
825
+ el2.classList.toggle("is-active", active);
826
+ el2.setAttribute("aria-pressed", String(active));
827
+ el2.disabled = cmd.isEnabled ? !cmd.isEnabled(state) : false;
558
828
  }
559
- for (const { spec, el } of selects) el.value = spec.value(state);
560
- for (const { spec, bar } of colors)
561
- bar.style.background = spec.value(state) ?? "";
829
+ for (const { spec, el: el2 } of selects) el2.value = spec.value(state);
830
+ for (const c of colors) c.sync();
562
831
  for (const { spec, cards } of splits) {
563
832
  const selected = spec.value(state);
564
833
  for (const card of cards)
@@ -592,6 +861,8 @@ var DEFAULT_LABELS = {
592
861
  strike: "Strikethrough",
593
862
  superscript: "Superscript",
594
863
  subscript: "Subscript",
864
+ "small-caps": "Small caps",
865
+ "double-strike": "Double strikethrough",
595
866
  "align-left": "Align left",
596
867
  "align-center": "Center",
597
868
  "align-right": "Align right",
@@ -608,8 +879,8 @@ var DEFAULT_LABELS = {
608
879
  redo: "Redo",
609
880
  "page-break": "Page break"
610
881
  };
611
- var STYLE3 = `
612
- .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}
882
+ var STYLE4 = `
883
+ .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}
613
884
  .bb-menubar *{box-sizing:border-box}
614
885
  .bb-menubar-menu{position:relative}
615
886
  .bb-menubar-title{height:28px;padding:0 10px;border:0;border-radius:6px;background:transparent;color:inherit;font:inherit;font-size:13px;cursor:pointer}
@@ -646,7 +917,7 @@ function defaultMenus(commands) {
646
917
  return entries.length ? [{ label: "Format", entries }] : [];
647
918
  }
648
919
  function mountMenubar(host, editor, options = {}) {
649
- injectStyle("bb-ui-menubar-styles", STYLE3);
920
+ injectStyle("bb-ui-menubar-styles", STYLE4);
650
921
  const labels = { ...DEFAULT_LABELS, ...options.labels ?? {} };
651
922
  const menus = options.menus ?? defaultMenus(editor.commands);
652
923
  const vertical = options.orientation === "vertical";
@@ -842,18 +1113,18 @@ var DEFAULT_LABELS2 = {
842
1113
  replaceOne: "Replace",
843
1114
  replaceAll: "Replace all"
844
1115
  };
845
- var STYLE4 = `
1116
+ var STYLE5 = `
846
1117
  .bb-find{display:flex;flex-direction:column;gap:8px;min-width:300px}
847
1118
  .bb-find *{box-sizing:border-box}
848
1119
  .bb-find-row{display:flex;gap:6px;align-items:center}
849
- .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}
1120
+ .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}
850
1121
  .bb-find-count{min-width:44px;text-align:center;font-size:12px;opacity:.65;font-variant-numeric:tabular-nums}
851
- .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}
1122
+ .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}
852
1123
  .bb-find-btn:hover:not(:disabled){background:var(--bb-ui-hover,#f1efe8)}
853
1124
  .bb-find-btn:disabled{opacity:.4;cursor:default}
854
1125
  `;
855
1126
  function createFindDialog(handle, options = {}) {
856
- injectStyle("bb-ui-find-styles", STYLE4);
1127
+ injectStyle("bb-ui-find-styles", STYLE5);
857
1128
  const find = typeof handle === "function" ? new Proxy({}, {
858
1129
  get: (_t, prop) => handle()[prop]
859
1130
  }) : handle;
@@ -954,7 +1225,7 @@ function createFindDialog(handle, options = {}) {
954
1225
  }
955
1226
 
956
1227
  // packages/ui/src/lib/context-menu.ts
957
- var STYLE5 = `
1228
+ var STYLE6 = `
958
1229
  .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)}
959
1230
  .bb-ctx *{box-sizing:border-box}
960
1231
  .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}
@@ -973,17 +1244,17 @@ function closeCurrent() {
973
1244
  }
974
1245
  }
975
1246
  function showContextMenu(entries, at) {
976
- injectStyle("bb-ui-contextmenu-styles", STYLE5);
1247
+ injectStyle("bb-ui-contextmenu-styles", STYLE6);
977
1248
  closeCurrent();
978
- const el = document.createElement("div");
979
- el.className = "bb-ctx";
980
- el.setAttribute("role", "menu");
1249
+ const el2 = document.createElement("div");
1250
+ el2.className = "bb-ctx";
1251
+ el2.setAttribute("role", "menu");
981
1252
  for (const entry of entries) {
982
1253
  if (entry === "separator") {
983
1254
  const sep = document.createElement("div");
984
1255
  sep.className = "bb-ctx-sep";
985
1256
  sep.setAttribute("role", "separator");
986
- el.appendChild(sep);
1257
+ el2.appendChild(sep);
987
1258
  continue;
988
1259
  }
989
1260
  const item = document.createElement("button");
@@ -1006,22 +1277,22 @@ function showContextMenu(entries, at) {
1006
1277
  closeCurrent();
1007
1278
  entry.run();
1008
1279
  });
1009
- el.appendChild(item);
1280
+ el2.appendChild(item);
1010
1281
  }
1011
- document.body.appendChild(el);
1012
- const r = el.getBoundingClientRect();
1282
+ document.body.appendChild(el2);
1283
+ const r = el2.getBoundingClientRect();
1013
1284
  const pad = 4;
1014
1285
  const fitsX = (v) => v >= pad && v + r.width <= window.innerWidth - pad;
1015
1286
  const fitsY = (v) => v >= pad && v + r.height <= window.innerHeight - pad;
1016
1287
  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));
1017
1288
  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));
1018
- el.style.left = `${x}px`;
1019
- el.style.top = `${y}px`;
1289
+ el2.style.left = `${x}px`;
1290
+ el2.style.top = `${y}px`;
1020
1291
  const items = () => Array.from(
1021
- el.querySelectorAll(".bb-ctx-item:not(:disabled)")
1292
+ el2.querySelectorAll(".bb-ctx-item:not(:disabled)")
1022
1293
  );
1023
1294
  const onDown = (e) => {
1024
- if (!el.contains(e.target)) closeCurrent();
1295
+ if (!el2.contains(e.target)) closeCurrent();
1025
1296
  };
1026
1297
  const onKey = (e) => {
1027
1298
  if (e.key === "Escape") return closeCurrent();
@@ -1043,19 +1314,19 @@ function showContextMenu(entries, at) {
1043
1314
  document.removeEventListener("pointerdown", onDown, true);
1044
1315
  document.removeEventListener("keydown", onKey, true);
1045
1316
  window.removeEventListener("scroll", onScroll, true);
1046
- el.remove();
1317
+ el2.remove();
1047
1318
  };
1048
1319
  items()[0]?.focus();
1049
1320
  return { close: closeCurrent };
1050
1321
  }
1051
1322
 
1052
1323
  // packages/ui/src/lib/link-panel.ts
1053
- var STYLE6 = `
1324
+ var STYLE7 = `
1054
1325
  .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}
1055
1326
  .bb-linkpanel *{box-sizing:border-box}
1056
1327
  .bb-linkpanel-row{display:flex;align-items:center;gap:8px}
1057
1328
  .bb-linkpanel-form{display:flex;flex-direction:column;gap:8px}
1058
- .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)}
1329
+ .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))}
1059
1330
  .bb-linkpanel-field:focus-within{border-color:var(--bb-ui-accent,#d85a30)}
1060
1331
  .bb-linkpanel-field svg{flex:none;opacity:.55}
1061
1332
  .bb-linkpanel-input{flex:1;min-width:0;height:30px;border:0;background:transparent;color:inherit;font:inherit;font-size:13px;outline:none}
@@ -1097,31 +1368,17 @@ function closeCurrent2() {
1097
1368
  dispose();
1098
1369
  }
1099
1370
  }
1100
- function place(el, anchor) {
1101
- const r = el.getBoundingClientRect();
1102
- const pad = 6;
1103
- const gap = 6;
1104
- const x = Math.max(
1105
- pad,
1106
- Math.min(anchor.x, window.innerWidth - r.width - pad)
1107
- );
1108
- const below = anchor.y + anchor.height + gap;
1109
- const above = anchor.y - gap - r.height;
1110
- const y = below + r.height <= window.innerHeight - pad || above < pad ? Math.min(below, window.innerHeight - r.height - pad) : above;
1111
- el.style.left = `${x}px`;
1112
- el.style.top = `${y}px`;
1113
- }
1114
1371
  function showLinkPanel(opts) {
1115
- injectStyle("bb-ui-linkpanel-styles", STYLE6);
1372
+ injectStyle("bb-ui-linkpanel-styles", STYLE7);
1116
1373
  if (opts.key && current2 && currentKey === opts.key && currentHandle) {
1117
1374
  cancelPendingClose();
1118
1375
  return currentHandle;
1119
1376
  }
1120
1377
  closeCurrent2();
1121
- const el = document.createElement("div");
1122
- el.className = "bb-linkpanel";
1123
- el.setAttribute("role", "dialog");
1124
- el.setAttribute("aria-label", "Link");
1378
+ const el2 = document.createElement("div");
1379
+ el2.className = "bb-linkpanel";
1380
+ el2.setAttribute("role", "dialog");
1381
+ el2.setAttribute("aria-label", "Link");
1125
1382
  const iconBtn = (svg, label, onClick) => {
1126
1383
  const b = document.createElement("button");
1127
1384
  b.type = "button";
@@ -1148,7 +1405,7 @@ function showLinkPanel(opts) {
1148
1405
  return { wrap, input };
1149
1406
  };
1150
1407
  const renderView = (existing) => {
1151
- el.textContent = "";
1408
+ el2.textContent = "";
1152
1409
  const row = document.createElement("div");
1153
1410
  row.className = "bb-linkpanel-row";
1154
1411
  const internal = opts.internal;
@@ -1195,11 +1452,11 @@ function showLinkPanel(opts) {
1195
1452
  })
1196
1453
  );
1197
1454
  }
1198
- el.appendChild(row);
1199
- place(el, opts.anchor);
1455
+ el2.appendChild(row);
1456
+ placeFloating(el2, opts.anchor);
1200
1457
  };
1201
1458
  const renderForm = (prefill) => {
1202
- el.textContent = "";
1459
+ el2.textContent = "";
1203
1460
  const form = document.createElement("div");
1204
1461
  form.className = "bb-linkpanel-form";
1205
1462
  const withText = !opts.hasSelection;
@@ -1240,16 +1497,16 @@ function showLinkPanel(opts) {
1240
1497
  }
1241
1498
  row.appendChild(apply);
1242
1499
  form.appendChild(row);
1243
- el.appendChild(form);
1244
- place(el, opts.anchor);
1500
+ el2.appendChild(form);
1501
+ placeFloating(el2, opts.anchor);
1245
1502
  u.input.focus();
1246
1503
  u.input.select();
1247
1504
  };
1248
- document.body.appendChild(el);
1505
+ document.body.appendChild(el2);
1249
1506
  if (opts.existing) renderView(opts.existing);
1250
1507
  else renderForm(null);
1251
1508
  const onDown = (e) => {
1252
- if (el.contains(e.target)) return;
1509
+ if (el2.contains(e.target)) return;
1253
1510
  if (opts.key) {
1254
1511
  cancelPendingClose();
1255
1512
  pendingClose = setTimeout(closeCurrent2, 80);
@@ -1270,7 +1527,7 @@ function showLinkPanel(opts) {
1270
1527
  document.removeEventListener("pointerdown", onDown, true);
1271
1528
  document.removeEventListener("keydown", onKey, true);
1272
1529
  window.removeEventListener("scroll", onScroll, true);
1273
- el.remove();
1530
+ el2.remove();
1274
1531
  };
1275
1532
  current2 = dispose;
1276
1533
  currentKey = opts.key ?? null;
@@ -1285,8 +1542,6 @@ function showLinkPanel(opts) {
1285
1542
  }
1286
1543
 
1287
1544
  // packages/ui/src/lib/cell-properties.ts
1288
- var FILL_PRESETS = ["#FCEBEB", "#FAEEDA", "#EAF3DE", "#E6F1FB", "#EEEDFE", "#FBEAF0", "#E1F5EE", "#F1EFE8", "#D3D1C7"];
1289
- var PEN_COLORS = ["#000000", "#5F5E5A", "#B0B0B0", "#E24B4A", "#185FA5", "#1D9E75", "#BA7517"];
1290
1545
  var PT_WIDTHS = [0.5, 0.75, 1, 1.5, 2.25, 3];
1291
1546
  var STYLES = [
1292
1547
  { key: "solid", label: "Solid" },
@@ -1300,43 +1555,53 @@ var VALIGNS = [
1300
1555
  { key: "bottom", label: "Bottom" }
1301
1556
  ];
1302
1557
  var PRESETS = [
1303
- { key: "all", label: "All borders", sides: [1, 1, 1, 1, 1, 1], inside: false },
1558
+ {
1559
+ key: "all",
1560
+ label: "All borders",
1561
+ sides: [1, 1, 1, 1, 1, 1],
1562
+ inside: false
1563
+ },
1304
1564
  { key: "none", label: "No border", sides: [0, 0, 0, 0, 0, 0], inside: false },
1305
- { key: "outside", label: "Outside", sides: [1, 1, 1, 1, 0, 0], inside: false },
1565
+ {
1566
+ key: "outside",
1567
+ label: "Outside",
1568
+ sides: [1, 1, 1, 1, 0, 0],
1569
+ inside: false
1570
+ },
1306
1571
  { key: "inside", label: "Inside", sides: [0, 0, 0, 0, 1, 1], inside: true },
1307
1572
  { key: "top", label: "Top", sides: [1, 0, 0, 0, 0, 0], inside: false },
1308
1573
  { key: "bottom", label: "Bottom", sides: [0, 0, 1, 0, 0, 0], inside: false },
1309
1574
  { key: "left", label: "Left", sides: [0, 0, 0, 1, 0, 0], inside: false },
1310
1575
  { key: "right", label: "Right", sides: [0, 1, 0, 0, 0, 0], inside: false },
1311
- { key: "insideH", label: "Inside horizontal", sides: [0, 0, 0, 0, 1, 0], inside: true },
1312
- { key: "insideV", label: "Inside vertical", sides: [0, 0, 0, 0, 0, 1], inside: true }
1576
+ {
1577
+ key: "insideH",
1578
+ label: "Inside horizontal",
1579
+ sides: [0, 0, 0, 0, 1, 0],
1580
+ inside: true
1581
+ },
1582
+ {
1583
+ key: "insideV",
1584
+ label: "Inside vertical",
1585
+ sides: [0, 0, 0, 0, 0, 1],
1586
+ inside: true
1587
+ }
1313
1588
  ];
1314
- var STYLE7 = `
1589
+ var STYLE8 = `
1315
1590
  .bb-cp{display:flex;flex-direction:column;gap:16px;min-width:320px;color:var(--bb-ui-fg,#2c2c2a)}
1316
1591
  .bb-cp *{box-sizing:border-box}
1317
1592
  .bb-cp-label{font-size:12px;opacity:.7;margin-bottom:8px}
1318
- .bb-cp-swatches{display:flex;flex-wrap:wrap;gap:7px}
1319
- .bb-cp-swatch{width:26px;height:26px;border-radius:6px;border:0.5px solid var(--bb-ui-border,#d8d6cf);padding:0;cursor:pointer}
1320
- .bb-cp-swatch.on{box-shadow:0 0 0 2px var(--bb-ui-active-fg,#0c447c)}
1321
- .bb-cp-none{display:flex;align-items:center;justify-content:center;background:var(--bb-ui-bg,#fff);font-size:15px;color:#b4b2a9}
1322
- .bb-cp-hexrow{display:flex;align-items:center;gap:8px;margin-top:10px}
1323
- .bb-cp-hexpreview{width:26px;height:26px;border-radius:6px;border:0.5px solid var(--bb-ui-border,#d8d6cf);flex:none}
1324
- .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}
1325
- .bb-cp-seg{display:inline-flex;border:0.5px solid var(--bb-ui-border,#d8d6cf);border-radius:6px;overflow:hidden}
1593
+ .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}
1326
1594
  .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}
1327
1595
  .bb-cp-segbtn:last-child{border-right:0}
1328
1596
  .bb-cp-segbtn.on{background:var(--bb-ui-active-bg,#e6f1fb);color:var(--bb-ui-active-fg,#0c447c)}
1329
1597
  .bb-cp-presets{display:grid;grid-template-columns:repeat(5,1fr);gap:6px;margin-bottom:12px}
1330
- .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}
1598
+ .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}
1331
1599
  .bb-cp-preset.on{border-color:var(--bb-ui-active-border,#7fb2ec);background:var(--bb-ui-active-bg,#e6f1fb)}
1332
1600
  .bb-cp-preset:disabled{opacity:.35;cursor:default}
1333
1601
  .bb-cp-penrow{display:flex;gap:10px;margin-bottom:10px}
1334
- .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}
1335
- .bb-cp-colors{display:flex;gap:7px}
1336
- .bb-cp-color{width:24px;height:24px;border-radius:6px;border:0.5px solid var(--bb-ui-border,#d8d6cf);padding:0;cursor:pointer}
1337
- .bb-cp-color.on{box-shadow:0 0 0 2px var(--bb-ui-active-fg,#0c447c)}
1602
+ .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}
1338
1603
  .bb-cp-footer{display:flex;justify-content:flex-end;gap:8px;margin-top:2px}
1339
- .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}
1604
+ .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}
1340
1605
  .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)}
1341
1606
  `;
1342
1607
  var ACTIVE = "#378ADD";
@@ -1351,8 +1616,12 @@ var section = (label) => {
1351
1616
  sec.appendChild(l);
1352
1617
  return sec;
1353
1618
  };
1354
- function openCellProperties({ initial, singleCell, onApply }) {
1355
- injectStyle("bb-ui-cellprops-styles", STYLE7);
1619
+ function openCellProperties({
1620
+ initial,
1621
+ singleCell,
1622
+ onApply
1623
+ }) {
1624
+ injectStyle("bb-ui-cellprops-styles", STYLE8);
1356
1625
  let background = initial.background;
1357
1626
  let vAlign = initial.vAlign;
1358
1627
  let penWidthPt = 1;
@@ -1362,48 +1631,16 @@ function openCellProperties({ initial, singleCell, onApply }) {
1362
1631
  const root = document.createElement("div");
1363
1632
  root.className = "bb-cp";
1364
1633
  const fill = section("Fill");
1365
- const swatches = document.createElement("div");
1366
- swatches.className = "bb-cp-swatches";
1367
- const swatchEls = [];
1368
- const addSwatch = (el, color) => {
1369
- el.addEventListener("click", () => {
1370
- background = color;
1371
- syncFill();
1372
- });
1373
- swatches.appendChild(el);
1374
- swatchEls.push({ el, color });
1375
- };
1376
- const none = document.createElement("button");
1377
- none.type = "button";
1378
- none.className = "bb-cp-swatch bb-cp-none";
1379
- none.title = "No fill";
1380
- none.textContent = "\u29B8";
1381
- addSwatch(none, null);
1382
- for (const color of FILL_PRESETS) {
1383
- const sw = document.createElement("button");
1384
- sw.type = "button";
1385
- sw.className = "bb-cp-swatch";
1386
- sw.style.background = color;
1387
- sw.title = color;
1388
- addSwatch(sw, color);
1389
- }
1390
- const hexRow = document.createElement("div");
1391
- hexRow.className = "bb-cp-hexrow";
1392
- const hexPreview = document.createElement("span");
1393
- hexPreview.className = "bb-cp-hexpreview";
1394
- const hex = document.createElement("input");
1395
- hex.type = "text";
1396
- hex.className = "bb-cp-hex";
1397
- hex.placeholder = "#RRGGBB";
1398
- hex.addEventListener("input", () => {
1399
- const v = hex.value.trim();
1400
- if (/^#[0-9a-fA-F]{6}$/.test(v)) {
1401
- background = v;
1402
- syncFill();
1634
+ const fillBtn = colorButton({
1635
+ title: "Cell fill",
1636
+ label: "Fill",
1637
+ clearLabel: "No fill",
1638
+ value: () => background,
1639
+ onPick: (c) => {
1640
+ background = c;
1403
1641
  }
1404
1642
  });
1405
- hexRow.append(hexPreview, hex);
1406
- fill.append(swatches, hexRow);
1643
+ fill.append(fillBtn.el);
1407
1644
  const valign = section("Vertical alignment");
1408
1645
  const seg = document.createElement("div");
1409
1646
  seg.className = "bb-cp-seg";
@@ -1433,7 +1670,10 @@ function openCellProperties({ initial, singleCell, onApply }) {
1433
1670
  if (pt === penWidthPt) o.selected = true;
1434
1671
  widthSel.appendChild(o);
1435
1672
  }
1436
- widthSel.addEventListener("change", () => penWidthPt = Number(widthSel.value));
1673
+ widthSel.addEventListener(
1674
+ "change",
1675
+ () => penWidthPt = Number(widthSel.value)
1676
+ );
1437
1677
  const styleSel = document.createElement("select");
1438
1678
  styleSel.className = "bb-cp-select";
1439
1679
  for (const { key, label } of STYLES) {
@@ -1442,24 +1682,19 @@ function openCellProperties({ initial, singleCell, onApply }) {
1442
1682
  o.textContent = label;
1443
1683
  styleSel.appendChild(o);
1444
1684
  }
1445
- styleSel.addEventListener("change", () => penStyle = styleSel.value);
1446
- penRow.append(widthSel, styleSel);
1447
- const colors = document.createElement("div");
1448
- colors.className = "bb-cp-colors";
1449
- const colorEls = [];
1450
- for (const color of PEN_COLORS) {
1451
- const c = document.createElement("button");
1452
- c.type = "button";
1453
- c.className = "bb-cp-color";
1454
- c.style.background = color;
1455
- c.title = color;
1456
- c.addEventListener("click", () => {
1457
- penColor = color;
1458
- syncColors();
1459
- });
1460
- colors.appendChild(c);
1461
- colorEls.push({ el: c, color });
1462
- }
1685
+ styleSel.addEventListener(
1686
+ "change",
1687
+ () => penStyle = styleSel.value
1688
+ );
1689
+ const penBtn = colorButton({
1690
+ title: "Border colour",
1691
+ label: "Colour",
1692
+ value: () => penColor,
1693
+ onPick: (c) => {
1694
+ if (c) penColor = c;
1695
+ }
1696
+ });
1697
+ penRow.append(widthSel, styleSel, penBtn.el);
1463
1698
  const grid = document.createElement("div");
1464
1699
  grid.className = "bb-cp-presets";
1465
1700
  const presetEls = [];
@@ -1478,7 +1713,7 @@ function openCellProperties({ initial, singleCell, onApply }) {
1478
1713
  grid.appendChild(btn);
1479
1714
  presetEls.push({ el: btn, key: p.key });
1480
1715
  }
1481
- borders.append(grid, penRow, colors);
1716
+ borders.append(grid, penRow);
1482
1717
  const footer = document.createElement("div");
1483
1718
  footer.className = "bb-cp-footer";
1484
1719
  const cancel = document.createElement("button");
@@ -1491,23 +1726,14 @@ function openCellProperties({ initial, singleCell, onApply }) {
1491
1726
  apply.textContent = "Apply";
1492
1727
  footer.append(cancel, apply);
1493
1728
  root.append(fill, valign, borders, footer);
1494
- function syncFill() {
1495
- for (const { el, color } of swatchEls) el.classList.toggle("on", color === background);
1496
- hexPreview.style.background = background ?? "transparent";
1497
- if (document.activeElement !== hex) hex.value = background ?? "";
1498
- }
1499
1729
  function syncVAlign() {
1500
1730
  for (const [key, b] of vaBtns) b.classList.toggle("on", vAlign === key);
1501
1731
  }
1502
- function syncColors() {
1503
- for (const { el, color } of colorEls) el.classList.toggle("on", color === penColor);
1504
- }
1505
1732
  function syncPresets() {
1506
- for (const { el, key } of presetEls) el.classList.toggle("on", preset === key);
1733
+ for (const { el: el2, key } of presetEls)
1734
+ el2.classList.toggle("on", preset === key);
1507
1735
  }
1508
- syncFill();
1509
1736
  syncVAlign();
1510
- syncColors();
1511
1737
  syncPresets();
1512
1738
  const dialog = new Dialog({ title: "Cell properties", modal: true });
1513
1739
  dialog.setContent(root);
@@ -1516,7 +1742,12 @@ function openCellProperties({ initial, singleCell, onApply }) {
1516
1742
  apply.addEventListener("click", () => {
1517
1743
  const result = { background, vAlign };
1518
1744
  if (preset) {
1519
- result.border = { preset, width: penWidthPt * 96 / 72, style: penStyle, color: penColor };
1745
+ result.border = {
1746
+ preset,
1747
+ width: penWidthPt * 96 / 72,
1748
+ style: penStyle,
1749
+ color: penColor
1750
+ };
1520
1751
  }
1521
1752
  onApply(result);
1522
1753
  dialog.close();
@@ -1525,15 +1756,15 @@ function openCellProperties({ initial, singleCell, onApply }) {
1525
1756
  }
1526
1757
 
1527
1758
  // packages/ui/src/lib/table-grid.ts
1528
- var STYLE8 = `
1759
+ var STYLE9 = `
1529
1760
  .bb-grid{display:flex;flex-direction:column;gap:6px;user-select:none}
1530
1761
  .bb-grid-cells{display:grid;gap:2px}
1531
- .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}
1762
+ .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}
1532
1763
  .bb-grid-cell.on{background:var(--bb-ui-active-bg,#e6f1fb);border-color:var(--bb-ui-active-border,#7fb2ec)}
1533
1764
  .bb-grid-label{font-size:12px;text-align:center;opacity:.7}
1534
1765
  `;
1535
1766
  function tableGridPicker(options) {
1536
- injectStyle("bb-ui-grid-styles", STYLE8);
1767
+ injectStyle("bb-ui-grid-styles", STYLE9);
1537
1768
  const maxRows = options.maxRows ?? 8;
1538
1769
  const maxCols = options.maxCols ?? 10;
1539
1770
  const root = document.createElement("div");
@@ -1576,7 +1807,7 @@ var cmToPx = (cm) => Math.round(cm * PX_PER_CM);
1576
1807
  function fmtCm(cm) {
1577
1808
  return `${Number(cm.toFixed(3))} cm`;
1578
1809
  }
1579
- var STYLE9 = `
1810
+ var STYLE10 = `
1580
1811
  .bb-ps{display:flex;flex-direction:column;min-width:250px;max-height:min(70vh,460px);overflow-y:auto}
1581
1812
  .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}
1582
1813
  .bb-ps-row:hover,.bb-ps-row:focus{background:var(--bb-ui-hover,#f1efe8);outline:none}
@@ -1591,15 +1822,15 @@ var STYLE9 = `
1591
1822
  .bb-pd-fields{display:grid;gap:10px 18px}
1592
1823
  .bb-pd-field{display:flex;align-items:center;gap:8px}
1593
1824
  .bb-pd-label{flex:0 0 74px;font-size:13px}
1594
- .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}
1825
+ .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}
1595
1826
  .bb-pd-input:focus{outline:2px solid var(--bb-ui-active-border,#7fb2ec);outline-offset:-1px}
1596
1827
  .bb-pd-actions{display:flex;justify-content:flex-end;gap:8px}
1597
1828
  `;
1598
1829
  var SVG_NS = "http://www.w3.org/2000/svg";
1599
1830
  function svgEl(name, attrs) {
1600
- const el = document.createElementNS(SVG_NS, name);
1601
- for (const [k, v] of Object.entries(attrs)) el.setAttribute(k, String(v));
1602
- return el;
1831
+ const el2 = document.createElementNS(SVG_NS, name);
1832
+ for (const [k, v] of Object.entries(attrs)) el2.setAttribute(k, String(v));
1833
+ return el2;
1603
1834
  }
1604
1835
  function pagePreview(ratio) {
1605
1836
  const svg = svgEl("svg", { viewBox: "0 0 34 42", class: "bb-ps-icon" });
@@ -1666,11 +1897,11 @@ function presetRow(icon, name, caption, active, onPick) {
1666
1897
  return row;
1667
1898
  }
1668
1899
  function captionLine(text) {
1669
- const el = document.createElement("span");
1670
- el.className = "bb-ps-dims";
1671
- el.style.display = "block";
1672
- el.textContent = text;
1673
- return el;
1900
+ const el2 = document.createElement("span");
1901
+ el2.className = "bb-ps-dims";
1902
+ el2.style.display = "block";
1903
+ el2.textContent = text;
1904
+ return el2;
1674
1905
  }
1675
1906
  function customRow(icon, title, description, onPick) {
1676
1907
  icon.appendChild(
@@ -1691,7 +1922,7 @@ function customRow(icon, title, description, onPick) {
1691
1922
  return presetRow(icon, title, captionLine(description), false, onPick);
1692
1923
  }
1693
1924
  function pageSizePicker(options) {
1694
- injectStyle("bb-ui-pagesetup-styles", STYLE9);
1925
+ injectStyle("bb-ui-pagesetup-styles", STYLE10);
1695
1926
  const root = document.createElement("div");
1696
1927
  root.className = "bb-ps";
1697
1928
  root.setAttribute("role", "menu");
@@ -1720,7 +1951,7 @@ function pageSizePicker(options) {
1720
1951
  return root;
1721
1952
  }
1722
1953
  function orientationPicker(options) {
1723
- injectStyle("bb-ui-pagesetup-styles", STYLE9);
1954
+ injectStyle("bb-ui-pagesetup-styles", STYLE10);
1724
1955
  const root = document.createElement("div");
1725
1956
  root.className = "bb-ps";
1726
1957
  root.setAttribute("role", "menu");
@@ -1743,7 +1974,7 @@ function orientationPicker(options) {
1743
1974
  return root;
1744
1975
  }
1745
1976
  function marginPresetPicker(options) {
1746
- injectStyle("bb-ui-pagesetup-styles", STYLE9);
1977
+ injectStyle("bb-ui-pagesetup-styles", STYLE10);
1747
1978
  const root = document.createElement("div");
1748
1979
  root.className = "bb-ps";
1749
1980
  root.setAttribute("role", "menu");
@@ -1799,8 +2030,8 @@ function cmField(label, valuePx, min) {
1799
2030
  return { row, input };
1800
2031
  }
1801
2032
  function dialogActions() {
1802
- const el = document.createElement("div");
1803
- el.className = "bb-pd-actions";
2033
+ const el2 = document.createElement("div");
2034
+ el2.className = "bb-pd-actions";
1804
2035
  const cancel = document.createElement("button");
1805
2036
  cancel.type = "button";
1806
2037
  cancel.className = "bb-prompt-btn";
@@ -1810,15 +2041,15 @@ function dialogActions() {
1810
2041
  ok.className = "bb-prompt-btn";
1811
2042
  ok.dataset["primary"] = "true";
1812
2043
  ok.textContent = "OK";
1813
- el.append(cancel, ok);
1814
- return { el, ok, cancel };
2044
+ el2.append(cancel, ok);
2045
+ return { el: el2, ok, cancel };
1815
2046
  }
1816
2047
  function readPx(input, fallbackPx) {
1817
2048
  const n = Number(input.value);
1818
2049
  return Number.isFinite(n) && n > 0 ? cmToPx(n) : fallbackPx;
1819
2050
  }
1820
2051
  function openPageSizeDialog(options) {
1821
- injectStyle("bb-ui-pagesetup-styles", STYLE9);
2052
+ injectStyle("bb-ui-pagesetup-styles", STYLE10);
1822
2053
  const dialog = new Dialog({ title: "Paper size", modal: true });
1823
2054
  const form = document.createElement("form");
1824
2055
  form.className = "bb-pd";
@@ -1845,7 +2076,7 @@ function openPageSizeDialog(options) {
1845
2076
  width.input.select();
1846
2077
  }
1847
2078
  function openMarginsDialog(options) {
1848
- injectStyle("bb-ui-pagesetup-styles", STYLE9);
2079
+ injectStyle("bb-ui-pagesetup-styles", STYLE10);
1849
2080
  const dialog = new Dialog({ title: "Margins", modal: true });
1850
2081
  const form = document.createElement("form");
1851
2082
  form.className = "bb-pd";
@@ -1880,9 +2111,340 @@ function openMarginsDialog(options) {
1880
2111
  top.input.focus();
1881
2112
  top.input.select();
1882
2113
  }
2114
+
2115
+ // packages/ui/src/lib/font-dialog.ts
2116
+ var twipsToPt = (tw) => tw / 20;
2117
+ var ptToTwips = (pt) => Math.round(pt * 20);
2118
+ var halfToPt = (hp) => hp / 2;
2119
+ var ptToHalf = (pt) => Math.round(pt * 2);
2120
+ var SCALE_PRESETS = [200, 150, 100, 90, 80, 66, 50, 33];
2121
+ var SUPERSUB_SCALE = 0.66;
2122
+ var STYLE11 = `
2123
+ .bb-fd{display:flex;flex-direction:column;gap:15px;min-width:396px;max-width:430px;color:var(--bb-ui-fg,#2c2c2a)}
2124
+ .bb-fd *{box-sizing:border-box}
2125
+ /* A segmented control, not a "tab joined to its pane": that pattern needs the
2126
+ active tab's background to equal the pane's, and this dialog floats on
2127
+ frosted glass whose colour the widget cannot know. Same shape cell-properties
2128
+ already uses, so the two dialogs read as one product. */
2129
+ .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}
2130
+ .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}
2131
+ .bb-fd-tab:last-child{border-right:0}
2132
+ .bb-fd-tab[aria-selected="true"]{background:var(--bb-ui-active-bg,#e6f1fb);color:var(--bb-ui-active-fg,#0c447c);opacity:1}
2133
+ .bb-fd-pane{display:flex;flex-direction:column;gap:13px}
2134
+ /* A class-level display beats the UA's [hidden]{display:none}; without this
2135
+ both tabs render at once. */
2136
+ .bb-fd-pane[hidden]{display:none}
2137
+ .bb-fd-lbl{font-size:12px;opacity:.7;margin-bottom:5px}
2138
+ .bb-fd-sec{font-size:12px;opacity:.7;margin-bottom:9px}
2139
+ .bb-fd-grid{display:grid;gap:10px}
2140
+ .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}
2141
+ .bb-fd-row{display:grid;grid-template-columns:96px 1fr 88px;gap:10px;align-items:center;margin-bottom:10px}
2142
+ .bb-fd-row label{font-size:13px}
2143
+ .bb-fd-state{font-size:12px;color:var(--bb-ui-active-fg,#0c447c)}
2144
+ .bb-fd-state.off{color:inherit;opacity:.5}
2145
+ .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}
2146
+ .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}
2147
+ .bb-fd-segbtn:last-child{border-right:0}
2148
+ .bb-fd-segbtn[aria-pressed="true"]{background:var(--bb-ui-active-bg,#e6f1fb);color:var(--bb-ui-active-fg,#0c447c)}
2149
+ .bb-fd-segbtn[aria-pressed="mixed"]{background:repeating-linear-gradient(135deg,transparent 0 4px,var(--bb-ui-active-bg,#e6f1fb) 4px 8px)}
2150
+ .bb-fd-swatch-custom{background:conic-gradient(#c0392b,#ba7517,#1d9e75,#185fa5,#7f77dd,#c0392b)}
2151
+ .bb-fd-swatch-custom::-webkit-color-swatch-wrapper{padding:0}
2152
+ .bb-fd-swatch-custom::-webkit-color-swatch{border:0;border-radius:5px;opacity:0}
2153
+ .bb-fd-eff{display:grid;grid-template-columns:1fr 1fr;gap:8px 14px}
2154
+ .bb-fd-cb{display:flex;align-items:center;gap:7px;font-size:13px;cursor:pointer}
2155
+ .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}
2156
+ .bb-fd-prev-cap{font-size:11px;opacity:.55;margin-bottom:9px;text-align:center}
2157
+ /* The surrounding sentence stays at a fixed size and weight \u2014 it is the ruler
2158
+ the sample is read against, so it must not move when the sample does. */
2159
+ .bb-fd-prev-line{font-size:15px;line-height:2;text-align:center}
2160
+ .bb-fd-prev-txt{display:inline-block}
2161
+ .bb-fd-dim{opacity:.4}
2162
+ .bb-fd-note{font-size:11px;opacity:.55;margin-top:5px}
2163
+ .bb-fd-foot{display:flex;justify-content:flex-end;gap:8px}
2164
+ .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}
2165
+ .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)}
2166
+ `;
2167
+ function el(tag, cls, text) {
2168
+ const n = document.createElement(tag);
2169
+ if (cls) n.className = cls;
2170
+ if (text) n.textContent = text;
2171
+ return n;
2172
+ }
2173
+ function select(options, value) {
2174
+ const s = el("select", "bb-fd-ctl");
2175
+ for (const [v, label] of options) {
2176
+ const o = document.createElement("option");
2177
+ o.value = v;
2178
+ o.textContent = label;
2179
+ s.append(o);
2180
+ }
2181
+ s.value = value;
2182
+ return s;
2183
+ }
2184
+ function checkbox(label, on) {
2185
+ const wrap = el("label", "bb-fd-cb");
2186
+ const input = document.createElement("input");
2187
+ input.type = "checkbox";
2188
+ input.checked = on === true;
2189
+ input.indeterminate = on === void 0;
2190
+ input.addEventListener("change", () => {
2191
+ input.indeterminate = false;
2192
+ });
2193
+ wrap.append(input, document.createTextNode(label));
2194
+ input.wrap = wrap;
2195
+ return input;
2196
+ }
2197
+ var wrapOf = (i) => i.wrap ?? i;
2198
+ function openFontDialog({
2199
+ initial,
2200
+ families,
2201
+ sizes,
2202
+ onApply
2203
+ }) {
2204
+ injectStyle("bb-font-dialog", STYLE11);
2205
+ const dialog = new Dialog({ title: "Font", modal: true });
2206
+ const root = el("div", "bb-fd");
2207
+ const tabs = el("div", "bb-fd-tabs");
2208
+ tabs.setAttribute("role", "tablist");
2209
+ const fontTab = el("button", "bb-fd-tab", "Font");
2210
+ const advTab = el("button", "bb-fd-tab", "Advanced");
2211
+ for (const t of [fontTab, advTab]) {
2212
+ t.type = "button";
2213
+ t.setAttribute("role", "tab");
2214
+ tabs.append(t);
2215
+ }
2216
+ const fontPane = el("div", "bb-fd-pane");
2217
+ const advPane = el("div", "bb-fd-pane");
2218
+ const showTab = (advanced) => {
2219
+ fontTab.setAttribute("aria-selected", String(!advanced));
2220
+ advTab.setAttribute("aria-selected", String(advanced));
2221
+ fontPane.hidden = advanced;
2222
+ advPane.hidden = !advanced;
2223
+ };
2224
+ fontTab.addEventListener("click", () => showTab(false));
2225
+ advTab.addEventListener("click", () => showTab(true));
2226
+ const famList = families.includes(initial.family ?? "") ? families : initial.family ? [initial.family, ...families] : families;
2227
+ const familySel = select(
2228
+ [["", "Font"], ...famList.map((f) => [f, f])],
2229
+ initial.family ?? ""
2230
+ );
2231
+ const sizeSel = select(
2232
+ [["", "Size"], ...sizes.map((n) => [String(n), String(n)])],
2233
+ initial.sizePt == null ? "" : String(initial.sizePt)
2234
+ );
2235
+ const topGrid = el("div", "bb-fd-grid");
2236
+ topGrid.style.gridTemplateColumns = "1fr 100px";
2237
+ const famCell = el("div");
2238
+ famCell.append(el("div", "bb-fd-lbl", "Font"), familySel);
2239
+ const sizeCell = el("div");
2240
+ sizeCell.append(el("div", "bb-fd-lbl", "Size"), sizeSel);
2241
+ topGrid.append(famCell, sizeCell);
2242
+ const seg = el("div", "bb-fd-seg");
2243
+ const mkSeg = (text, on, style) => {
2244
+ const b = el("button", "bb-fd-segbtn", text);
2245
+ b.type = "button";
2246
+ b.setAttribute("aria-pressed", on === void 0 ? "mixed" : String(on));
2247
+ if (style) b.setAttribute("style", style);
2248
+ b.addEventListener("click", () => {
2249
+ b.setAttribute(
2250
+ "aria-pressed",
2251
+ b.getAttribute("aria-pressed") === "true" ? "false" : "true"
2252
+ );
2253
+ paint();
2254
+ });
2255
+ seg.append(b);
2256
+ return b;
2257
+ };
2258
+ const boldBtn = mkSeg("B", initial.bold, "font-weight:600");
2259
+ const italicBtn = mkSeg("I", initial.italic, "font-style:italic");
2260
+ const underBtn = mkSeg("U", initial.underline, "text-decoration:underline");
2261
+ let color = initial.color ?? null;
2262
+ let highlight = initial.highlight ?? null;
2263
+ const colorBtn = colorButton({
2264
+ title: "Text color",
2265
+ label: "Text color",
2266
+ clearLabel: "Automatic",
2267
+ value: () => color,
2268
+ onPick: (c) => {
2269
+ color = c;
2270
+ paint();
2271
+ }
2272
+ });
2273
+ const hlBtn = colorButton({
2274
+ title: "Highlight",
2275
+ label: "Highlight",
2276
+ clearLabel: "No highlight",
2277
+ value: () => highlight,
2278
+ onPick: (c) => {
2279
+ highlight = c;
2280
+ paint();
2281
+ }
2282
+ });
2283
+ const styleRow = el("div", "bb-fd-grid");
2284
+ styleRow.style.gridTemplateColumns = "auto 1fr 1fr";
2285
+ styleRow.style.alignItems = "end";
2286
+ const styleCell = el("div");
2287
+ styleCell.append(el("div", "bb-fd-lbl", "Style"), seg);
2288
+ const colorCell = el("div");
2289
+ colorCell.append(el("div", "bb-fd-lbl", "Colour"), colorBtn.el);
2290
+ const hlCell = el("div");
2291
+ hlCell.append(el("div", "bb-fd-lbl", "Highlight"), hlBtn.el);
2292
+ styleRow.append(styleCell, colorCell, hlCell);
2293
+ const effWrap = el("div");
2294
+ effWrap.append(el("div", "bb-fd-sec", "Effects"));
2295
+ const effGrid = el("div", "bb-fd-eff");
2296
+ const strikeCb = checkbox("Strikethrough", initial.strike);
2297
+ const dstrikeCb = checkbox("Double strikethrough", initial.doubleStrike);
2298
+ const smallCapsCb = checkbox("Small caps", initial.smallCaps);
2299
+ const supCb = checkbox("Superscript", initial.vertAlign === "super");
2300
+ const subCb = checkbox("Subscript", initial.vertAlign === "sub");
2301
+ const exclusive = (a, b) => {
2302
+ a.addEventListener("change", () => {
2303
+ if (a.checked) b.checked = false;
2304
+ paint();
2305
+ });
2306
+ };
2307
+ exclusive(strikeCb, dstrikeCb);
2308
+ exclusive(dstrikeCb, strikeCb);
2309
+ exclusive(supCb, subCb);
2310
+ exclusive(subCb, supCb);
2311
+ smallCapsCb.addEventListener("change", () => paint());
2312
+ effGrid.append(
2313
+ wrapOf(strikeCb),
2314
+ wrapOf(dstrikeCb),
2315
+ wrapOf(supCb),
2316
+ wrapOf(subCb),
2317
+ wrapOf(smallCapsCb)
2318
+ );
2319
+ effWrap.append(effGrid);
2320
+ fontPane.append(topGrid, styleRow, effWrap);
2321
+ const advWrap = el("div");
2322
+ advWrap.append(el("div", "bb-fd-sec", "Character spacing"));
2323
+ const mkRow = (label, control, stateText, dim = false) => {
2324
+ const row = el("div", "bb-fd-row");
2325
+ if (dim) row.classList.add("bb-fd-dim");
2326
+ const l = el("label", void 0, label);
2327
+ const st = el("span", "bb-fd-state off", stateText);
2328
+ row.append(l, control, st);
2329
+ advWrap.append(row);
2330
+ return st;
2331
+ };
2332
+ const scaleSel = select(
2333
+ SCALE_PRESETS.map((n) => [String(n), `${n}%`]),
2334
+ String(initial.scalePercent)
2335
+ );
2336
+ const scaleState = mkRow("Scale", scaleSel, "");
2337
+ const spacingInput = el("input", "bb-fd-ctl");
2338
+ spacingInput.type = "number";
2339
+ spacingInput.step = "0.05";
2340
+ spacingInput.value = String(twipsToPt(initial.letterSpacingTwips));
2341
+ const spacingState = mkRow("Spacing", spacingInput, "");
2342
+ const positionInput = el("input", "bb-fd-ctl");
2343
+ positionInput.type = "number";
2344
+ positionInput.step = "0.5";
2345
+ positionInput.value = String(halfToPt(initial.positionHalfPoints));
2346
+ const positionState = mkRow("Position", positionInput, "");
2347
+ const kernInput = el("input", "bb-fd-ctl");
2348
+ kernInput.type = "number";
2349
+ kernInput.value = "12";
2350
+ kernInput.disabled = true;
2351
+ mkRow("Kerning", kernInput, "not supported", true);
2352
+ const kernNote = el(
2353
+ "div",
2354
+ "bb-fd-note",
2355
+ "Kerning is read from the file but not reproduced when drawing, so it is shown here rather than silently dropped."
2356
+ );
2357
+ advWrap.append(kernNote);
2358
+ advPane.append(advWrap);
2359
+ const prev = el("div", "bb-fd-prev");
2360
+ const prevLine = el("div", "bb-fd-prev-line");
2361
+ const prevTxt = el("span", "bb-fd-prev-txt", "The quick brown fox");
2362
+ prevLine.append(
2363
+ document.createTextNode("Text before "),
2364
+ prevTxt,
2365
+ document.createTextNode(" and text after.")
2366
+ );
2367
+ prev.append(el("div", "bb-fd-prev-cap", "Preview"), prevLine);
2368
+ const num = (i) => {
2369
+ const v = Number(i.value);
2370
+ return Number.isFinite(v) ? v : 0;
2371
+ };
2372
+ const pressed = (b) => b.getAttribute("aria-pressed") === "true";
2373
+ const pressedOrMixed = (b) => b.getAttribute("aria-pressed") === "mixed" ? void 0 : pressed(b);
2374
+ const checkedOrMixed = (i) => i.indeterminate ? void 0 : i.checked;
2375
+ function paint() {
2376
+ const spacingPt = num(spacingInput);
2377
+ const positionPt = num(positionInput);
2378
+ const scale = Number(scaleSel.value) || 100;
2379
+ scaleState.textContent = scale === 100 ? "normal" : `${scale}%`;
2380
+ scaleState.classList.toggle("off", scale === 100);
2381
+ spacingState.textContent = spacingPt === 0 ? "normal" : spacingPt > 0 ? "expanded" : "condensed";
2382
+ spacingState.classList.toggle("off", spacingPt === 0);
2383
+ positionState.textContent = positionPt === 0 ? "on baseline" : positionPt > 0 ? "raised" : "lowered";
2384
+ positionState.classList.toggle("off", positionPt === 0);
2385
+ const deco = [
2386
+ pressed(underBtn) ? "underline" : "",
2387
+ strikeCb.checked || dstrikeCb.checked ? "line-through" : ""
2388
+ ].filter(Boolean).join(" ");
2389
+ const s = prevTxt.style;
2390
+ s.fontFamily = familySel.value || "inherit";
2391
+ const shrink = supCb.checked || subCb.checked ? SUPERSUB_SCALE : 1;
2392
+ s.fontSize = sizeSel.value ? `${Number(sizeSel.value) * shrink}pt` : `${15 * shrink}px`;
2393
+ s.fontWeight = pressed(boldBtn) ? "700" : "400";
2394
+ s.fontStyle = pressed(italicBtn) ? "italic" : "normal";
2395
+ s.textDecoration = deco || "none";
2396
+ s.textDecorationStyle = dstrikeCb.checked ? "double" : "solid";
2397
+ s.fontVariant = smallCapsCb.checked ? "small-caps" : "normal";
2398
+ s.color = color ?? "inherit";
2399
+ s.background = highlight ?? "transparent";
2400
+ s.letterSpacing = `${spacingPt}pt`;
2401
+ s.transform = scale === 100 ? "none" : `scaleX(${scale / 100})`;
2402
+ s.verticalAlign = supCb.checked ? "super" : subCb.checked ? "sub" : `${positionPt}pt`;
2403
+ }
2404
+ for (const c of [familySel, sizeSel, scaleSel])
2405
+ c.addEventListener("change", paint);
2406
+ for (const i of [spacingInput, positionInput])
2407
+ i.addEventListener("input", paint);
2408
+ const foot = el("div", "bb-fd-foot");
2409
+ const cancel = el("button", "bb-fd-btn", "Cancel");
2410
+ const ok = el("button", "bb-fd-btn primary", "OK");
2411
+ cancel.type = ok.type = "button";
2412
+ cancel.addEventListener("click", () => dialog.close());
2413
+ ok.addEventListener("click", () => {
2414
+ const spacingPt = num(spacingInput);
2415
+ const positionPt = num(positionInput);
2416
+ const scale = Number(scaleSel.value) || 100;
2417
+ onApply({
2418
+ family: familySel.value || void 0,
2419
+ sizePt: sizeSel.value ? Number(sizeSel.value) : void 0,
2420
+ bold: pressedOrMixed(boldBtn),
2421
+ italic: pressedOrMixed(italicBtn),
2422
+ underline: pressedOrMixed(underBtn),
2423
+ strike: checkedOrMixed(strikeCb),
2424
+ doubleStrike: checkedOrMixed(dstrikeCb),
2425
+ smallCaps: checkedOrMixed(smallCapsCb),
2426
+ vertAlign: supCb.checked ? "super" : subCb.checked ? "sub" : null,
2427
+ color,
2428
+ highlight,
2429
+ scalePercent: scale,
2430
+ letterSpacingTwips: ptToTwips(spacingPt),
2431
+ positionHalfPoints: ptToHalf(positionPt)
2432
+ });
2433
+ dialog.close();
2434
+ });
2435
+ foot.append(cancel, ok);
2436
+ root.append(tabs, fontPane, advPane, prev, foot);
2437
+ dialog.body.append(root);
2438
+ dialog.onClose(() => dialog.destroy());
2439
+ showTab(false);
2440
+ paint();
2441
+ dialog.open();
2442
+ }
1883
2443
  export {
2444
+ COLOR_PALETTE,
1884
2445
  Dialog,
1885
2446
  cmToPx,
2447
+ colorButton,
1886
2448
  createFindDialog,
1887
2449
  defaultMenus,
1888
2450
  defaultToolbarGroups,
@@ -1890,6 +2452,8 @@ export {
1890
2452
  mountMenubar,
1891
2453
  mountToolbar,
1892
2454
  openCellProperties,
2455
+ openColorPicker,
2456
+ openFontDialog,
1893
2457
  openMarginsDialog,
1894
2458
  openPageSizeDialog,
1895
2459
  orientationPicker,