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