@shadow-garden/bapbong-ui 0.1.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 ADDED
@@ -0,0 +1,1187 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // packages/ui/src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ Dialog: () => Dialog,
24
+ createFindDialog: () => createFindDialog,
25
+ defaultMenus: () => defaultMenus,
26
+ defaultToolbarGroups: () => defaultToolbarGroups,
27
+ mountMenubar: () => mountMenubar,
28
+ mountToolbar: () => mountToolbar,
29
+ openCellProperties: () => openCellProperties,
30
+ promptDialog: () => promptDialog,
31
+ showContextMenu: () => showContextMenu,
32
+ tableGridPicker: () => tableGridPicker
33
+ });
34
+ module.exports = __toCommonJS(index_exports);
35
+
36
+ // packages/ui/src/lib/internal.ts
37
+ function injectStyle(id, css) {
38
+ if (document.getElementById(id)) return;
39
+ const el = document.createElement("style");
40
+ el.id = id;
41
+ el.textContent = css;
42
+ document.head.appendChild(el);
43
+ }
44
+
45
+ // packages/ui/src/lib/dialog.ts
46
+ var STYLE = `
47
+ .bb-dialog{position:fixed;inset:auto;z-index:1100;margin:0;padding:0;border:1px solid var(--bb-ui-pop-border,var(--bb-ui-border,#e3e3e0));border-radius:10px;background:var(--bb-ui-dialog-bg,var(--bb-ui-menu-bg,#fff));-webkit-backdrop-filter:var(--bb-ui-dialog-filter,none);backdrop-filter:var(--bb-ui-dialog-filter,none);color:var(--bb-ui-fg,#2c2c2a);box-shadow:0 12px 40px rgba(0,0,0,.18);font-family:var(--bb-ui-font,system-ui,-apple-system,sans-serif);min-width:280px;max-width:min(92vw,440px)}
48
+ .bb-dialog *{box-sizing:border-box}
49
+ .bb-dialog::backdrop{background:rgba(0,0,0,.28)}
50
+ .bb-dialog-modal{top:50%;left:50%;transform:translate(-50%,-50%)}
51
+ .bb-dialog-float{top:62px;right:24px}
52
+ .bb-dialog-header{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:9px 10px 9px 12px;border-bottom:1px solid var(--bb-ui-border,#e3e3e0)}
53
+ .bb-dialog-title{font-size:13px;font-weight:600}
54
+ .bb-dialog-close{border:0;background:transparent;color:inherit;opacity:.55;cursor:pointer;font-size:14px;width:24px;height:24px;border-radius:5px;line-height:1}
55
+ .bb-dialog-close:hover{opacity:1;background:var(--bb-ui-hover,#f1efe8)}
56
+ .bb-dialog-body{padding:12px}
57
+ .bb-prompt{display:flex;flex-direction:column;gap:10px;min-width:280px}
58
+ .bb-prompt-input{height:30px;padding:0 9px;border:1px solid var(--bb-ui-border,#d8d6cf);border-radius:6px;font:inherit;font-size:13px}
59
+ .bb-prompt-actions{display:flex;justify-content:flex-end;gap:8px}
60
+ .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}
61
+ .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)}
62
+ .bb-prompt-btn:hover{filter:brightness(.97)}
63
+ `;
64
+ var Dialog = class {
65
+ el;
66
+ /** Content slot — put your element(s) here. */
67
+ body;
68
+ title;
69
+ modal;
70
+ anchor;
71
+ closeListeners = /* @__PURE__ */ new Set();
72
+ reposition = () => {
73
+ if (this.modal || !this.anchor) return;
74
+ const r = this.anchor();
75
+ if (!r) return;
76
+ this.el.style.top = `${Math.max(8, r.top + 8)}px`;
77
+ this.el.style.left = "auto";
78
+ this.el.style.right = `${Math.max(8, window.innerWidth - r.right + 8)}px`;
79
+ };
80
+ constructor(options = {}) {
81
+ injectStyle("bb-ui-dialog-styles", STYLE);
82
+ this.modal = options.modal ?? false;
83
+ this.anchor = options.anchor;
84
+ const el = document.createElement("dialog");
85
+ el.className = `bb-dialog ${this.modal ? "bb-dialog-modal" : "bb-dialog-float"}` + (options.className ? ` ${options.className}` : "");
86
+ const header = document.createElement("div");
87
+ header.className = "bb-dialog-header";
88
+ this.title = document.createElement("span");
89
+ this.title.className = "bb-dialog-title";
90
+ this.title.textContent = options.title ?? "";
91
+ const close = document.createElement("button");
92
+ close.type = "button";
93
+ close.className = "bb-dialog-close";
94
+ close.textContent = "\u2715";
95
+ close.setAttribute("aria-label", "Close");
96
+ close.addEventListener("click", () => this.close());
97
+ header.append(this.title, close);
98
+ this.body = document.createElement("div");
99
+ this.body.className = "bb-dialog-body";
100
+ el.append(header, this.body);
101
+ document.body.appendChild(el);
102
+ el.addEventListener("close", () => {
103
+ this.detachReposition();
104
+ this.closeListeners.forEach((cb) => cb());
105
+ });
106
+ if (!this.modal) {
107
+ el.addEventListener("keydown", (e) => {
108
+ if (e.key === "Escape") this.close();
109
+ });
110
+ }
111
+ this.el = el;
112
+ }
113
+ attachReposition() {
114
+ window.addEventListener("scroll", this.reposition, true);
115
+ window.addEventListener("resize", this.reposition);
116
+ }
117
+ detachReposition() {
118
+ window.removeEventListener("scroll", this.reposition, true);
119
+ window.removeEventListener("resize", this.reposition);
120
+ }
121
+ setTitle(text) {
122
+ this.title.textContent = text;
123
+ }
124
+ /** Replace the body content. */
125
+ setContent(node) {
126
+ this.body.replaceChildren(node);
127
+ }
128
+ open() {
129
+ if (this.el.open) return;
130
+ if (this.modal) this.el.showModal();
131
+ else this.el.show();
132
+ if (!this.modal && this.anchor) {
133
+ this.reposition();
134
+ this.attachReposition();
135
+ }
136
+ }
137
+ close() {
138
+ if (this.el.open) this.el.close();
139
+ }
140
+ get isOpen() {
141
+ return this.el.open;
142
+ }
143
+ /** Subscribe to close (Esc, ✕, backdrop, or `close()`); returns unsubscribe. */
144
+ onClose(cb) {
145
+ this.closeListeners.add(cb);
146
+ return () => this.closeListeners.delete(cb);
147
+ }
148
+ destroy() {
149
+ this.detachReposition();
150
+ this.closeListeners.clear();
151
+ this.el.remove();
152
+ }
153
+ };
154
+ function promptDialog(options) {
155
+ return new Promise((resolve) => {
156
+ const dialog = new Dialog({ title: options.title, modal: true });
157
+ const form = document.createElement("form");
158
+ form.className = "bb-prompt";
159
+ const input = document.createElement("input");
160
+ input.type = "text";
161
+ input.className = "bb-prompt-input";
162
+ input.placeholder = options.placeholder ?? "";
163
+ input.value = options.initial ?? "";
164
+ const actions = document.createElement("div");
165
+ actions.className = "bb-prompt-actions";
166
+ const cancel = document.createElement("button");
167
+ cancel.type = "button";
168
+ cancel.className = "bb-prompt-btn";
169
+ cancel.textContent = options.cancel ?? "Cancel";
170
+ const ok = document.createElement("button");
171
+ ok.type = "submit";
172
+ ok.className = "bb-prompt-btn";
173
+ ok.dataset["primary"] = "true";
174
+ ok.textContent = options.confirm ?? "OK";
175
+ actions.append(cancel, ok);
176
+ form.append(input, actions);
177
+ let settled = false;
178
+ const done = (value) => {
179
+ if (settled) return;
180
+ settled = true;
181
+ resolve(value);
182
+ dialog.destroy();
183
+ };
184
+ form.addEventListener("submit", (e) => {
185
+ e.preventDefault();
186
+ done(input.value.trim() || null);
187
+ });
188
+ cancel.addEventListener("click", () => done(null));
189
+ dialog.onClose(() => done(null));
190
+ dialog.setContent(form);
191
+ dialog.open();
192
+ input.focus();
193
+ });
194
+ }
195
+
196
+ // packages/ui/src/lib/toolbar.ts
197
+ 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(([x1, x2], i) => `<line x1="${x1}" y1="${4 + i * 4}" x2="${x2}" y2="${4 + i * 4}"/>`).join("") + `</g></svg>`;
198
+ var DEFAULT_ITEMS = {
199
+ undo: {
200
+ title: "Undo",
201
+ svg: '<svg viewBox="0 0 16 16" width="16" height="16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M4 7h6a3.5 3.5 0 0 1 0 7H6"/><path d="M4 7 7 4M4 7l3 3"/></svg>'
202
+ },
203
+ redo: {
204
+ title: "Redo",
205
+ svg: '<svg viewBox="0 0 16 16" width="16" height="16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M12 7H6a3.5 3.5 0 0 0 0 7h4"/><path d="m12 7-3-3m3 3-3 3"/></svg>'
206
+ },
207
+ "clear-format": {
208
+ title: "Clear formatting",
209
+ svg: '<svg viewBox="0 0 16 16" width="15" height="15" fill="none" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round"><path d="M5 12 8 4l3 8M6 9.4h4"/><path d="M2.5 14 13.5 3" opacity=".55"/></svg>'
210
+ },
211
+ bold: { title: "Bold", label: "B", className: "bb-i-bold" },
212
+ italic: { title: "Italic", label: "I", className: "bb-i-italic" },
213
+ underline: { title: "Underline", label: "U", className: "bb-i-underline" },
214
+ strike: { title: "Strikethrough", label: "S", className: "bb-i-strike" },
215
+ superscript: {
216
+ title: "Superscript",
217
+ svg: '<svg viewBox="0 0 16 16" width="16" height="16" fill="currentColor" aria-hidden="true"><text x="0" y="13" font-size="10.5" font-family="serif">x</text><text x="8.5" y="6.5" font-size="7" font-family="serif">2</text></svg>'
218
+ },
219
+ subscript: {
220
+ title: "Subscript",
221
+ svg: '<svg viewBox="0 0 16 16" width="16" height="16" fill="currentColor" aria-hidden="true"><text x="0" y="11.5" font-size="10.5" font-family="serif">x</text><text x="8.5" y="15.5" font-size="7" font-family="serif">2</text></svg>'
222
+ },
223
+ "align-left": { title: "Align left", svg: alignSvg([[2, 14], [2, 9], [2, 12]]) },
224
+ "align-center": { title: "Center", svg: alignSvg([[2, 14], [4, 12], [3, 13]]) },
225
+ "align-right": { title: "Align right", svg: alignSvg([[2, 14], [7, 14], [4, 14]]) },
226
+ "align-justify": { title: "Justify", svg: alignSvg([[2, 14], [2, 14], [2, 14]]) },
227
+ "bullet-list": {
228
+ title: "Bullet list",
229
+ 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"/><circle cx="3" cy="4" r="1" fill="currentColor" stroke="none"/><circle cx="3" cy="8" r="1" fill="currentColor" stroke="none"/><circle cx="3" cy="12" r="1" fill="currentColor" stroke="none"/></svg>'
230
+ },
231
+ "ordered-list": {
232
+ title: "Numbered list",
233
+ 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>'
234
+ }
235
+ };
236
+ var STYLE2 = `
237
+ .bb-toolbar-wrap{position:relative}
238
+ .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}
239
+ .bb-toolbar *{box-sizing:border-box}
240
+ .bb-toolbar-group{display:flex;gap:2px;flex:none}
241
+ .bb-toolbar-group+.bb-toolbar-group{padding-left:10px;border-left:1px solid var(--bb-ui-border,#e3e3e0)}
242
+ .bb-toolbar-more{margin-left:auto;flex:none}
243
+ .bb-toolbar-pop{position:absolute;z-index:1200;top:100%;left:0;right:0;margin-top:4px;display:flex;flex-wrap:wrap;gap:10px;row-gap:6px;align-items:center;padding:6px 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:10px;box-shadow:0 8px 28px rgba(0,0,0,.16);font-family:var(--bb-ui-font,system-ui,-apple-system,sans-serif);color:var(--bb-ui-fg,#2c2c2a)}
244
+ .bb-toolbar-pop[hidden]{display:none}
245
+ .bb-toolbar-btn{min-width:30px;height:30px;display:inline-flex;align-items:center;justify-content:center;border:1px solid transparent;border-radius:6px;background:transparent;color:inherit;cursor:pointer;font-size:14px;line-height:1;padding:0 7px;font-family:inherit}
246
+ .bb-toolbar-btn:hover{background:var(--bb-ui-hover,#f1efe8)}
247
+ .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)}
248
+ .bb-toolbar-btn:disabled{opacity:.38;cursor:default}
249
+ .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}
250
+ .bb-toolbar-select:hover{background:var(--bb-ui-hover,#f1efe8)}
251
+ .bb-toolbar-color{position:relative}
252
+ .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}
253
+ .bb-toolbar-color .bb-color-bar{width:16px;height:3px;border-radius:1px;background:currentColor}
254
+ .bb-color-pop{position:absolute;z-index:1200;top:33px;left:0;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)}
255
+ .bb-color-pop[hidden]{display:none}
256
+ .bb-color-swatch{width:16px;height:16px;padding:0;border:1px solid rgba(0,0,0,.18);border-radius:3px;cursor:pointer}
257
+ .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}
258
+ .bb-color-none:hover{background:var(--bb-ui-hover,#f1efe8)}
259
+ .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}
260
+ `;
261
+ function defaultToolbarGroups(commands) {
262
+ const names = [...commands].map((c) => c.name);
263
+ const aligns = names.filter((n) => n.startsWith("align-"));
264
+ const rest = names.filter((n) => !n.startsWith("align-"));
265
+ return [rest, aligns].filter((g) => g.length > 0);
266
+ }
267
+ function mountToolbar(host, editor, options = {}) {
268
+ injectStyle("bb-ui-toolbar-styles", STYLE2);
269
+ const items = { ...DEFAULT_ITEMS, ...options.items ?? {} };
270
+ const groups = options.groups ?? defaultToolbarGroups(editor.commands);
271
+ const wrap = document.createElement("div");
272
+ wrap.className = "bb-toolbar-wrap";
273
+ const root = document.createElement("div");
274
+ root.className = "bb-toolbar";
275
+ root.setAttribute("role", "toolbar");
276
+ const buttons = [];
277
+ const selects = [];
278
+ const colors = [];
279
+ let latest = null;
280
+ for (const group of groups) {
281
+ const entries = group.filter((e) => typeof e !== "string" || editor.commands.has(e));
282
+ if (entries.length === 0) continue;
283
+ const groupEl = document.createElement("div");
284
+ groupEl.className = "bb-toolbar-group";
285
+ for (const entry of entries) {
286
+ if (typeof entry !== "string" && entry.kind === "select") {
287
+ const sel = document.createElement("select");
288
+ sel.className = "bb-toolbar-select";
289
+ sel.title = entry.title;
290
+ sel.setAttribute("aria-label", entry.title);
291
+ if (entry.width) sel.style.width = `${entry.width}px`;
292
+ for (const opt of entry.options) {
293
+ const o = document.createElement("option");
294
+ o.value = opt.value;
295
+ o.textContent = opt.label;
296
+ sel.appendChild(o);
297
+ }
298
+ sel.addEventListener("mousedown", (e) => e.stopPropagation());
299
+ sel.addEventListener("change", () => {
300
+ entry.onSelect(sel.value);
301
+ editor.focus();
302
+ });
303
+ groupEl.appendChild(sel);
304
+ selects.push({ spec: entry, el: sel });
305
+ continue;
306
+ }
307
+ if (typeof entry !== "string" && entry.kind === "color") {
308
+ const wrap2 = document.createElement("div");
309
+ wrap2.className = "bb-toolbar-color";
310
+ const btn2 = document.createElement("button");
311
+ btn2.type = "button";
312
+ btn2.className = "bb-toolbar-btn";
313
+ btn2.title = entry.title;
314
+ btn2.setAttribute("aria-label", entry.title);
315
+ const glyph = document.createElement("span");
316
+ glyph.className = "bb-color-glyph";
317
+ glyph.innerHTML = entry.glyph;
318
+ const bar = document.createElement("span");
319
+ bar.className = "bb-color-bar";
320
+ glyph.appendChild(bar);
321
+ btn2.appendChild(glyph);
322
+ const pop2 = document.createElement("div");
323
+ pop2.className = "bb-color-pop";
324
+ pop2.hidden = true;
325
+ const pick = (color) => {
326
+ entry.onSelect(color);
327
+ pop2.hidden = true;
328
+ editor.focus();
329
+ };
330
+ for (const c of entry.swatches) {
331
+ const sw = document.createElement("button");
332
+ sw.type = "button";
333
+ sw.className = "bb-color-swatch";
334
+ sw.style.background = c;
335
+ sw.title = c;
336
+ sw.addEventListener("mousedown", (e) => e.preventDefault());
337
+ sw.addEventListener("click", () => pick(c));
338
+ pop2.appendChild(sw);
339
+ }
340
+ if (entry.allowNone) {
341
+ const none = document.createElement("button");
342
+ none.type = "button";
343
+ none.className = "bb-color-none";
344
+ none.textContent = "None";
345
+ none.addEventListener("mousedown", (e) => e.preventDefault());
346
+ none.addEventListener("click", () => pick(null));
347
+ pop2.appendChild(none);
348
+ }
349
+ btn2.addEventListener("mousedown", (e) => e.preventDefault());
350
+ btn2.addEventListener("click", () => {
351
+ const open = pop2.hidden;
352
+ root.querySelectorAll(".bb-color-pop").forEach((p) => p.hidden = true);
353
+ pop2.hidden = !open;
354
+ if (!pop2.hidden) {
355
+ const onDoc = (ev) => {
356
+ if (!wrap2.contains(ev.target)) {
357
+ pop2.hidden = true;
358
+ document.removeEventListener("pointerdown", onDoc, true);
359
+ }
360
+ };
361
+ document.addEventListener("pointerdown", onDoc, true);
362
+ }
363
+ });
364
+ wrap2.append(btn2, pop2);
365
+ groupEl.appendChild(wrap2);
366
+ colors.push({ spec: entry, bar });
367
+ continue;
368
+ }
369
+ const item = items[entry] ?? { title: entry };
370
+ const btn = document.createElement("button");
371
+ btn.type = "button";
372
+ btn.className = "bb-toolbar-btn" + (item.className ? ` ${item.className}` : "");
373
+ btn.title = item.title;
374
+ btn.setAttribute("aria-label", item.title);
375
+ if (item.svg) btn.innerHTML = item.svg;
376
+ else btn.textContent = item.label ?? entry;
377
+ btn.addEventListener("mousedown", (e) => e.preventDefault());
378
+ btn.addEventListener("click", () => {
379
+ if (!latest) return;
380
+ editor.commands.get(entry)?.run(latest, (tr) => editor.dispatch(tr));
381
+ editor.focus();
382
+ });
383
+ groupEl.appendChild(btn);
384
+ buttons.push({ name: entry, el: btn });
385
+ }
386
+ root.appendChild(groupEl);
387
+ }
388
+ const moreBtn = document.createElement("button");
389
+ moreBtn.type = "button";
390
+ moreBtn.className = "bb-toolbar-btn bb-toolbar-more";
391
+ moreBtn.title = "More tools";
392
+ moreBtn.setAttribute("aria-label", "More tools");
393
+ moreBtn.setAttribute("aria-expanded", "false");
394
+ moreBtn.innerHTML = '<svg viewBox="0 0 16 16" width="15" height="15" fill="currentColor" aria-hidden="true"><circle cx="8" cy="3.2" r="1.4"/><circle cx="8" cy="8" r="1.4"/><circle cx="8" cy="12.8" r="1.4"/></svg>';
395
+ moreBtn.style.display = "none";
396
+ root.appendChild(moreBtn);
397
+ const pop = document.createElement("div");
398
+ pop.className = "bb-toolbar-pop";
399
+ pop.hidden = true;
400
+ wrap.append(root, pop);
401
+ host.appendChild(wrap);
402
+ const closePop = () => {
403
+ pop.hidden = true;
404
+ moreBtn.setAttribute("aria-expanded", "false");
405
+ };
406
+ const onDocPointer = (e) => {
407
+ if (!pop.hidden && !wrap.contains(e.target)) closePop();
408
+ };
409
+ document.addEventListener("pointerdown", onDocPointer, true);
410
+ moreBtn.addEventListener("mousedown", (e) => e.preventDefault());
411
+ moreBtn.addEventListener("click", () => {
412
+ pop.hidden = !pop.hidden;
413
+ moreBtn.setAttribute("aria-expanded", String(!pop.hidden));
414
+ });
415
+ const fits = () => root.scrollWidth <= root.clientWidth + 1;
416
+ const layout = () => {
417
+ while (pop.firstChild) root.insertBefore(pop.firstChild, moreBtn);
418
+ moreBtn.style.display = "none";
419
+ closePop();
420
+ if (fits()) return;
421
+ moreBtn.style.display = "";
422
+ const groupEls = Array.from(root.children).filter((el) => el.classList.contains("bb-toolbar-group"));
423
+ for (let i = groupEls.length - 1; i >= 0 && !fits(); i--) {
424
+ pop.insertBefore(groupEls[i], pop.firstChild);
425
+ }
426
+ };
427
+ let ro = null;
428
+ if (typeof ResizeObserver !== "undefined") {
429
+ ro = new ResizeObserver(layout);
430
+ ro.observe(root);
431
+ }
432
+ window.addEventListener("resize", layout);
433
+ layout();
434
+ const refresh = (state) => {
435
+ latest = state;
436
+ for (const { name, el } of buttons) {
437
+ const cmd = editor.commands.get(name);
438
+ if (!cmd) continue;
439
+ const active = cmd.isActive?.(state) ?? false;
440
+ el.classList.toggle("is-active", active);
441
+ el.setAttribute("aria-pressed", String(active));
442
+ el.disabled = cmd.isEnabled ? !cmd.isEnabled(state) : false;
443
+ }
444
+ for (const { spec, el } of selects) el.value = spec.value(state);
445
+ for (const { spec, bar } of colors) bar.style.background = spec.value(state) ?? "";
446
+ };
447
+ const off = editor.onChange((c) => refresh(c.state));
448
+ try {
449
+ refresh(editor.state);
450
+ } catch {
451
+ }
452
+ return {
453
+ destroy() {
454
+ off();
455
+ ro?.disconnect();
456
+ window.removeEventListener("resize", layout);
457
+ document.removeEventListener("pointerdown", onDocPointer, true);
458
+ wrap.remove();
459
+ }
460
+ };
461
+ }
462
+
463
+ // packages/ui/src/lib/menubar.ts
464
+ var DEFAULT_LABELS = {
465
+ bold: "Bold",
466
+ italic: "Italic",
467
+ underline: "Underline",
468
+ strike: "Strikethrough",
469
+ superscript: "Superscript",
470
+ subscript: "Subscript",
471
+ "align-left": "Align left",
472
+ "align-center": "Center",
473
+ "align-right": "Align right",
474
+ "align-justify": "Justify",
475
+ "bullet-list": "Bullet list",
476
+ "ordered-list": "Numbered list",
477
+ "heading-1": "Heading 1",
478
+ "heading-2": "Heading 2",
479
+ "heading-3": "Heading 3",
480
+ "heading-4": "Heading 4",
481
+ "heading-5": "Heading 5",
482
+ "heading-6": "Heading 6",
483
+ undo: "Undo",
484
+ redo: "Redo",
485
+ "page-break": "Page break"
486
+ };
487
+ var STYLE3 = `
488
+ .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}
489
+ .bb-menubar *{box-sizing:border-box}
490
+ .bb-menubar-menu{position:relative}
491
+ .bb-menubar-title{height:28px;padding:0 10px;border:0;border-radius:6px;background:transparent;color:inherit;font:inherit;font-size:13px;cursor:pointer}
492
+ .bb-menubar-title:hover,.bb-menubar-title[aria-expanded="true"]{background:var(--bb-ui-hover,#f1efe8)}
493
+ .bb-menu{position:absolute;top:100%;left:0;min-width:200px;margin-top:3px;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);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,.14);z-index:1000}
494
+ .bb-menu[hidden]{display:none}
495
+ .bb-menu-sub{position:relative}
496
+ .bb-submenu{top:-5px;left:100%;margin-left:2px;display:none}
497
+ .bb-menu-sub:hover>.bb-submenu,.bb-menu-sub:focus-within>.bb-submenu{display:block}
498
+ .bb-submenu-widget{padding:8px;min-width:0}
499
+ .bb-menu-item{display:flex;align-items:center;gap:8px;width:100%;height:30px;padding:0 10px 0 6px;border:0;border-radius:5px;background:transparent;color:inherit;font:inherit;font-size:13px;text-align:left;white-space:nowrap;cursor:pointer}
500
+ .bb-menu-item:hover,.bb-menu-item:focus{background:var(--bb-ui-hover,#f1efe8);outline:none}
501
+ .bb-menu-item:disabled{opacity:.4;cursor:default}
502
+ .bb-menu-check{width:14px;flex:none;display:inline-flex;justify-content:center;color:var(--bb-ui-active-fg,#0c447c)}
503
+ .bb-menu-label{flex:1 1 auto}
504
+ .bb-menu-shortcut{flex:none;opacity:.5;font-size:12px;padding-left:24px}
505
+ .bb-menu-arrow{flex:none;opacity:.55;padding-left:12px}
506
+ .bb-menu-sep{height:1px;margin:4px 6px;background:var(--bb-ui-border,#e3e3e0)}
507
+ .bb-menubar-v{flex-direction:column;align-items:stretch;gap:1px;border-bottom:none;padding:4px}
508
+ .bb-menubar-v .bb-menubar-title{text-align:left;width:100%}
509
+ .bb-menubar-v .bb-menu{top:auto;bottom:0;left:100%;margin-top:0;margin-left:4px}
510
+ `;
511
+ function defaultMenus(commands) {
512
+ const names = [...commands].map((c) => c.name);
513
+ const marks = ["bold", "italic", "underline", "strike"].filter((n) => commands.has(n));
514
+ const aligns = names.filter((n) => n.startsWith("align-"));
515
+ const entries = [
516
+ ...marks.map((command) => ({ command })),
517
+ ...marks.length && aligns.length ? ["separator"] : [],
518
+ ...aligns.map((command) => ({ command }))
519
+ ];
520
+ return entries.length ? [{ label: "Format", entries }] : [];
521
+ }
522
+ function mountMenubar(host, editor, options = {}) {
523
+ injectStyle("bb-ui-menubar-styles", STYLE3);
524
+ const labels = { ...DEFAULT_LABELS, ...options.labels ?? {} };
525
+ const menus = options.menus ?? defaultMenus(editor.commands);
526
+ const vertical = options.orientation === "vertical";
527
+ const root = document.createElement("div");
528
+ root.className = "bb-menubar" + (vertical ? " bb-menubar-v" : "");
529
+ root.setAttribute("role", "menubar");
530
+ if (vertical) root.setAttribute("aria-orientation", "vertical");
531
+ const panels = [];
532
+ const checks = [];
533
+ const enables = [];
534
+ let openIdx = -1;
535
+ let latest = null;
536
+ const close = (refocusTitle = false) => {
537
+ if (openIdx < 0) return;
538
+ const p = panels[openIdx];
539
+ p.dropdown.hidden = true;
540
+ p.title.setAttribute("aria-expanded", "false");
541
+ if (refocusTitle) p.title.focus();
542
+ openIdx = -1;
543
+ };
544
+ const open = (idx, focusFirst = false) => {
545
+ if (openIdx === idx) return;
546
+ close();
547
+ const p = panels[idx];
548
+ p.dropdown.hidden = false;
549
+ p.title.setAttribute("aria-expanded", "true");
550
+ openIdx = idx;
551
+ if (latest) refresh(latest);
552
+ if (focusFirst) p.dropdown.querySelector(".bb-menu-item")?.focus();
553
+ };
554
+ const makeRow = (label, opts) => {
555
+ const item = document.createElement("button");
556
+ item.type = "button";
557
+ item.className = "bb-menu-item" + (opts.hasSub ? " bb-has-sub" : "");
558
+ item.addEventListener("mousedown", (e) => e.preventDefault());
559
+ const check = document.createElement("span");
560
+ check.className = "bb-menu-check";
561
+ const text = document.createElement("span");
562
+ text.className = "bb-menu-label";
563
+ text.textContent = label;
564
+ item.append(check, text);
565
+ if (opts.shortcut) {
566
+ const sc = document.createElement("span");
567
+ sc.className = "bb-menu-shortcut";
568
+ sc.textContent = opts.shortcut;
569
+ item.appendChild(sc);
570
+ }
571
+ if (opts.hasSub) {
572
+ const arrow = document.createElement("span");
573
+ arrow.className = "bb-menu-arrow";
574
+ arrow.textContent = "\u203A";
575
+ item.appendChild(arrow);
576
+ }
577
+ return { item, check };
578
+ };
579
+ const buildEntries = (entries, container) => {
580
+ for (const entry of entries) {
581
+ if (entry === "separator") {
582
+ const sep = document.createElement("div");
583
+ sep.className = "bb-menu-sep";
584
+ sep.setAttribute("role", "separator");
585
+ container.appendChild(sep);
586
+ } else if ("submenu" in entry || "widget" in entry) {
587
+ const wrap = document.createElement("div");
588
+ wrap.className = "bb-menu-sub";
589
+ const { item } = makeRow(entry.label, { hasSub: true });
590
+ item.setAttribute("aria-haspopup", "true");
591
+ const flyout = document.createElement("div");
592
+ flyout.className = "bb-menu bb-submenu";
593
+ flyout.setAttribute("role", "menu");
594
+ if ("widget" in entry) {
595
+ flyout.classList.add("bb-submenu-widget");
596
+ flyout.appendChild(entry.widget(() => close()));
597
+ } else {
598
+ buildEntries(entry.submenu, flyout);
599
+ }
600
+ wrap.append(item, flyout);
601
+ container.appendChild(wrap);
602
+ } else if ("command" in entry) {
603
+ const cmd = editor.commands.get(entry.command);
604
+ if (!cmd) continue;
605
+ const { item, check } = makeRow(entry.label ?? labels[entry.command] ?? entry.command, {});
606
+ item.setAttribute("role", "menuitemcheckbox");
607
+ item.addEventListener("click", () => {
608
+ if (latest) editor.commands.get(entry.command)?.run(latest, (tr) => editor.dispatch(tr));
609
+ close();
610
+ editor.focus();
611
+ });
612
+ checks.push({ el: check, active: (s) => cmd.isActive?.(s) ?? false });
613
+ if (cmd.isEnabled) enables.push({ el: item, enabled: (s) => cmd.isEnabled(s) });
614
+ container.appendChild(item);
615
+ } else {
616
+ const { item, check } = makeRow(entry.label, { shortcut: entry.shortcut });
617
+ item.setAttribute("role", entry.isActive ? "menuitemcheckbox" : "menuitem");
618
+ item.addEventListener("click", () => {
619
+ entry.run();
620
+ close();
621
+ });
622
+ if (entry.isActive) checks.push({ el: check, active: () => entry.isActive() });
623
+ if (entry.isEnabled) enables.push({ el: item, enabled: () => entry.isEnabled() });
624
+ container.appendChild(item);
625
+ }
626
+ }
627
+ };
628
+ menus.forEach((menu, idx) => {
629
+ const wrap = document.createElement("div");
630
+ wrap.className = "bb-menubar-menu";
631
+ const title = document.createElement("button");
632
+ title.type = "button";
633
+ title.className = "bb-menubar-title";
634
+ title.textContent = menu.label;
635
+ title.setAttribute("role", "menuitem");
636
+ title.setAttribute("aria-haspopup", "true");
637
+ title.setAttribute("aria-expanded", "false");
638
+ const dropdown = document.createElement("div");
639
+ dropdown.className = "bb-menu";
640
+ dropdown.setAttribute("role", "menu");
641
+ dropdown.hidden = true;
642
+ title.addEventListener("mousedown", (e) => e.preventDefault());
643
+ title.addEventListener("click", () => openIdx === idx ? close() : open(idx));
644
+ title.addEventListener("mouseenter", () => {
645
+ if (openIdx >= 0 && openIdx !== idx) open(idx);
646
+ });
647
+ title.addEventListener("keydown", (e) => {
648
+ if (e.key === "ArrowDown" || e.key === "Enter" || e.key === " " || vertical && e.key === "ArrowRight") {
649
+ e.preventDefault();
650
+ open(idx, true);
651
+ }
652
+ });
653
+ buildEntries(menu.entries, dropdown);
654
+ wrap.append(title, dropdown);
655
+ root.appendChild(wrap);
656
+ panels.push({ title, dropdown });
657
+ });
658
+ host.appendChild(root);
659
+ const onDocPointer = (e) => {
660
+ if (openIdx >= 0 && !root.contains(e.target)) close();
661
+ };
662
+ const onKey = (e) => {
663
+ if (e.key === "Escape" && openIdx >= 0) {
664
+ e.preventDefault();
665
+ close(true);
666
+ }
667
+ };
668
+ document.addEventListener("pointerdown", onDocPointer);
669
+ document.addEventListener("keydown", onKey);
670
+ function refresh(state) {
671
+ latest = state;
672
+ if (openIdx < 0) return;
673
+ for (const c of checks) c.el.textContent = c.active(state) ? "\u2713" : "";
674
+ for (const e of enables) e.el.disabled = !e.enabled(state);
675
+ }
676
+ const off = editor.onChange((c) => refresh(c.state));
677
+ try {
678
+ latest = editor.state;
679
+ } catch {
680
+ }
681
+ return {
682
+ destroy() {
683
+ off();
684
+ document.removeEventListener("pointerdown", onDocPointer);
685
+ document.removeEventListener("keydown", onKey);
686
+ root.remove();
687
+ }
688
+ };
689
+ }
690
+
691
+ // packages/ui/src/lib/find.ts
692
+ var DEFAULT_LABELS2 = {
693
+ title: "Find and replace",
694
+ find: "Find\u2026",
695
+ replace: "Replace\u2026",
696
+ prev: "Previous",
697
+ next: "Next",
698
+ replaceOne: "Replace",
699
+ replaceAll: "Replace all"
700
+ };
701
+ var STYLE4 = `
702
+ .bb-find{display:flex;flex-direction:column;gap:8px;min-width:300px}
703
+ .bb-find *{box-sizing:border-box}
704
+ .bb-find-row{display:flex;gap:6px;align-items:center}
705
+ .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}
706
+ .bb-find-count{min-width:44px;text-align:center;font-size:12px;opacity:.65;font-variant-numeric:tabular-nums}
707
+ .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}
708
+ .bb-find-btn:hover:not(:disabled){background:var(--bb-ui-hover,#f1efe8)}
709
+ .bb-find-btn:disabled{opacity:.4;cursor:default}
710
+ `;
711
+ function createFindDialog(find, options = {}) {
712
+ injectStyle("bb-ui-find-styles", STYLE4);
713
+ const labels = { ...DEFAULT_LABELS2, ...options.labels ?? {} };
714
+ const root = document.createElement("div");
715
+ root.className = "bb-find";
716
+ const findInput = document.createElement("input");
717
+ findInput.type = "text";
718
+ findInput.className = "bb-find-input";
719
+ findInput.placeholder = labels.find;
720
+ findInput.setAttribute("aria-label", labels.find);
721
+ const count = document.createElement("span");
722
+ count.className = "bb-find-count";
723
+ count.textContent = "0";
724
+ const mkBtn = (label, title) => {
725
+ const b = document.createElement("button");
726
+ b.type = "button";
727
+ b.className = "bb-find-btn";
728
+ b.textContent = label;
729
+ b.title = title;
730
+ b.setAttribute("aria-label", title);
731
+ return b;
732
+ };
733
+ const prev = mkBtn("\u25C0", labels.prev);
734
+ const next = mkBtn("\u25B6", labels.next);
735
+ const replaceInput = document.createElement("input");
736
+ replaceInput.type = "text";
737
+ replaceInput.className = "bb-find-input";
738
+ replaceInput.placeholder = labels.replace;
739
+ replaceInput.setAttribute("aria-label", labels.replace);
740
+ const replaceOne = mkBtn(labels.replaceOne, labels.replaceOne);
741
+ const replaceAll = mkBtn(labels.replaceAll, labels.replaceAll);
742
+ const row1 = document.createElement("div");
743
+ row1.className = "bb-find-row";
744
+ row1.append(findInput, count, prev, next);
745
+ const row2 = document.createElement("div");
746
+ row2.className = "bb-find-row";
747
+ row2.append(replaceInput, replaceOne, replaceAll);
748
+ root.append(row1, row2);
749
+ const matchButtons = [prev, next, replaceOne, replaceAll];
750
+ findInput.addEventListener("input", () => find.setQuery(findInput.value));
751
+ findInput.addEventListener("keydown", (e) => {
752
+ if (e.key === "Enter") {
753
+ e.preventDefault();
754
+ find.next();
755
+ }
756
+ });
757
+ prev.addEventListener("click", () => find.prev());
758
+ next.addEventListener("click", () => find.next());
759
+ replaceOne.addEventListener("click", () => find.replaceCurrent(replaceInput.value));
760
+ replaceAll.addEventListener("click", () => find.replaceAll(replaceInput.value));
761
+ const off = find.onState((s) => {
762
+ count.textContent = s.count ? `${s.active}/${s.count}` : "0";
763
+ for (const b of matchButtons) b.disabled = s.count === 0;
764
+ });
765
+ for (const b of matchButtons) b.disabled = true;
766
+ const dialog = new Dialog({
767
+ title: labels.title,
768
+ modal: options.modal ?? false,
769
+ anchor: options.anchor,
770
+ className: "bb-find-dialog"
771
+ });
772
+ dialog.setContent(root);
773
+ dialog.onClose(() => {
774
+ find.clear();
775
+ findInput.value = "";
776
+ });
777
+ const openPanel = () => {
778
+ dialog.open();
779
+ findInput.focus();
780
+ findInput.select();
781
+ };
782
+ const onHotkey = (e) => {
783
+ if ((e.metaKey || e.ctrlKey) && !e.altKey && (e.key === "f" || e.key === "F")) {
784
+ e.preventDefault();
785
+ openPanel();
786
+ }
787
+ };
788
+ if (options.shortcut !== false) document.addEventListener("keydown", onHotkey);
789
+ return {
790
+ open: openPanel,
791
+ close() {
792
+ dialog.close();
793
+ },
794
+ destroy() {
795
+ document.removeEventListener("keydown", onHotkey);
796
+ off();
797
+ dialog.destroy();
798
+ }
799
+ };
800
+ }
801
+
802
+ // packages/ui/src/lib/context-menu.ts
803
+ var STYLE5 = `
804
+ .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)}
805
+ .bb-ctx *{box-sizing:border-box}
806
+ .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}
807
+ .bb-ctx-item:hover:not(:disabled),.bb-ctx-item:focus{background:var(--bb-ui-hover,#f1efe8);outline:none}
808
+ .bb-ctx-item:disabled{opacity:.4;cursor:default}
809
+ .bb-ctx-label{flex:1 1 auto}
810
+ .bb-ctx-shortcut{flex:none;opacity:.5;font-size:12px}
811
+ .bb-ctx-sep{height:1px;margin:4px 6px;background:var(--bb-ui-border,#e3e3e0)}
812
+ `;
813
+ var current = null;
814
+ function closeCurrent() {
815
+ if (current) {
816
+ const dispose = current;
817
+ current = null;
818
+ dispose();
819
+ }
820
+ }
821
+ function showContextMenu(entries, at) {
822
+ injectStyle("bb-ui-contextmenu-styles", STYLE5);
823
+ closeCurrent();
824
+ const el = document.createElement("div");
825
+ el.className = "bb-ctx";
826
+ el.setAttribute("role", "menu");
827
+ for (const entry of entries) {
828
+ if (entry === "separator") {
829
+ const sep = document.createElement("div");
830
+ sep.className = "bb-ctx-sep";
831
+ sep.setAttribute("role", "separator");
832
+ el.appendChild(sep);
833
+ continue;
834
+ }
835
+ const item = document.createElement("button");
836
+ item.type = "button";
837
+ item.className = "bb-ctx-item";
838
+ item.setAttribute("role", "menuitem");
839
+ item.disabled = entry.enabled === false;
840
+ const label = document.createElement("span");
841
+ label.className = "bb-ctx-label";
842
+ label.textContent = entry.label;
843
+ item.appendChild(label);
844
+ if (entry.shortcut) {
845
+ const sc = document.createElement("span");
846
+ sc.className = "bb-ctx-shortcut";
847
+ sc.textContent = entry.shortcut;
848
+ item.appendChild(sc);
849
+ }
850
+ item.addEventListener("mousedown", (e) => e.preventDefault());
851
+ item.addEventListener("click", () => {
852
+ closeCurrent();
853
+ entry.run();
854
+ });
855
+ el.appendChild(item);
856
+ }
857
+ document.body.appendChild(el);
858
+ const r = el.getBoundingClientRect();
859
+ el.style.left = `${Math.max(4, Math.min(at.x, window.innerWidth - r.width - 4))}px`;
860
+ el.style.top = `${Math.max(4, Math.min(at.y, window.innerHeight - r.height - 4))}px`;
861
+ const items = () => Array.from(el.querySelectorAll(".bb-ctx-item:not(:disabled)"));
862
+ const onDown = (e) => {
863
+ if (!el.contains(e.target)) closeCurrent();
864
+ };
865
+ const onKey = (e) => {
866
+ if (e.key === "Escape") return closeCurrent();
867
+ if (e.key === "ArrowDown" || e.key === "ArrowUp") {
868
+ e.preventDefault();
869
+ const list = items();
870
+ const i = list.indexOf(document.activeElement);
871
+ const next = e.key === "ArrowDown" ? (i + 1) % list.length : (i - 1 + list.length) % list.length;
872
+ list[next]?.focus();
873
+ }
874
+ };
875
+ const onScroll = () => closeCurrent();
876
+ setTimeout(() => {
877
+ document.addEventListener("pointerdown", onDown, true);
878
+ document.addEventListener("keydown", onKey, true);
879
+ window.addEventListener("scroll", onScroll, true);
880
+ }, 0);
881
+ current = () => {
882
+ document.removeEventListener("pointerdown", onDown, true);
883
+ document.removeEventListener("keydown", onKey, true);
884
+ window.removeEventListener("scroll", onScroll, true);
885
+ el.remove();
886
+ };
887
+ items()[0]?.focus();
888
+ return { close: closeCurrent };
889
+ }
890
+
891
+ // packages/ui/src/lib/cell-properties.ts
892
+ var FILL_PRESETS = ["#FCEBEB", "#FAEEDA", "#EAF3DE", "#E6F1FB", "#EEEDFE", "#FBEAF0", "#E1F5EE", "#F1EFE8", "#D3D1C7"];
893
+ var PEN_COLORS = ["#000000", "#5F5E5A", "#B0B0B0", "#E24B4A", "#185FA5", "#1D9E75", "#BA7517"];
894
+ var PT_WIDTHS = [0.5, 0.75, 1, 1.5, 2.25, 3];
895
+ var STYLES = [
896
+ { key: "solid", label: "Solid" },
897
+ { key: "dashed", label: "Dashed" },
898
+ { key: "dotted", label: "Dotted" },
899
+ { key: "double", label: "Double" }
900
+ ];
901
+ var VALIGNS = [
902
+ { key: "top", label: "Top" },
903
+ { key: "center", label: "Middle" },
904
+ { key: "bottom", label: "Bottom" }
905
+ ];
906
+ var PRESETS = [
907
+ { key: "all", label: "All borders", sides: [1, 1, 1, 1, 1, 1], inside: false },
908
+ { key: "none", label: "No border", sides: [0, 0, 0, 0, 0, 0], inside: false },
909
+ { key: "outside", label: "Outside", sides: [1, 1, 1, 1, 0, 0], inside: false },
910
+ { key: "inside", label: "Inside", sides: [0, 0, 0, 0, 1, 1], inside: true },
911
+ { key: "top", label: "Top", sides: [1, 0, 0, 0, 0, 0], inside: false },
912
+ { key: "bottom", label: "Bottom", sides: [0, 0, 1, 0, 0, 0], inside: false },
913
+ { key: "left", label: "Left", sides: [0, 0, 0, 1, 0, 0], inside: false },
914
+ { key: "right", label: "Right", sides: [0, 1, 0, 0, 0, 0], inside: false },
915
+ { key: "insideH", label: "Inside horizontal", sides: [0, 0, 0, 0, 1, 0], inside: true },
916
+ { key: "insideV", label: "Inside vertical", sides: [0, 0, 0, 0, 0, 1], inside: true }
917
+ ];
918
+ var STYLE6 = `
919
+ .bb-cp{display:flex;flex-direction:column;gap:16px;min-width:320px;color:var(--bb-ui-fg,#2c2c2a)}
920
+ .bb-cp *{box-sizing:border-box}
921
+ .bb-cp-label{font-size:12px;opacity:.7;margin-bottom:8px}
922
+ .bb-cp-swatches{display:flex;flex-wrap:wrap;gap:7px}
923
+ .bb-cp-swatch{width:26px;height:26px;border-radius:6px;border:0.5px solid var(--bb-ui-border,#d8d6cf);padding:0;cursor:pointer}
924
+ .bb-cp-swatch.on{box-shadow:0 0 0 2px var(--bb-ui-active-fg,#0c447c)}
925
+ .bb-cp-none{display:flex;align-items:center;justify-content:center;background:var(--bb-ui-bg,#fff);font-size:15px;color:#b4b2a9}
926
+ .bb-cp-hexrow{display:flex;align-items:center;gap:8px;margin-top:10px}
927
+ .bb-cp-hexpreview{width:26px;height:26px;border-radius:6px;border:0.5px solid var(--bb-ui-border,#d8d6cf);flex:none}
928
+ .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}
929
+ .bb-cp-seg{display:inline-flex;border:0.5px solid var(--bb-ui-border,#d8d6cf);border-radius:6px;overflow:hidden}
930
+ .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}
931
+ .bb-cp-segbtn:last-child{border-right:0}
932
+ .bb-cp-segbtn.on{background:var(--bb-ui-active-bg,#e6f1fb);color:var(--bb-ui-active-fg,#0c447c)}
933
+ .bb-cp-presets{display:grid;grid-template-columns:repeat(5,1fr);gap:6px;margin-bottom:12px}
934
+ .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}
935
+ .bb-cp-preset.on{border-color:var(--bb-ui-active-border,#7fb2ec);background:var(--bb-ui-active-bg,#e6f1fb)}
936
+ .bb-cp-preset:disabled{opacity:.35;cursor:default}
937
+ .bb-cp-penrow{display:flex;gap:10px;margin-bottom:10px}
938
+ .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}
939
+ .bb-cp-colors{display:flex;gap:7px}
940
+ .bb-cp-color{width:24px;height:24px;border-radius:6px;border:0.5px solid var(--bb-ui-border,#d8d6cf);padding:0;cursor:pointer}
941
+ .bb-cp-color.on{box-shadow:0 0 0 2px var(--bb-ui-active-fg,#0c447c)}
942
+ .bb-cp-footer{display:flex;justify-content:flex-end;gap:8px;margin-top:2px}
943
+ .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}
944
+ .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)}
945
+ `;
946
+ var ACTIVE = "#378ADD";
947
+ var INACTIVE = "#c9c7be";
948
+ var presetLine = (x1, y1, x2, y2, on) => `<line x1="${x1}" y1="${y1}" x2="${x2}" y2="${y2}" stroke="${on ? ACTIVE : INACTIVE}" stroke-width="${on ? 1.6 : 1}" ${on ? "" : 'stroke-dasharray="1.5 1.5"'} stroke-linecap="round"/>`;
949
+ var presetIcon = (s) => `<svg width="20" height="20" viewBox="0 0 20 20" aria-hidden="true">` + presetLine(3, 3, 17, 3, s[0]) + presetLine(17, 3, 17, 17, s[1]) + presetLine(3, 17, 17, 17, s[2]) + presetLine(3, 3, 3, 17, s[3]) + presetLine(3, 10, 17, 10, s[4]) + presetLine(10, 3, 10, 17, s[5]) + `</svg>`;
950
+ var section = (label) => {
951
+ const sec = document.createElement("div");
952
+ const l = document.createElement("div");
953
+ l.className = "bb-cp-label";
954
+ l.textContent = label;
955
+ sec.appendChild(l);
956
+ return sec;
957
+ };
958
+ function openCellProperties({ initial, singleCell, onApply }) {
959
+ injectStyle("bb-ui-cellprops-styles", STYLE6);
960
+ let background = initial.background;
961
+ let vAlign = initial.vAlign;
962
+ let penWidthPt = 1;
963
+ let penStyle = "solid";
964
+ let penColor = "#000000";
965
+ let preset = null;
966
+ const root = document.createElement("div");
967
+ root.className = "bb-cp";
968
+ const fill = section("Fill");
969
+ const swatches = document.createElement("div");
970
+ swatches.className = "bb-cp-swatches";
971
+ const swatchEls = [];
972
+ const addSwatch = (el, color) => {
973
+ el.addEventListener("click", () => {
974
+ background = color;
975
+ syncFill();
976
+ });
977
+ swatches.appendChild(el);
978
+ swatchEls.push({ el, color });
979
+ };
980
+ const none = document.createElement("button");
981
+ none.type = "button";
982
+ none.className = "bb-cp-swatch bb-cp-none";
983
+ none.title = "No fill";
984
+ none.textContent = "\u29B8";
985
+ addSwatch(none, null);
986
+ for (const color of FILL_PRESETS) {
987
+ const sw = document.createElement("button");
988
+ sw.type = "button";
989
+ sw.className = "bb-cp-swatch";
990
+ sw.style.background = color;
991
+ sw.title = color;
992
+ addSwatch(sw, color);
993
+ }
994
+ const hexRow = document.createElement("div");
995
+ hexRow.className = "bb-cp-hexrow";
996
+ const hexPreview = document.createElement("span");
997
+ hexPreview.className = "bb-cp-hexpreview";
998
+ const hex = document.createElement("input");
999
+ hex.type = "text";
1000
+ hex.className = "bb-cp-hex";
1001
+ hex.placeholder = "#RRGGBB";
1002
+ hex.addEventListener("input", () => {
1003
+ const v = hex.value.trim();
1004
+ if (/^#[0-9a-fA-F]{6}$/.test(v)) {
1005
+ background = v;
1006
+ syncFill();
1007
+ }
1008
+ });
1009
+ hexRow.append(hexPreview, hex);
1010
+ fill.append(swatches, hexRow);
1011
+ const valign = section("Vertical alignment");
1012
+ const seg = document.createElement("div");
1013
+ seg.className = "bb-cp-seg";
1014
+ const vaBtns = /* @__PURE__ */ new Map();
1015
+ for (const { key, label } of VALIGNS) {
1016
+ const b = document.createElement("button");
1017
+ b.type = "button";
1018
+ b.className = "bb-cp-segbtn";
1019
+ b.textContent = label;
1020
+ b.addEventListener("click", () => {
1021
+ vAlign = key;
1022
+ syncVAlign();
1023
+ });
1024
+ seg.appendChild(b);
1025
+ vaBtns.set(key, b);
1026
+ }
1027
+ valign.appendChild(seg);
1028
+ const borders = section("Borders");
1029
+ const penRow = document.createElement("div");
1030
+ penRow.className = "bb-cp-penrow";
1031
+ const widthSel = document.createElement("select");
1032
+ widthSel.className = "bb-cp-select";
1033
+ for (const pt of PT_WIDTHS) {
1034
+ const o = document.createElement("option");
1035
+ o.value = String(pt);
1036
+ o.textContent = `${pt} pt`;
1037
+ if (pt === penWidthPt) o.selected = true;
1038
+ widthSel.appendChild(o);
1039
+ }
1040
+ widthSel.addEventListener("change", () => penWidthPt = Number(widthSel.value));
1041
+ const styleSel = document.createElement("select");
1042
+ styleSel.className = "bb-cp-select";
1043
+ for (const { key, label } of STYLES) {
1044
+ const o = document.createElement("option");
1045
+ o.value = key;
1046
+ o.textContent = label;
1047
+ styleSel.appendChild(o);
1048
+ }
1049
+ styleSel.addEventListener("change", () => penStyle = styleSel.value);
1050
+ penRow.append(widthSel, styleSel);
1051
+ const colors = document.createElement("div");
1052
+ colors.className = "bb-cp-colors";
1053
+ const colorEls = [];
1054
+ for (const color of PEN_COLORS) {
1055
+ const c = document.createElement("button");
1056
+ c.type = "button";
1057
+ c.className = "bb-cp-color";
1058
+ c.style.background = color;
1059
+ c.title = color;
1060
+ c.addEventListener("click", () => {
1061
+ penColor = color;
1062
+ syncColors();
1063
+ });
1064
+ colors.appendChild(c);
1065
+ colorEls.push({ el: c, color });
1066
+ }
1067
+ const grid = document.createElement("div");
1068
+ grid.className = "bb-cp-presets";
1069
+ const presetEls = [];
1070
+ for (const p of PRESETS) {
1071
+ const btn = document.createElement("button");
1072
+ btn.type = "button";
1073
+ btn.className = "bb-cp-preset";
1074
+ btn.title = p.label;
1075
+ btn.setAttribute("aria-label", p.label);
1076
+ btn.innerHTML = presetIcon(p.sides);
1077
+ btn.disabled = singleCell && p.inside;
1078
+ btn.addEventListener("click", () => {
1079
+ preset = preset === p.key ? null : p.key;
1080
+ syncPresets();
1081
+ });
1082
+ grid.appendChild(btn);
1083
+ presetEls.push({ el: btn, key: p.key });
1084
+ }
1085
+ borders.append(grid, penRow, colors);
1086
+ const footer = document.createElement("div");
1087
+ footer.className = "bb-cp-footer";
1088
+ const cancel = document.createElement("button");
1089
+ cancel.type = "button";
1090
+ cancel.className = "bb-cp-btn";
1091
+ cancel.textContent = "Cancel";
1092
+ const apply = document.createElement("button");
1093
+ apply.type = "button";
1094
+ apply.className = "bb-cp-btn bb-cp-primary";
1095
+ apply.textContent = "Apply";
1096
+ footer.append(cancel, apply);
1097
+ root.append(fill, valign, borders, footer);
1098
+ function syncFill() {
1099
+ for (const { el, color } of swatchEls) el.classList.toggle("on", color === background);
1100
+ hexPreview.style.background = background ?? "transparent";
1101
+ if (document.activeElement !== hex) hex.value = background ?? "";
1102
+ }
1103
+ function syncVAlign() {
1104
+ for (const [key, b] of vaBtns) b.classList.toggle("on", vAlign === key);
1105
+ }
1106
+ function syncColors() {
1107
+ for (const { el, color } of colorEls) el.classList.toggle("on", color === penColor);
1108
+ }
1109
+ function syncPresets() {
1110
+ for (const { el, key } of presetEls) el.classList.toggle("on", preset === key);
1111
+ }
1112
+ syncFill();
1113
+ syncVAlign();
1114
+ syncColors();
1115
+ syncPresets();
1116
+ const dialog = new Dialog({ title: "Cell properties", modal: true });
1117
+ dialog.setContent(root);
1118
+ dialog.onClose(() => dialog.destroy());
1119
+ cancel.addEventListener("click", () => dialog.close());
1120
+ apply.addEventListener("click", () => {
1121
+ const result = { background, vAlign };
1122
+ if (preset) {
1123
+ result.border = { preset, width: penWidthPt * 96 / 72, style: penStyle, color: penColor };
1124
+ }
1125
+ onApply(result);
1126
+ dialog.close();
1127
+ });
1128
+ dialog.open();
1129
+ }
1130
+
1131
+ // packages/ui/src/lib/table-grid.ts
1132
+ var STYLE7 = `
1133
+ .bb-grid{display:flex;flex-direction:column;gap:6px;user-select:none}
1134
+ .bb-grid-cells{display:grid;gap:2px}
1135
+ .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}
1136
+ .bb-grid-cell.on{background:var(--bb-ui-active-bg,#e6f1fb);border-color:var(--bb-ui-active-border,#7fb2ec)}
1137
+ .bb-grid-label{font-size:12px;text-align:center;opacity:.7}
1138
+ `;
1139
+ function tableGridPicker(options) {
1140
+ injectStyle("bb-ui-grid-styles", STYLE7);
1141
+ const maxRows = options.maxRows ?? 8;
1142
+ const maxCols = options.maxCols ?? 10;
1143
+ const root = document.createElement("div");
1144
+ root.className = "bb-grid";
1145
+ const cellsEl = document.createElement("div");
1146
+ cellsEl.className = "bb-grid-cells";
1147
+ cellsEl.style.gridTemplateColumns = `repeat(${maxCols}, 15px)`;
1148
+ const label = document.createElement("div");
1149
+ label.className = "bb-grid-label";
1150
+ label.textContent = "0 \xD7 0";
1151
+ const cells = [];
1152
+ const highlight = (r, c) => {
1153
+ for (let i = 0; i < cells.length; i++) {
1154
+ const row = Math.floor(i / maxCols);
1155
+ const col = i % maxCols;
1156
+ cells[i].classList.toggle("on", row <= r && col <= c);
1157
+ }
1158
+ label.textContent = `${r + 1} \xD7 ${c + 1}`;
1159
+ };
1160
+ for (let r = 0; r < maxRows; r++) {
1161
+ for (let c = 0; c < maxCols; c++) {
1162
+ const cell = document.createElement("button");
1163
+ cell.type = "button";
1164
+ cell.className = "bb-grid-cell";
1165
+ cell.addEventListener("mousedown", (e) => e.preventDefault());
1166
+ cell.addEventListener("mouseenter", () => highlight(r, c));
1167
+ cell.addEventListener("click", () => options.onPick(r + 1, c + 1));
1168
+ cells.push(cell);
1169
+ cellsEl.appendChild(cell);
1170
+ }
1171
+ }
1172
+ root.append(cellsEl, label);
1173
+ return root;
1174
+ }
1175
+ // Annotate the CommonJS export names for ESM import in node:
1176
+ 0 && (module.exports = {
1177
+ Dialog,
1178
+ createFindDialog,
1179
+ defaultMenus,
1180
+ defaultToolbarGroups,
1181
+ mountMenubar,
1182
+ mountToolbar,
1183
+ openCellProperties,
1184
+ promptDialog,
1185
+ showContextMenu,
1186
+ tableGridPicker
1187
+ });