@shadow-garden/bapbong-ui 0.1.1 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -29,6 +29,7 @@ __export(index_exports, {
29
29
  openCellProperties: () => openCellProperties,
30
30
  promptDialog: () => promptDialog,
31
31
  showContextMenu: () => showContextMenu,
32
+ showLinkPanel: () => showLinkPanel,
32
33
  tableGridPicker: () => tableGridPicker
33
34
  });
34
35
  module.exports = __toCommonJS(index_exports);
@@ -194,7 +195,9 @@ function promptDialog(options) {
194
195
  }
195
196
 
196
197
  // 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 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(
199
+ ([x1, x2], i) => `<line x1="${x1}" y1="${4 + i * 4}" x2="${x2}" y2="${4 + i * 4}"/>`
200
+ ).join("") + `</g></svg>`;
198
201
  var DEFAULT_ITEMS = {
199
202
  undo: {
200
203
  title: "Undo",
@@ -220,10 +223,38 @@ var DEFAULT_ITEMS = {
220
223
  title: "Subscript",
221
224
  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
225
  },
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]]) },
226
+ "align-left": {
227
+ title: "Align left",
228
+ svg: alignSvg([
229
+ [2, 14],
230
+ [2, 9],
231
+ [2, 12]
232
+ ])
233
+ },
234
+ "align-center": {
235
+ title: "Center",
236
+ svg: alignSvg([
237
+ [2, 14],
238
+ [4, 12],
239
+ [3, 13]
240
+ ])
241
+ },
242
+ "align-right": {
243
+ title: "Align right",
244
+ svg: alignSvg([
245
+ [2, 14],
246
+ [7, 14],
247
+ [4, 14]
248
+ ])
249
+ },
250
+ "align-justify": {
251
+ title: "Justify",
252
+ svg: alignSvg([
253
+ [2, 14],
254
+ [2, 14],
255
+ [2, 14]
256
+ ])
257
+ },
227
258
  "bullet-list": {
228
259
  title: "Bullet list",
229
260
  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>'
@@ -251,12 +282,26 @@ var STYLE2 = `
251
282
  .bb-toolbar-color{position:relative}
252
283
  .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
284
  .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)}
285
+ .bb-color-pop{position:absolute;z-index:1200;display:grid;grid-template-columns:repeat(8,16px);gap:4px;padding:8px;background:var(--bb-ui-menu-bg,#fff);-webkit-backdrop-filter:var(--bb-ui-pop-filter,none);backdrop-filter:var(--bb-ui-pop-filter,none);border:1px solid var(--bb-ui-pop-border,var(--bb-ui-border,#e3e3e0));border-radius:8px;box-shadow:0 8px 28px rgba(0,0,0,.16)}
255
286
  .bb-color-pop[hidden]{display:none}
256
287
  .bb-color-swatch{width:16px;height:16px;padding:0;border:1px solid rgba(0,0,0,.18);border-radius:3px;cursor:pointer}
257
288
  .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
289
  .bb-color-none:hover{background:var(--bb-ui-hover,#f1efe8)}
259
290
  .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}
291
+ .bb-toolbar-split{display:flex;align-items:center;gap:0}
292
+ .bb-toolbar-split .bb-toolbar-btn{min-width:26px;padding:0 5px}
293
+ .bb-split-arrow{min-width:14px !important;width:15px;padding:0 !important}
294
+ .bb-split-arrow svg{display:block}
295
+ .bb-split-pop{position:absolute;z-index:1200;display:grid;grid-template-columns:repeat(3,auto);gap:8px;padding:10px;background:var(--bb-ui-menu-bg,#fff);-webkit-backdrop-filter:var(--bb-ui-pop-filter,none);backdrop-filter:var(--bb-ui-pop-filter,none);border:1px solid var(--bb-ui-pop-border,var(--bb-ui-border,#e3e3e0));border-radius:10px;box-shadow:0 8px 28px rgba(0,0,0,.16)}
296
+ .bb-split-pop[hidden]{display:none}
297
+ .bb-split-card{display:flex;flex-direction:column;gap:6px;width:76px;padding:9px 8px;border:1px solid var(--bb-ui-border,#e3e3e0);border-radius:6px;background:transparent;cursor:pointer;font-family:inherit}
298
+ .bb-split-card:hover{background:var(--bb-ui-hover,#f1efe8)}
299
+ .bb-split-card.is-selected{background:var(--bb-ui-active-bg,#e6f1fb);border-color:var(--bb-ui-active-border,#b5d4f4)}
300
+ .bb-split-row{display:flex;align-items:center;gap:4px}
301
+ .bb-split-row[data-lvl="1"]{padding-left:9px}
302
+ .bb-split-row[data-lvl="2"]{padding-left:18px}
303
+ .bb-split-marker{flex:none;min-width:14px;font-size:9px;line-height:1.1;text-align:right;color:var(--bb-ui-fg,#2c2c2a);white-space:nowrap}
304
+ .bb-split-bar{flex:1;height:3px;border-radius:2px;background:var(--bb-ui-border,#d9d9d4)}
260
305
  `;
261
306
  function defaultToolbarGroups(commands) {
262
307
  const names = [...commands].map((c) => c.name);
@@ -276,9 +321,12 @@ function mountToolbar(host, editor, options = {}) {
276
321
  const buttons = [];
277
322
  const selects = [];
278
323
  const colors = [];
324
+ const splits = [];
279
325
  let latest = null;
280
326
  for (const group of groups) {
281
- const entries = group.filter((e) => typeof e !== "string" || editor.commands.has(e));
327
+ const entries = group.filter(
328
+ (e) => typeof e !== "string" || editor.commands.has(e)
329
+ );
282
330
  if (entries.length === 0) continue;
283
331
  const groupEl = document.createElement("div");
284
332
  groupEl.className = "bb-toolbar-group";
@@ -305,8 +353,8 @@ function mountToolbar(host, editor, options = {}) {
305
353
  continue;
306
354
  }
307
355
  if (typeof entry !== "string" && entry.kind === "color") {
308
- const wrap2 = document.createElement("div");
309
- wrap2.className = "bb-toolbar-color";
356
+ const colorWrap = document.createElement("div");
357
+ colorWrap.className = "bb-toolbar-color";
310
358
  const btn2 = document.createElement("button");
311
359
  btn2.type = "button";
312
360
  btn2.className = "bb-toolbar-btn";
@@ -349,11 +397,22 @@ function mountToolbar(host, editor, options = {}) {
349
397
  btn2.addEventListener("mousedown", (e) => e.preventDefault());
350
398
  btn2.addEventListener("click", () => {
351
399
  const open = pop2.hidden;
352
- root.querySelectorAll(".bb-color-pop").forEach((p) => p.hidden = true);
400
+ wrap.querySelectorAll(".bb-color-pop,.bb-split-pop").forEach((p) => p.hidden = true);
353
401
  pop2.hidden = !open;
354
402
  if (!pop2.hidden) {
403
+ const wrapRect = wrap.getBoundingClientRect();
404
+ const btnRect = btn2.getBoundingClientRect();
405
+ pop2.style.top = `${btnRect.bottom - wrapRect.top + 4}px`;
406
+ const left = Math.max(
407
+ 0,
408
+ Math.min(
409
+ btnRect.left - wrapRect.left,
410
+ wrap.clientWidth - pop2.offsetWidth - 4
411
+ )
412
+ );
413
+ pop2.style.left = `${left}px`;
355
414
  const onDoc = (ev) => {
356
- if (!wrap2.contains(ev.target)) {
415
+ if (!colorWrap.contains(ev.target) && !pop2.contains(ev.target)) {
357
416
  pop2.hidden = true;
358
417
  document.removeEventListener("pointerdown", onDoc, true);
359
418
  }
@@ -361,11 +420,100 @@ function mountToolbar(host, editor, options = {}) {
361
420
  document.addEventListener("pointerdown", onDoc, true);
362
421
  }
363
422
  });
364
- wrap2.append(btn2, pop2);
365
- groupEl.appendChild(wrap2);
423
+ colorWrap.appendChild(btn2);
424
+ wrap.appendChild(pop2);
425
+ groupEl.appendChild(colorWrap);
366
426
  colors.push({ spec: entry, bar });
367
427
  continue;
368
428
  }
429
+ if (typeof entry !== "string" && entry.kind === "split") {
430
+ const splitWrap = document.createElement("div");
431
+ splitWrap.className = "bb-toolbar-split";
432
+ const item2 = items[entry.name] ?? { title: entry.name };
433
+ const main = document.createElement("button");
434
+ main.type = "button";
435
+ main.className = "bb-toolbar-btn";
436
+ main.title = item2.title;
437
+ main.setAttribute("aria-label", item2.title);
438
+ if (item2.svg) main.innerHTML = item2.svg;
439
+ else main.textContent = item2.label ?? entry.name;
440
+ main.addEventListener("mousedown", (e) => e.preventDefault());
441
+ main.addEventListener("click", () => {
442
+ if (!latest) return;
443
+ editor.commands.get(entry.name)?.run(latest, (tr) => editor.dispatch(tr));
444
+ editor.focus();
445
+ });
446
+ buttons.push({ name: entry.name, el: main });
447
+ const arrow = document.createElement("button");
448
+ arrow.type = "button";
449
+ arrow.className = "bb-toolbar-btn bb-split-arrow";
450
+ arrow.title = `${item2.title} styles`;
451
+ arrow.setAttribute("aria-label", `${item2.title} styles`);
452
+ arrow.setAttribute("aria-haspopup", "true");
453
+ arrow.innerHTML = '<svg viewBox="0 0 8 8" width="8" height="8" fill="currentColor" aria-hidden="true"><path d="M1 2.5h6L4 6z"/></svg>';
454
+ const pop2 = document.createElement("div");
455
+ pop2.className = "bb-split-pop";
456
+ pop2.hidden = true;
457
+ const cards = [];
458
+ for (const opt of entry.options) {
459
+ const card = document.createElement("button");
460
+ card.type = "button";
461
+ card.className = "bb-split-card";
462
+ card.dataset["value"] = opt.value;
463
+ if (opt.title) card.title = opt.title;
464
+ opt.rows.forEach((marker, i) => {
465
+ const row = document.createElement("span");
466
+ row.className = "bb-split-row";
467
+ row.dataset["lvl"] = String(i);
468
+ const m = document.createElement("span");
469
+ m.className = "bb-split-marker";
470
+ m.textContent = marker;
471
+ const bar = document.createElement("span");
472
+ bar.className = "bb-split-bar";
473
+ row.append(m, bar);
474
+ card.appendChild(row);
475
+ });
476
+ card.addEventListener("mousedown", (e) => e.preventDefault());
477
+ card.addEventListener("click", () => {
478
+ entry.onSelect(opt.value);
479
+ pop2.hidden = true;
480
+ editor.focus();
481
+ });
482
+ pop2.appendChild(card);
483
+ cards.push(card);
484
+ }
485
+ arrow.addEventListener("mousedown", (e) => e.preventDefault());
486
+ arrow.addEventListener("click", () => {
487
+ const open = pop2.hidden;
488
+ wrap.querySelectorAll(".bb-split-pop,.bb-color-pop").forEach((p) => p.hidden = true);
489
+ pop2.hidden = !open;
490
+ if (!pop2.hidden) {
491
+ const wrapRect = wrap.getBoundingClientRect();
492
+ const btnRect = arrow.getBoundingClientRect();
493
+ pop2.style.top = `${btnRect.bottom - wrapRect.top + 4}px`;
494
+ const left = Math.max(
495
+ 0,
496
+ Math.min(
497
+ btnRect.left - wrapRect.left,
498
+ wrap.clientWidth - pop2.offsetWidth - 4
499
+ )
500
+ );
501
+ pop2.style.left = `${left}px`;
502
+ const onDoc = (ev) => {
503
+ if (!splitWrap.contains(ev.target) && !pop2.contains(ev.target)) {
504
+ pop2.hidden = true;
505
+ document.removeEventListener("pointerdown", onDoc, true);
506
+ }
507
+ };
508
+ document.addEventListener("pointerdown", onDoc, true);
509
+ }
510
+ });
511
+ splitWrap.append(main, arrow);
512
+ wrap.appendChild(pop2);
513
+ groupEl.appendChild(splitWrap);
514
+ splits.push({ spec: entry, cards });
515
+ continue;
516
+ }
369
517
  const item = items[entry] ?? { title: entry };
370
518
  const btn = document.createElement("button");
371
519
  btn.type = "button";
@@ -417,9 +565,12 @@ function mountToolbar(host, editor, options = {}) {
417
565
  while (pop.firstChild) root.insertBefore(pop.firstChild, moreBtn);
418
566
  moreBtn.style.display = "none";
419
567
  closePop();
568
+ wrap.querySelectorAll(".bb-color-pop,.bb-split-pop").forEach((p) => p.hidden = true);
420
569
  if (fits()) return;
421
570
  moreBtn.style.display = "";
422
- const groupEls = Array.from(root.children).filter((el) => el.classList.contains("bb-toolbar-group"));
571
+ const groupEls = Array.from(root.children).filter(
572
+ (el) => el.classList.contains("bb-toolbar-group")
573
+ );
423
574
  for (let i = groupEls.length - 1; i >= 0 && !fits(); i--) {
424
575
  pop.insertBefore(groupEls[i], pop.firstChild);
425
576
  }
@@ -442,7 +593,16 @@ function mountToolbar(host, editor, options = {}) {
442
593
  el.disabled = cmd.isEnabled ? !cmd.isEnabled(state) : false;
443
594
  }
444
595
  for (const { spec, el } of selects) el.value = spec.value(state);
445
- for (const { spec, bar } of colors) bar.style.background = spec.value(state) ?? "";
596
+ for (const { spec, bar } of colors)
597
+ bar.style.background = spec.value(state) ?? "";
598
+ for (const { spec, cards } of splits) {
599
+ const selected = spec.value(state);
600
+ for (const card of cards)
601
+ card.classList.toggle(
602
+ "is-selected",
603
+ selected != null && card.dataset["value"] === selected
604
+ );
605
+ }
446
606
  };
447
607
  const off = editor.onChange((c) => refresh(c.state));
448
608
  try {
@@ -856,9 +1016,16 @@ function showContextMenu(entries, at) {
856
1016
  }
857
1017
  document.body.appendChild(el);
858
1018
  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)"));
1019
+ const pad = 4;
1020
+ const fitsX = (v) => v >= pad && v + r.width <= window.innerWidth - pad;
1021
+ const fitsY = (v) => v >= pad && v + r.height <= window.innerHeight - pad;
1022
+ const x = fitsX(at.x) ? at.x : fitsX(at.x - r.width) ? at.x - r.width : Math.max(pad, Math.min(at.x, window.innerWidth - r.width - pad));
1023
+ const y = fitsY(at.y) ? at.y : fitsY(at.y - r.height) ? at.y - r.height : Math.max(pad, Math.min(at.y, window.innerHeight - r.height - pad));
1024
+ el.style.left = `${x}px`;
1025
+ el.style.top = `${y}px`;
1026
+ const items = () => Array.from(
1027
+ el.querySelectorAll(".bb-ctx-item:not(:disabled)")
1028
+ );
862
1029
  const onDown = (e) => {
863
1030
  if (!el.contains(e.target)) closeCurrent();
864
1031
  };
@@ -888,6 +1055,213 @@ function showContextMenu(entries, at) {
888
1055
  return { close: closeCurrent };
889
1056
  }
890
1057
 
1058
+ // packages/ui/src/lib/link-panel.ts
1059
+ var STYLE6 = `
1060
+ .bb-linkpanel{position:fixed;z-index:1200;width:300px;padding:8px;background:var(--bb-ui-menu-bg,#fff);-webkit-backdrop-filter:var(--bb-ui-pop-filter,none);backdrop-filter:var(--bb-ui-pop-filter,none);color:var(--bb-ui-fg,#2c2c2a);border:1px solid var(--bb-ui-pop-border,var(--bb-ui-border,#e3e3e0));border-radius:10px;box-shadow:0 8px 28px rgba(0,0,0,.16);font-family:var(--bb-ui-font,system-ui,-apple-system,sans-serif);box-sizing:border-box}
1061
+ .bb-linkpanel *{box-sizing:border-box}
1062
+ .bb-linkpanel-row{display:flex;align-items:center;gap:8px}
1063
+ .bb-linkpanel-form{display:flex;flex-direction:column;gap:8px}
1064
+ .bb-linkpanel-field{display:flex;align-items:center;gap:7px;border:1px solid var(--bb-ui-border,#e3e3e0);border-radius:7px;padding:0 9px;background:var(--bb-ui-bg,#fff)}
1065
+ .bb-linkpanel-field:focus-within{border-color:var(--bb-ui-accent,#d85a30)}
1066
+ .bb-linkpanel-field svg{flex:none;opacity:.55}
1067
+ .bb-linkpanel-input{flex:1;min-width:0;height:30px;border:0;background:transparent;color:inherit;font:inherit;font-size:13px;outline:none}
1068
+ .bb-linkpanel-href{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:13px;color:var(--bb-ui-accent,#d85a30)}
1069
+ .bb-linkpanel-btn{display:inline-flex;align-items:center;justify-content:center;width:28px;height:28px;padding:0;border:0;border-radius:6px;background:transparent;color:inherit;cursor:pointer;flex:none}
1070
+ .bb-linkpanel-btn:hover{background:var(--bb-ui-hover,#f1efe8)}
1071
+ .bb-linkpanel-apply{font:inherit;font-size:13px;font-weight:600;color:var(--bb-ui-accent,#d85a30);background:transparent;border:0;border-radius:6px;padding:6px 8px;cursor:pointer;flex:none}
1072
+ .bb-linkpanel-apply:hover{background:var(--bb-ui-hover,#f1efe8)}
1073
+ .bb-linkpanel-apply:disabled{opacity:.4;cursor:default}
1074
+ `;
1075
+ var ICONS = {
1076
+ link: '<svg viewBox="0 0 16 16" width="15" height="15" fill="none" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M6.5 9.5 9.5 6.5"/><path d="M7.5 4.5 9 3a2.47 2.47 0 0 1 3.5 0L13 3.5a2.47 2.47 0 0 1 0 3.5l-1.5 1.5"/><path d="M8.5 11.5 7 13a2.47 2.47 0 0 1-3.5 0L3 12.5a2.47 2.47 0 0 1 0-3.5l1.5-1.5"/></svg>',
1077
+ text: '<svg viewBox="0 0 16 16" width="15" height="15" fill="none" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M3 4V2.5h10V4M8 2.5v11M6 13.5h4"/></svg>',
1078
+ copy: '<svg viewBox="0 0 16 16" width="15" height="15" fill="none" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="5.5" y="5.5" width="8" height="8" rx="1.5"/><path d="M10.5 5.5v-2a1 1 0 0 0-1-1h-6a1 1 0 0 0-1 1v6a1 1 0 0 0 1 1h2"/></svg>',
1079
+ edit: '<svg viewBox="0 0 16 16" width="15" height="15" fill="none" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m9.5 3.5 3 3L6 13l-3.5.5L3 10z"/><path d="m11 2 .9-.9a1.55 1.55 0 0 1 2.2 0l.8.8a1.55 1.55 0 0 1 0 2.2L14 5"/></svg>',
1080
+ unlink: '<svg viewBox="0 0 16 16" width="15" height="15" fill="none" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M7.5 4.5 9 3a2.47 2.47 0 0 1 3.5 0L13 3.5a2.47 2.47 0 0 1 0 3.5l-1.5 1.5"/><path d="M8.5 11.5 7 13a2.47 2.47 0 0 1-3.5 0L3 12.5a2.47 2.47 0 0 1 0-3.5l1.5-1.5"/><path d="m3 3 10 10"/></svg>'
1081
+ };
1082
+ var current2 = null;
1083
+ var currentKey = null;
1084
+ var currentHandle = null;
1085
+ var pendingClose = null;
1086
+ function cancelPendingClose() {
1087
+ if (pendingClose !== null) {
1088
+ clearTimeout(pendingClose);
1089
+ pendingClose = null;
1090
+ }
1091
+ }
1092
+ function closeCurrent2() {
1093
+ cancelPendingClose();
1094
+ if (current2) {
1095
+ const dispose = current2;
1096
+ current2 = null;
1097
+ currentKey = null;
1098
+ currentHandle = null;
1099
+ dispose();
1100
+ }
1101
+ }
1102
+ function place(el, anchor) {
1103
+ const r = el.getBoundingClientRect();
1104
+ const pad = 6;
1105
+ const gap = 6;
1106
+ const x = Math.max(
1107
+ pad,
1108
+ Math.min(anchor.x, window.innerWidth - r.width - pad)
1109
+ );
1110
+ const below = anchor.y + anchor.height + gap;
1111
+ const above = anchor.y - gap - r.height;
1112
+ const y = below + r.height <= window.innerHeight - pad || above < pad ? Math.min(below, window.innerHeight - r.height - pad) : above;
1113
+ el.style.left = `${x}px`;
1114
+ el.style.top = `${y}px`;
1115
+ }
1116
+ function showLinkPanel(opts) {
1117
+ injectStyle("bb-ui-linkpanel-styles", STYLE6);
1118
+ if (opts.key && current2 && currentKey === opts.key && currentHandle) {
1119
+ cancelPendingClose();
1120
+ return currentHandle;
1121
+ }
1122
+ closeCurrent2();
1123
+ const el = document.createElement("div");
1124
+ el.className = "bb-linkpanel";
1125
+ el.setAttribute("role", "dialog");
1126
+ el.setAttribute("aria-label", "Link");
1127
+ const iconBtn = (svg, label, onClick) => {
1128
+ const b = document.createElement("button");
1129
+ b.type = "button";
1130
+ b.className = "bb-linkpanel-btn";
1131
+ b.title = label;
1132
+ b.setAttribute("aria-label", label);
1133
+ b.innerHTML = svg;
1134
+ b.addEventListener("mousedown", (e) => e.preventDefault());
1135
+ b.addEventListener("click", onClick);
1136
+ return b;
1137
+ };
1138
+ const field = (icon, placeholder, value) => {
1139
+ const wrap = document.createElement("div");
1140
+ wrap.className = "bb-linkpanel-field";
1141
+ wrap.innerHTML = icon;
1142
+ const input = document.createElement("input");
1143
+ input.className = "bb-linkpanel-input";
1144
+ input.type = "text";
1145
+ input.placeholder = placeholder;
1146
+ input.value = value;
1147
+ input.spellcheck = false;
1148
+ input.autocomplete = "off";
1149
+ wrap.appendChild(input);
1150
+ return { wrap, input };
1151
+ };
1152
+ const renderView = (existing) => {
1153
+ el.textContent = "";
1154
+ const row = document.createElement("div");
1155
+ row.className = "bb-linkpanel-row";
1156
+ const href = document.createElement("span");
1157
+ href.className = "bb-linkpanel-href";
1158
+ href.textContent = existing.href;
1159
+ href.title = existing.href;
1160
+ row.appendChild(href);
1161
+ row.appendChild(
1162
+ iconBtn(ICONS.copy, "Copy link", () => {
1163
+ void navigator.clipboard?.writeText(existing.href).catch(() => void 0);
1164
+ closeCurrent2();
1165
+ })
1166
+ );
1167
+ row.appendChild(
1168
+ iconBtn(ICONS.edit, "Edit link", () => renderForm(existing))
1169
+ );
1170
+ row.appendChild(
1171
+ iconBtn(ICONS.unlink, "Remove link", () => {
1172
+ closeCurrent2();
1173
+ opts.onUnlink?.();
1174
+ })
1175
+ );
1176
+ el.appendChild(row);
1177
+ place(el, opts.anchor);
1178
+ };
1179
+ const renderForm = (prefill) => {
1180
+ el.textContent = "";
1181
+ const form = document.createElement("div");
1182
+ form.className = "bb-linkpanel-form";
1183
+ const withText = !opts.hasSelection;
1184
+ let textInput = null;
1185
+ if (withText) {
1186
+ const t = field(ICONS.text, "Text (optional)", prefill?.text ?? "");
1187
+ textInput = t.input;
1188
+ form.appendChild(t.wrap);
1189
+ }
1190
+ const u = field(ICONS.link, "Paste or type a link", prefill?.href ?? "");
1191
+ const row = document.createElement("div");
1192
+ row.className = "bb-linkpanel-row";
1193
+ row.appendChild(u.wrap);
1194
+ u.wrap.style.flex = "1";
1195
+ const apply = document.createElement("button");
1196
+ apply.type = "button";
1197
+ apply.className = "bb-linkpanel-apply";
1198
+ apply.textContent = "Apply";
1199
+ apply.disabled = !u.input.value.trim();
1200
+ u.input.addEventListener("input", () => {
1201
+ apply.disabled = !u.input.value.trim();
1202
+ });
1203
+ const submit = () => {
1204
+ const href = u.input.value.trim();
1205
+ if (!href) return;
1206
+ const text = textInput?.value ?? void 0;
1207
+ closeCurrent2();
1208
+ opts.onApply(href, text);
1209
+ };
1210
+ apply.addEventListener("click", submit);
1211
+ for (const input of [textInput, u.input]) {
1212
+ input?.addEventListener("keydown", (e) => {
1213
+ if (e.key === "Enter") {
1214
+ e.preventDefault();
1215
+ submit();
1216
+ }
1217
+ });
1218
+ }
1219
+ row.appendChild(apply);
1220
+ form.appendChild(row);
1221
+ el.appendChild(form);
1222
+ place(el, opts.anchor);
1223
+ u.input.focus();
1224
+ u.input.select();
1225
+ };
1226
+ document.body.appendChild(el);
1227
+ if (opts.existing) renderView(opts.existing);
1228
+ else renderForm(null);
1229
+ const onDown = (e) => {
1230
+ if (el.contains(e.target)) return;
1231
+ if (opts.key) {
1232
+ cancelPendingClose();
1233
+ pendingClose = setTimeout(closeCurrent2, 80);
1234
+ } else {
1235
+ closeCurrent2();
1236
+ }
1237
+ };
1238
+ const onKey = (e) => {
1239
+ if (e.key === "Escape") closeCurrent2();
1240
+ };
1241
+ const onScroll = () => closeCurrent2();
1242
+ setTimeout(() => {
1243
+ document.addEventListener("pointerdown", onDown, true);
1244
+ document.addEventListener("keydown", onKey, true);
1245
+ window.addEventListener("scroll", onScroll, true);
1246
+ }, 0);
1247
+ const dispose = () => {
1248
+ document.removeEventListener("pointerdown", onDown, true);
1249
+ document.removeEventListener("keydown", onKey, true);
1250
+ window.removeEventListener("scroll", onScroll, true);
1251
+ el.remove();
1252
+ };
1253
+ current2 = dispose;
1254
+ currentKey = opts.key ?? null;
1255
+ const handle = {
1256
+ close: () => {
1257
+ if (current2 === dispose) closeCurrent2();
1258
+ },
1259
+ isOpen: () => current2 === dispose
1260
+ };
1261
+ currentHandle = handle;
1262
+ return handle;
1263
+ }
1264
+
891
1265
  // packages/ui/src/lib/cell-properties.ts
892
1266
  var FILL_PRESETS = ["#FCEBEB", "#FAEEDA", "#EAF3DE", "#E6F1FB", "#EEEDFE", "#FBEAF0", "#E1F5EE", "#F1EFE8", "#D3D1C7"];
893
1267
  var PEN_COLORS = ["#000000", "#5F5E5A", "#B0B0B0", "#E24B4A", "#185FA5", "#1D9E75", "#BA7517"];
@@ -915,7 +1289,7 @@ var PRESETS = [
915
1289
  { key: "insideH", label: "Inside horizontal", sides: [0, 0, 0, 0, 1, 0], inside: true },
916
1290
  { key: "insideV", label: "Inside vertical", sides: [0, 0, 0, 0, 0, 1], inside: true }
917
1291
  ];
918
- var STYLE6 = `
1292
+ var STYLE7 = `
919
1293
  .bb-cp{display:flex;flex-direction:column;gap:16px;min-width:320px;color:var(--bb-ui-fg,#2c2c2a)}
920
1294
  .bb-cp *{box-sizing:border-box}
921
1295
  .bb-cp-label{font-size:12px;opacity:.7;margin-bottom:8px}
@@ -956,7 +1330,7 @@ var section = (label) => {
956
1330
  return sec;
957
1331
  };
958
1332
  function openCellProperties({ initial, singleCell, onApply }) {
959
- injectStyle("bb-ui-cellprops-styles", STYLE6);
1333
+ injectStyle("bb-ui-cellprops-styles", STYLE7);
960
1334
  let background = initial.background;
961
1335
  let vAlign = initial.vAlign;
962
1336
  let penWidthPt = 1;
@@ -1129,7 +1503,7 @@ function openCellProperties({ initial, singleCell, onApply }) {
1129
1503
  }
1130
1504
 
1131
1505
  // packages/ui/src/lib/table-grid.ts
1132
- var STYLE7 = `
1506
+ var STYLE8 = `
1133
1507
  .bb-grid{display:flex;flex-direction:column;gap:6px;user-select:none}
1134
1508
  .bb-grid-cells{display:grid;gap:2px}
1135
1509
  .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}
@@ -1137,7 +1511,7 @@ var STYLE7 = `
1137
1511
  .bb-grid-label{font-size:12px;text-align:center;opacity:.7}
1138
1512
  `;
1139
1513
  function tableGridPicker(options) {
1140
- injectStyle("bb-ui-grid-styles", STYLE7);
1514
+ injectStyle("bb-ui-grid-styles", STYLE8);
1141
1515
  const maxRows = options.maxRows ?? 8;
1142
1516
  const maxCols = options.maxCols ?? 10;
1143
1517
  const root = document.createElement("div");
@@ -1183,5 +1557,6 @@ function tableGridPicker(options) {
1183
1557
  openCellProperties,
1184
1558
  promptDialog,
1185
1559
  showContextMenu,
1560
+ showLinkPanel,
1186
1561
  tableGridPicker
1187
1562
  });
package/dist/index.d.ts CHANGED
@@ -4,6 +4,7 @@ export * from './lib/toolbar.js';
4
4
  export * from './lib/menubar.js';
5
5
  export * from './lib/find.js';
6
6
  export * from './lib/context-menu.js';
7
+ export * from './lib/link-panel.js';
7
8
  export * from './lib/cell-properties.js';
8
9
  export * from './lib/table-grid.js';
9
10
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,YAAY,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AACrE,cAAc,iBAAiB,CAAC;AAChC,cAAc,kBAAkB,CAAC;AACjC,cAAc,kBAAkB,CAAC;AACjC,cAAc,eAAe,CAAC;AAC9B,cAAc,uBAAuB,CAAC;AACtC,cAAc,0BAA0B,CAAC;AACzC,cAAc,qBAAqB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,YAAY,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AACrE,cAAc,iBAAiB,CAAC;AAChC,cAAc,kBAAkB,CAAC;AACjC,cAAc,kBAAkB,CAAC;AACjC,cAAc,eAAe,CAAC;AAC9B,cAAc,uBAAuB,CAAC;AACtC,cAAc,qBAAqB,CAAC;AACpC,cAAc,0BAA0B,CAAC;AACzC,cAAc,qBAAqB,CAAC"}
package/dist/index.js CHANGED
@@ -159,7 +159,9 @@ function promptDialog(options) {
159
159
  }
160
160
 
161
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>`;
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(
163
+ ([x1, x2], i) => `<line x1="${x1}" y1="${4 + i * 4}" x2="${x2}" y2="${4 + i * 4}"/>`
164
+ ).join("") + `</g></svg>`;
163
165
  var DEFAULT_ITEMS = {
164
166
  undo: {
165
167
  title: "Undo",
@@ -185,10 +187,38 @@ var DEFAULT_ITEMS = {
185
187
  title: "Subscript",
186
188
  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
189
  },
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]]) },
190
+ "align-left": {
191
+ title: "Align left",
192
+ svg: alignSvg([
193
+ [2, 14],
194
+ [2, 9],
195
+ [2, 12]
196
+ ])
197
+ },
198
+ "align-center": {
199
+ title: "Center",
200
+ svg: alignSvg([
201
+ [2, 14],
202
+ [4, 12],
203
+ [3, 13]
204
+ ])
205
+ },
206
+ "align-right": {
207
+ title: "Align right",
208
+ svg: alignSvg([
209
+ [2, 14],
210
+ [7, 14],
211
+ [4, 14]
212
+ ])
213
+ },
214
+ "align-justify": {
215
+ title: "Justify",
216
+ svg: alignSvg([
217
+ [2, 14],
218
+ [2, 14],
219
+ [2, 14]
220
+ ])
221
+ },
192
222
  "bullet-list": {
193
223
  title: "Bullet list",
194
224
  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>'
@@ -216,12 +246,26 @@ var STYLE2 = `
216
246
  .bb-toolbar-color{position:relative}
217
247
  .bb-toolbar-color .bb-color-glyph{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:1px;line-height:1;font-size:13px}
218
248
  .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)}
249
+ .bb-color-pop{position:absolute;z-index:1200;display:grid;grid-template-columns:repeat(8,16px);gap:4px;padding:8px;background:var(--bb-ui-menu-bg,#fff);-webkit-backdrop-filter:var(--bb-ui-pop-filter,none);backdrop-filter:var(--bb-ui-pop-filter,none);border:1px solid var(--bb-ui-pop-border,var(--bb-ui-border,#e3e3e0));border-radius:8px;box-shadow:0 8px 28px rgba(0,0,0,.16)}
220
250
  .bb-color-pop[hidden]{display:none}
221
251
  .bb-color-swatch{width:16px;height:16px;padding:0;border:1px solid rgba(0,0,0,.18);border-radius:3px;cursor:pointer}
222
252
  .bb-color-none{grid-column:1/-1;font:inherit;font-size:12px;padding:3px 0;border:1px solid var(--bb-ui-border,#e3e3e0);border-radius:5px;background:var(--bb-ui-bg,#fff);cursor:pointer}
223
253
  .bb-color-none:hover{background:var(--bb-ui-hover,#f1efe8)}
224
254
  .bb-i-bold{font-weight:700}.bb-i-italic{font-style:italic}.bb-i-underline{text-decoration:underline}.bb-i-strike{text-decoration:line-through}
255
+ .bb-toolbar-split{display:flex;align-items:center;gap:0}
256
+ .bb-toolbar-split .bb-toolbar-btn{min-width:26px;padding:0 5px}
257
+ .bb-split-arrow{min-width:14px !important;width:15px;padding:0 !important}
258
+ .bb-split-arrow svg{display:block}
259
+ .bb-split-pop{position:absolute;z-index:1200;display:grid;grid-template-columns:repeat(3,auto);gap:8px;padding:10px;background:var(--bb-ui-menu-bg,#fff);-webkit-backdrop-filter:var(--bb-ui-pop-filter,none);backdrop-filter:var(--bb-ui-pop-filter,none);border:1px solid var(--bb-ui-pop-border,var(--bb-ui-border,#e3e3e0));border-radius:10px;box-shadow:0 8px 28px rgba(0,0,0,.16)}
260
+ .bb-split-pop[hidden]{display:none}
261
+ .bb-split-card{display:flex;flex-direction:column;gap:6px;width:76px;padding:9px 8px;border:1px solid var(--bb-ui-border,#e3e3e0);border-radius:6px;background:transparent;cursor:pointer;font-family:inherit}
262
+ .bb-split-card:hover{background:var(--bb-ui-hover,#f1efe8)}
263
+ .bb-split-card.is-selected{background:var(--bb-ui-active-bg,#e6f1fb);border-color:var(--bb-ui-active-border,#b5d4f4)}
264
+ .bb-split-row{display:flex;align-items:center;gap:4px}
265
+ .bb-split-row[data-lvl="1"]{padding-left:9px}
266
+ .bb-split-row[data-lvl="2"]{padding-left:18px}
267
+ .bb-split-marker{flex:none;min-width:14px;font-size:9px;line-height:1.1;text-align:right;color:var(--bb-ui-fg,#2c2c2a);white-space:nowrap}
268
+ .bb-split-bar{flex:1;height:3px;border-radius:2px;background:var(--bb-ui-border,#d9d9d4)}
225
269
  `;
226
270
  function defaultToolbarGroups(commands) {
227
271
  const names = [...commands].map((c) => c.name);
@@ -241,9 +285,12 @@ function mountToolbar(host, editor, options = {}) {
241
285
  const buttons = [];
242
286
  const selects = [];
243
287
  const colors = [];
288
+ const splits = [];
244
289
  let latest = null;
245
290
  for (const group of groups) {
246
- const entries = group.filter((e) => typeof e !== "string" || editor.commands.has(e));
291
+ const entries = group.filter(
292
+ (e) => typeof e !== "string" || editor.commands.has(e)
293
+ );
247
294
  if (entries.length === 0) continue;
248
295
  const groupEl = document.createElement("div");
249
296
  groupEl.className = "bb-toolbar-group";
@@ -270,8 +317,8 @@ function mountToolbar(host, editor, options = {}) {
270
317
  continue;
271
318
  }
272
319
  if (typeof entry !== "string" && entry.kind === "color") {
273
- const wrap2 = document.createElement("div");
274
- wrap2.className = "bb-toolbar-color";
320
+ const colorWrap = document.createElement("div");
321
+ colorWrap.className = "bb-toolbar-color";
275
322
  const btn2 = document.createElement("button");
276
323
  btn2.type = "button";
277
324
  btn2.className = "bb-toolbar-btn";
@@ -314,11 +361,22 @@ function mountToolbar(host, editor, options = {}) {
314
361
  btn2.addEventListener("mousedown", (e) => e.preventDefault());
315
362
  btn2.addEventListener("click", () => {
316
363
  const open = pop2.hidden;
317
- root.querySelectorAll(".bb-color-pop").forEach((p) => p.hidden = true);
364
+ wrap.querySelectorAll(".bb-color-pop,.bb-split-pop").forEach((p) => p.hidden = true);
318
365
  pop2.hidden = !open;
319
366
  if (!pop2.hidden) {
367
+ const wrapRect = wrap.getBoundingClientRect();
368
+ const btnRect = btn2.getBoundingClientRect();
369
+ pop2.style.top = `${btnRect.bottom - wrapRect.top + 4}px`;
370
+ const left = Math.max(
371
+ 0,
372
+ Math.min(
373
+ btnRect.left - wrapRect.left,
374
+ wrap.clientWidth - pop2.offsetWidth - 4
375
+ )
376
+ );
377
+ pop2.style.left = `${left}px`;
320
378
  const onDoc = (ev) => {
321
- if (!wrap2.contains(ev.target)) {
379
+ if (!colorWrap.contains(ev.target) && !pop2.contains(ev.target)) {
322
380
  pop2.hidden = true;
323
381
  document.removeEventListener("pointerdown", onDoc, true);
324
382
  }
@@ -326,11 +384,100 @@ function mountToolbar(host, editor, options = {}) {
326
384
  document.addEventListener("pointerdown", onDoc, true);
327
385
  }
328
386
  });
329
- wrap2.append(btn2, pop2);
330
- groupEl.appendChild(wrap2);
387
+ colorWrap.appendChild(btn2);
388
+ wrap.appendChild(pop2);
389
+ groupEl.appendChild(colorWrap);
331
390
  colors.push({ spec: entry, bar });
332
391
  continue;
333
392
  }
393
+ if (typeof entry !== "string" && entry.kind === "split") {
394
+ const splitWrap = document.createElement("div");
395
+ splitWrap.className = "bb-toolbar-split";
396
+ const item2 = items[entry.name] ?? { title: entry.name };
397
+ const main = document.createElement("button");
398
+ main.type = "button";
399
+ main.className = "bb-toolbar-btn";
400
+ main.title = item2.title;
401
+ main.setAttribute("aria-label", item2.title);
402
+ if (item2.svg) main.innerHTML = item2.svg;
403
+ else main.textContent = item2.label ?? entry.name;
404
+ main.addEventListener("mousedown", (e) => e.preventDefault());
405
+ main.addEventListener("click", () => {
406
+ if (!latest) return;
407
+ editor.commands.get(entry.name)?.run(latest, (tr) => editor.dispatch(tr));
408
+ editor.focus();
409
+ });
410
+ buttons.push({ name: entry.name, el: main });
411
+ const arrow = document.createElement("button");
412
+ arrow.type = "button";
413
+ arrow.className = "bb-toolbar-btn bb-split-arrow";
414
+ arrow.title = `${item2.title} styles`;
415
+ arrow.setAttribute("aria-label", `${item2.title} styles`);
416
+ arrow.setAttribute("aria-haspopup", "true");
417
+ arrow.innerHTML = '<svg viewBox="0 0 8 8" width="8" height="8" fill="currentColor" aria-hidden="true"><path d="M1 2.5h6L4 6z"/></svg>';
418
+ const pop2 = document.createElement("div");
419
+ pop2.className = "bb-split-pop";
420
+ pop2.hidden = true;
421
+ const cards = [];
422
+ for (const opt of entry.options) {
423
+ const card = document.createElement("button");
424
+ card.type = "button";
425
+ card.className = "bb-split-card";
426
+ card.dataset["value"] = opt.value;
427
+ if (opt.title) card.title = opt.title;
428
+ opt.rows.forEach((marker, i) => {
429
+ const row = document.createElement("span");
430
+ row.className = "bb-split-row";
431
+ row.dataset["lvl"] = String(i);
432
+ const m = document.createElement("span");
433
+ m.className = "bb-split-marker";
434
+ m.textContent = marker;
435
+ const bar = document.createElement("span");
436
+ bar.className = "bb-split-bar";
437
+ row.append(m, bar);
438
+ card.appendChild(row);
439
+ });
440
+ card.addEventListener("mousedown", (e) => e.preventDefault());
441
+ card.addEventListener("click", () => {
442
+ entry.onSelect(opt.value);
443
+ pop2.hidden = true;
444
+ editor.focus();
445
+ });
446
+ pop2.appendChild(card);
447
+ cards.push(card);
448
+ }
449
+ arrow.addEventListener("mousedown", (e) => e.preventDefault());
450
+ arrow.addEventListener("click", () => {
451
+ const open = pop2.hidden;
452
+ wrap.querySelectorAll(".bb-split-pop,.bb-color-pop").forEach((p) => p.hidden = true);
453
+ pop2.hidden = !open;
454
+ if (!pop2.hidden) {
455
+ const wrapRect = wrap.getBoundingClientRect();
456
+ const btnRect = arrow.getBoundingClientRect();
457
+ pop2.style.top = `${btnRect.bottom - wrapRect.top + 4}px`;
458
+ const left = Math.max(
459
+ 0,
460
+ Math.min(
461
+ btnRect.left - wrapRect.left,
462
+ wrap.clientWidth - pop2.offsetWidth - 4
463
+ )
464
+ );
465
+ pop2.style.left = `${left}px`;
466
+ const onDoc = (ev) => {
467
+ if (!splitWrap.contains(ev.target) && !pop2.contains(ev.target)) {
468
+ pop2.hidden = true;
469
+ document.removeEventListener("pointerdown", onDoc, true);
470
+ }
471
+ };
472
+ document.addEventListener("pointerdown", onDoc, true);
473
+ }
474
+ });
475
+ splitWrap.append(main, arrow);
476
+ wrap.appendChild(pop2);
477
+ groupEl.appendChild(splitWrap);
478
+ splits.push({ spec: entry, cards });
479
+ continue;
480
+ }
334
481
  const item = items[entry] ?? { title: entry };
335
482
  const btn = document.createElement("button");
336
483
  btn.type = "button";
@@ -382,9 +529,12 @@ function mountToolbar(host, editor, options = {}) {
382
529
  while (pop.firstChild) root.insertBefore(pop.firstChild, moreBtn);
383
530
  moreBtn.style.display = "none";
384
531
  closePop();
532
+ wrap.querySelectorAll(".bb-color-pop,.bb-split-pop").forEach((p) => p.hidden = true);
385
533
  if (fits()) return;
386
534
  moreBtn.style.display = "";
387
- const groupEls = Array.from(root.children).filter((el) => el.classList.contains("bb-toolbar-group"));
535
+ const groupEls = Array.from(root.children).filter(
536
+ (el) => el.classList.contains("bb-toolbar-group")
537
+ );
388
538
  for (let i = groupEls.length - 1; i >= 0 && !fits(); i--) {
389
539
  pop.insertBefore(groupEls[i], pop.firstChild);
390
540
  }
@@ -407,7 +557,16 @@ function mountToolbar(host, editor, options = {}) {
407
557
  el.disabled = cmd.isEnabled ? !cmd.isEnabled(state) : false;
408
558
  }
409
559
  for (const { spec, el } of selects) el.value = spec.value(state);
410
- for (const { spec, bar } of colors) bar.style.background = spec.value(state) ?? "";
560
+ for (const { spec, bar } of colors)
561
+ bar.style.background = spec.value(state) ?? "";
562
+ for (const { spec, cards } of splits) {
563
+ const selected = spec.value(state);
564
+ for (const card of cards)
565
+ card.classList.toggle(
566
+ "is-selected",
567
+ selected != null && card.dataset["value"] === selected
568
+ );
569
+ }
411
570
  };
412
571
  const off = editor.onChange((c) => refresh(c.state));
413
572
  try {
@@ -821,9 +980,16 @@ function showContextMenu(entries, at) {
821
980
  }
822
981
  document.body.appendChild(el);
823
982
  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)"));
983
+ const pad = 4;
984
+ const fitsX = (v) => v >= pad && v + r.width <= window.innerWidth - pad;
985
+ const fitsY = (v) => v >= pad && v + r.height <= window.innerHeight - pad;
986
+ const x = fitsX(at.x) ? at.x : fitsX(at.x - r.width) ? at.x - r.width : Math.max(pad, Math.min(at.x, window.innerWidth - r.width - pad));
987
+ const y = fitsY(at.y) ? at.y : fitsY(at.y - r.height) ? at.y - r.height : Math.max(pad, Math.min(at.y, window.innerHeight - r.height - pad));
988
+ el.style.left = `${x}px`;
989
+ el.style.top = `${y}px`;
990
+ const items = () => Array.from(
991
+ el.querySelectorAll(".bb-ctx-item:not(:disabled)")
992
+ );
827
993
  const onDown = (e) => {
828
994
  if (!el.contains(e.target)) closeCurrent();
829
995
  };
@@ -853,6 +1019,213 @@ function showContextMenu(entries, at) {
853
1019
  return { close: closeCurrent };
854
1020
  }
855
1021
 
1022
+ // packages/ui/src/lib/link-panel.ts
1023
+ var STYLE6 = `
1024
+ .bb-linkpanel{position:fixed;z-index:1200;width:300px;padding:8px;background:var(--bb-ui-menu-bg,#fff);-webkit-backdrop-filter:var(--bb-ui-pop-filter,none);backdrop-filter:var(--bb-ui-pop-filter,none);color:var(--bb-ui-fg,#2c2c2a);border:1px solid var(--bb-ui-pop-border,var(--bb-ui-border,#e3e3e0));border-radius:10px;box-shadow:0 8px 28px rgba(0,0,0,.16);font-family:var(--bb-ui-font,system-ui,-apple-system,sans-serif);box-sizing:border-box}
1025
+ .bb-linkpanel *{box-sizing:border-box}
1026
+ .bb-linkpanel-row{display:flex;align-items:center;gap:8px}
1027
+ .bb-linkpanel-form{display:flex;flex-direction:column;gap:8px}
1028
+ .bb-linkpanel-field{display:flex;align-items:center;gap:7px;border:1px solid var(--bb-ui-border,#e3e3e0);border-radius:7px;padding:0 9px;background:var(--bb-ui-bg,#fff)}
1029
+ .bb-linkpanel-field:focus-within{border-color:var(--bb-ui-accent,#d85a30)}
1030
+ .bb-linkpanel-field svg{flex:none;opacity:.55}
1031
+ .bb-linkpanel-input{flex:1;min-width:0;height:30px;border:0;background:transparent;color:inherit;font:inherit;font-size:13px;outline:none}
1032
+ .bb-linkpanel-href{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:13px;color:var(--bb-ui-accent,#d85a30)}
1033
+ .bb-linkpanel-btn{display:inline-flex;align-items:center;justify-content:center;width:28px;height:28px;padding:0;border:0;border-radius:6px;background:transparent;color:inherit;cursor:pointer;flex:none}
1034
+ .bb-linkpanel-btn:hover{background:var(--bb-ui-hover,#f1efe8)}
1035
+ .bb-linkpanel-apply{font:inherit;font-size:13px;font-weight:600;color:var(--bb-ui-accent,#d85a30);background:transparent;border:0;border-radius:6px;padding:6px 8px;cursor:pointer;flex:none}
1036
+ .bb-linkpanel-apply:hover{background:var(--bb-ui-hover,#f1efe8)}
1037
+ .bb-linkpanel-apply:disabled{opacity:.4;cursor:default}
1038
+ `;
1039
+ var ICONS = {
1040
+ link: '<svg viewBox="0 0 16 16" width="15" height="15" fill="none" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M6.5 9.5 9.5 6.5"/><path d="M7.5 4.5 9 3a2.47 2.47 0 0 1 3.5 0L13 3.5a2.47 2.47 0 0 1 0 3.5l-1.5 1.5"/><path d="M8.5 11.5 7 13a2.47 2.47 0 0 1-3.5 0L3 12.5a2.47 2.47 0 0 1 0-3.5l1.5-1.5"/></svg>',
1041
+ text: '<svg viewBox="0 0 16 16" width="15" height="15" fill="none" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M3 4V2.5h10V4M8 2.5v11M6 13.5h4"/></svg>',
1042
+ copy: '<svg viewBox="0 0 16 16" width="15" height="15" fill="none" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="5.5" y="5.5" width="8" height="8" rx="1.5"/><path d="M10.5 5.5v-2a1 1 0 0 0-1-1h-6a1 1 0 0 0-1 1v6a1 1 0 0 0 1 1h2"/></svg>',
1043
+ edit: '<svg viewBox="0 0 16 16" width="15" height="15" fill="none" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m9.5 3.5 3 3L6 13l-3.5.5L3 10z"/><path d="m11 2 .9-.9a1.55 1.55 0 0 1 2.2 0l.8.8a1.55 1.55 0 0 1 0 2.2L14 5"/></svg>',
1044
+ unlink: '<svg viewBox="0 0 16 16" width="15" height="15" fill="none" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M7.5 4.5 9 3a2.47 2.47 0 0 1 3.5 0L13 3.5a2.47 2.47 0 0 1 0 3.5l-1.5 1.5"/><path d="M8.5 11.5 7 13a2.47 2.47 0 0 1-3.5 0L3 12.5a2.47 2.47 0 0 1 0-3.5l1.5-1.5"/><path d="m3 3 10 10"/></svg>'
1045
+ };
1046
+ var current2 = null;
1047
+ var currentKey = null;
1048
+ var currentHandle = null;
1049
+ var pendingClose = null;
1050
+ function cancelPendingClose() {
1051
+ if (pendingClose !== null) {
1052
+ clearTimeout(pendingClose);
1053
+ pendingClose = null;
1054
+ }
1055
+ }
1056
+ function closeCurrent2() {
1057
+ cancelPendingClose();
1058
+ if (current2) {
1059
+ const dispose = current2;
1060
+ current2 = null;
1061
+ currentKey = null;
1062
+ currentHandle = null;
1063
+ dispose();
1064
+ }
1065
+ }
1066
+ function place(el, anchor) {
1067
+ const r = el.getBoundingClientRect();
1068
+ const pad = 6;
1069
+ const gap = 6;
1070
+ const x = Math.max(
1071
+ pad,
1072
+ Math.min(anchor.x, window.innerWidth - r.width - pad)
1073
+ );
1074
+ const below = anchor.y + anchor.height + gap;
1075
+ const above = anchor.y - gap - r.height;
1076
+ const y = below + r.height <= window.innerHeight - pad || above < pad ? Math.min(below, window.innerHeight - r.height - pad) : above;
1077
+ el.style.left = `${x}px`;
1078
+ el.style.top = `${y}px`;
1079
+ }
1080
+ function showLinkPanel(opts) {
1081
+ injectStyle("bb-ui-linkpanel-styles", STYLE6);
1082
+ if (opts.key && current2 && currentKey === opts.key && currentHandle) {
1083
+ cancelPendingClose();
1084
+ return currentHandle;
1085
+ }
1086
+ closeCurrent2();
1087
+ const el = document.createElement("div");
1088
+ el.className = "bb-linkpanel";
1089
+ el.setAttribute("role", "dialog");
1090
+ el.setAttribute("aria-label", "Link");
1091
+ const iconBtn = (svg, label, onClick) => {
1092
+ const b = document.createElement("button");
1093
+ b.type = "button";
1094
+ b.className = "bb-linkpanel-btn";
1095
+ b.title = label;
1096
+ b.setAttribute("aria-label", label);
1097
+ b.innerHTML = svg;
1098
+ b.addEventListener("mousedown", (e) => e.preventDefault());
1099
+ b.addEventListener("click", onClick);
1100
+ return b;
1101
+ };
1102
+ const field = (icon, placeholder, value) => {
1103
+ const wrap = document.createElement("div");
1104
+ wrap.className = "bb-linkpanel-field";
1105
+ wrap.innerHTML = icon;
1106
+ const input = document.createElement("input");
1107
+ input.className = "bb-linkpanel-input";
1108
+ input.type = "text";
1109
+ input.placeholder = placeholder;
1110
+ input.value = value;
1111
+ input.spellcheck = false;
1112
+ input.autocomplete = "off";
1113
+ wrap.appendChild(input);
1114
+ return { wrap, input };
1115
+ };
1116
+ const renderView = (existing) => {
1117
+ el.textContent = "";
1118
+ const row = document.createElement("div");
1119
+ row.className = "bb-linkpanel-row";
1120
+ const href = document.createElement("span");
1121
+ href.className = "bb-linkpanel-href";
1122
+ href.textContent = existing.href;
1123
+ href.title = existing.href;
1124
+ row.appendChild(href);
1125
+ row.appendChild(
1126
+ iconBtn(ICONS.copy, "Copy link", () => {
1127
+ void navigator.clipboard?.writeText(existing.href).catch(() => void 0);
1128
+ closeCurrent2();
1129
+ })
1130
+ );
1131
+ row.appendChild(
1132
+ iconBtn(ICONS.edit, "Edit link", () => renderForm(existing))
1133
+ );
1134
+ row.appendChild(
1135
+ iconBtn(ICONS.unlink, "Remove link", () => {
1136
+ closeCurrent2();
1137
+ opts.onUnlink?.();
1138
+ })
1139
+ );
1140
+ el.appendChild(row);
1141
+ place(el, opts.anchor);
1142
+ };
1143
+ const renderForm = (prefill) => {
1144
+ el.textContent = "";
1145
+ const form = document.createElement("div");
1146
+ form.className = "bb-linkpanel-form";
1147
+ const withText = !opts.hasSelection;
1148
+ let textInput = null;
1149
+ if (withText) {
1150
+ const t = field(ICONS.text, "Text (optional)", prefill?.text ?? "");
1151
+ textInput = t.input;
1152
+ form.appendChild(t.wrap);
1153
+ }
1154
+ const u = field(ICONS.link, "Paste or type a link", prefill?.href ?? "");
1155
+ const row = document.createElement("div");
1156
+ row.className = "bb-linkpanel-row";
1157
+ row.appendChild(u.wrap);
1158
+ u.wrap.style.flex = "1";
1159
+ const apply = document.createElement("button");
1160
+ apply.type = "button";
1161
+ apply.className = "bb-linkpanel-apply";
1162
+ apply.textContent = "Apply";
1163
+ apply.disabled = !u.input.value.trim();
1164
+ u.input.addEventListener("input", () => {
1165
+ apply.disabled = !u.input.value.trim();
1166
+ });
1167
+ const submit = () => {
1168
+ const href = u.input.value.trim();
1169
+ if (!href) return;
1170
+ const text = textInput?.value ?? void 0;
1171
+ closeCurrent2();
1172
+ opts.onApply(href, text);
1173
+ };
1174
+ apply.addEventListener("click", submit);
1175
+ for (const input of [textInput, u.input]) {
1176
+ input?.addEventListener("keydown", (e) => {
1177
+ if (e.key === "Enter") {
1178
+ e.preventDefault();
1179
+ submit();
1180
+ }
1181
+ });
1182
+ }
1183
+ row.appendChild(apply);
1184
+ form.appendChild(row);
1185
+ el.appendChild(form);
1186
+ place(el, opts.anchor);
1187
+ u.input.focus();
1188
+ u.input.select();
1189
+ };
1190
+ document.body.appendChild(el);
1191
+ if (opts.existing) renderView(opts.existing);
1192
+ else renderForm(null);
1193
+ const onDown = (e) => {
1194
+ if (el.contains(e.target)) return;
1195
+ if (opts.key) {
1196
+ cancelPendingClose();
1197
+ pendingClose = setTimeout(closeCurrent2, 80);
1198
+ } else {
1199
+ closeCurrent2();
1200
+ }
1201
+ };
1202
+ const onKey = (e) => {
1203
+ if (e.key === "Escape") closeCurrent2();
1204
+ };
1205
+ const onScroll = () => closeCurrent2();
1206
+ setTimeout(() => {
1207
+ document.addEventListener("pointerdown", onDown, true);
1208
+ document.addEventListener("keydown", onKey, true);
1209
+ window.addEventListener("scroll", onScroll, true);
1210
+ }, 0);
1211
+ const dispose = () => {
1212
+ document.removeEventListener("pointerdown", onDown, true);
1213
+ document.removeEventListener("keydown", onKey, true);
1214
+ window.removeEventListener("scroll", onScroll, true);
1215
+ el.remove();
1216
+ };
1217
+ current2 = dispose;
1218
+ currentKey = opts.key ?? null;
1219
+ const handle = {
1220
+ close: () => {
1221
+ if (current2 === dispose) closeCurrent2();
1222
+ },
1223
+ isOpen: () => current2 === dispose
1224
+ };
1225
+ currentHandle = handle;
1226
+ return handle;
1227
+ }
1228
+
856
1229
  // packages/ui/src/lib/cell-properties.ts
857
1230
  var FILL_PRESETS = ["#FCEBEB", "#FAEEDA", "#EAF3DE", "#E6F1FB", "#EEEDFE", "#FBEAF0", "#E1F5EE", "#F1EFE8", "#D3D1C7"];
858
1231
  var PEN_COLORS = ["#000000", "#5F5E5A", "#B0B0B0", "#E24B4A", "#185FA5", "#1D9E75", "#BA7517"];
@@ -880,7 +1253,7 @@ var PRESETS = [
880
1253
  { key: "insideH", label: "Inside horizontal", sides: [0, 0, 0, 0, 1, 0], inside: true },
881
1254
  { key: "insideV", label: "Inside vertical", sides: [0, 0, 0, 0, 0, 1], inside: true }
882
1255
  ];
883
- var STYLE6 = `
1256
+ var STYLE7 = `
884
1257
  .bb-cp{display:flex;flex-direction:column;gap:16px;min-width:320px;color:var(--bb-ui-fg,#2c2c2a)}
885
1258
  .bb-cp *{box-sizing:border-box}
886
1259
  .bb-cp-label{font-size:12px;opacity:.7;margin-bottom:8px}
@@ -921,7 +1294,7 @@ var section = (label) => {
921
1294
  return sec;
922
1295
  };
923
1296
  function openCellProperties({ initial, singleCell, onApply }) {
924
- injectStyle("bb-ui-cellprops-styles", STYLE6);
1297
+ injectStyle("bb-ui-cellprops-styles", STYLE7);
925
1298
  let background = initial.background;
926
1299
  let vAlign = initial.vAlign;
927
1300
  let penWidthPt = 1;
@@ -1094,7 +1467,7 @@ function openCellProperties({ initial, singleCell, onApply }) {
1094
1467
  }
1095
1468
 
1096
1469
  // packages/ui/src/lib/table-grid.ts
1097
- var STYLE7 = `
1470
+ var STYLE8 = `
1098
1471
  .bb-grid{display:flex;flex-direction:column;gap:6px;user-select:none}
1099
1472
  .bb-grid-cells{display:grid;gap:2px}
1100
1473
  .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}
@@ -1102,7 +1475,7 @@ var STYLE7 = `
1102
1475
  .bb-grid-label{font-size:12px;text-align:center;opacity:.7}
1103
1476
  `;
1104
1477
  function tableGridPicker(options) {
1105
- injectStyle("bb-ui-grid-styles", STYLE7);
1478
+ injectStyle("bb-ui-grid-styles", STYLE8);
1106
1479
  const maxRows = options.maxRows ?? 8;
1107
1480
  const maxCols = options.maxCols ?? 10;
1108
1481
  const root = document.createElement("div");
@@ -1147,5 +1520,6 @@ export {
1147
1520
  openCellProperties,
1148
1521
  promptDialog,
1149
1522
  showContextMenu,
1523
+ showLinkPanel,
1150
1524
  tableGridPicker
1151
1525
  };
@@ -1 +1 @@
1
- {"version":3,"file":"context-menu.d.ts","sourceRoot":"","sources":["../../src/lib/context-menu.ts"],"names":[],"mappings":"AAEA,mCAAmC;AACnC,MAAM,WAAW,eAAe;IAC9B,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,MAAM,IAAI,CAAC;IAChB,kCAAkC;IAClC,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,+CAA+C;IAC/C,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AACD,MAAM,MAAM,gBAAgB,GAAG,eAAe,GAAG,WAAW,CAAC;AAE7D,MAAM,WAAW,iBAAiB;IAChC,KAAK,IAAI,IAAI,CAAC;CACf;AAuBD;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,gBAAgB,EAAE,EAAE,EAAE,EAAE;IAAE,CAAC,EAAE,MAAM,CAAC;IAAC,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,iBAAiB,CA2E5G"}
1
+ {"version":3,"file":"context-menu.d.ts","sourceRoot":"","sources":["../../src/lib/context-menu.ts"],"names":[],"mappings":"AAEA,mCAAmC;AACnC,MAAM,WAAW,eAAe;IAC9B,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,MAAM,IAAI,CAAC;IAChB,kCAAkC;IAClC,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,+CAA+C;IAC/C,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AACD,MAAM,MAAM,gBAAgB,GAAG,eAAe,GAAG,WAAW,CAAC;AAE7D,MAAM,WAAW,iBAAiB;IAChC,KAAK,IAAI,IAAI,CAAC;CACf;AAuBD;;;;;GAKG;AACH,wBAAgB,eAAe,CAC7B,OAAO,EAAE,gBAAgB,EAAE,EAC3B,EAAE,EAAE;IAAE,CAAC,EAAE,MAAM,CAAC;IAAC,CAAC,EAAE,MAAM,CAAA;CAAE,GAC3B,iBAAiB,CAmGnB"}
@@ -0,0 +1,47 @@
1
+ /**
2
+ * Floating link panel, anchored at the caret / selection (Google Docs-style).
3
+ *
4
+ * Three modes:
5
+ * - **view** — the caret sits inside an existing link: the href plus three
6
+ * icon actions (copy / edit / remove). Edit switches to the form.
7
+ * - **form with selection** — one URL field; Apply wraps the selection.
8
+ * - **form at a bare caret** — an optional "Text" field above the URL; left
9
+ * empty, the URL itself becomes the inserted text.
10
+ *
11
+ * The panel is one-shot DOM (no framework), closes on Escape / outside click /
12
+ * scroll, and positions with connected pairs: below the anchor first, flipped
13
+ * above when the viewport runs out.
14
+ */
15
+ export interface LinkPanelAnchor {
16
+ /** Caret top-left in viewport CSS px + the caret's rendered height. */
17
+ x: number;
18
+ y: number;
19
+ height: number;
20
+ }
21
+ export interface LinkPanelOptions {
22
+ anchor: LinkPanelAnchor;
23
+ /** Existing link under the caret → open in view mode (prefills the form). */
24
+ existing?: {
25
+ href: string;
26
+ text: string;
27
+ } | null;
28
+ /** A text range is selected → the Text field is hidden (the selection is
29
+ * the text). */
30
+ hasSelection?: boolean;
31
+ /** Identity for keep-alive: with a key, an outside click defers the close
32
+ * briefly, and re-showing the SAME key within that window keeps the
33
+ * existing panel untouched — so a caret moving within one link never
34
+ * re-creates it. Hosts pass e.g. the link's doc range. */
35
+ key?: string;
36
+ /** Apply: `text` is present only when the Text field was shown. */
37
+ onApply(href: string, text?: string): void;
38
+ onUnlink?(): void;
39
+ }
40
+ export interface LinkPanelHandle {
41
+ close(): void;
42
+ /** Whether this panel is still on screen (hosts skip redundant reopens —
43
+ * e.g. the caret moving WITHIN the same link should not repaint). */
44
+ isOpen(): boolean;
45
+ }
46
+ export declare function showLinkPanel(opts: LinkPanelOptions): LinkPanelHandle;
47
+ //# sourceMappingURL=link-panel.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"link-panel.d.ts","sourceRoot":"","sources":["../../src/lib/link-panel.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;GAaG;AAEH,MAAM,WAAW,eAAe;IAC9B,uEAAuE;IACvE,CAAC,EAAE,MAAM,CAAC;IACV,CAAC,EAAE,MAAM,CAAC;IACV,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,gBAAgB;IAC/B,MAAM,EAAE,eAAe,CAAC;IACxB,6EAA6E;IAC7E,QAAQ,CAAC,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;IACjD;qBACiB;IACjB,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB;;;+DAG2D;IAC3D,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,mEAAmE;IACnE,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3C,QAAQ,CAAC,IAAI,IAAI,CAAC;CACnB;AAED,MAAM,WAAW,eAAe;IAC9B,KAAK,IAAI,IAAI,CAAC;IACd;0EACsE;IACtE,MAAM,IAAI,OAAO,CAAC;CACnB;AAwED,wBAAgB,aAAa,CAAC,IAAI,EAAE,gBAAgB,GAAG,eAAe,CAgLrE"}
@@ -46,8 +46,28 @@ export interface ToolbarColor {
46
46
  /** Called with the picked colour (null = cleared). */
47
47
  onSelect: (color: string | null) => void;
48
48
  }
49
- /** A toolbar group entry: a command name (button) or a control (select/colour). */
50
- export type ToolbarEntry = string | ToolbarSelect | ToolbarColor;
49
+ /** One style card in a split button's dropdown: three preview markers, one per
50
+ * nesting level (e.g. ['1.', 'a.', 'i.']), rendered beside placeholder bars. */
51
+ export interface ToolbarSplitOption {
52
+ value: string;
53
+ rows: [string, string, string];
54
+ title?: string;
55
+ }
56
+ /** A split button: the main button runs the command `name` (toggle, with its
57
+ * active/enabled tracking and icon), and a narrow ▾ opens a dropdown of style
58
+ * cards. The host owns the parameterized command each card runs. */
59
+ export interface ToolbarSplit {
60
+ kind: 'split';
61
+ /** Command for the main button — also the icon/tooltip lookup key. */
62
+ name: string;
63
+ options: ToolbarSplitOption[];
64
+ /** Currently applied option value (highlights its card), or null. */
65
+ value: (state: EditorStateOf) => string | null;
66
+ /** Called when the user picks a card. */
67
+ onSelect: (value: string) => void;
68
+ }
69
+ /** A toolbar group entry: a command name (button) or a control (select/colour/split). */
70
+ export type ToolbarEntry = string | ToolbarSelect | ToolbarColor | ToolbarSplit;
51
71
  export interface ToolbarOptions {
52
72
  /** Groups rendered as separated clusters — command names (buttons) and/or
53
73
  * controls (selects). Defaults to marks then alignments, from the registry. */
@@ -1 +1 @@
1
- {"version":3,"file":"toolbar.d.ts","sourceRoot":"","sources":["../../src/lib/toolbar.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,OAAO,EAAE,MAAM,kCAAkC,CAAC;AAC5E,OAAO,EAAE,KAAK,YAAY,EAAE,KAAK,aAAa,EAAe,MAAM,eAAe,CAAC;AAEnF;wCACwC;AACxC,MAAM,WAAW,WAAW;IAC1B,kCAAkC;IAClC,KAAK,EAAE,MAAM,CAAC;IACd,6CAA6C;IAC7C,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,mDAAmD;IACnD,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,6DAA6D;IAC7D,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED;;wEAEwE;AACxE,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,QAAQ,CAAC;IACf,kCAAkC;IAClC,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;IAC5C,qEAAqE;IACrE,KAAK,EAAE,CAAC,KAAK,EAAE,aAAa,KAAK,MAAM,CAAC;IACxC,0CAA0C;IAC1C,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAClC,4CAA4C;IAC5C,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED;8EAC8E;AAC9E,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,OAAO,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,iFAAiF;IACjF,KAAK,EAAE,MAAM,CAAC;IACd,oCAAoC;IACpC,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,+DAA+D;IAC/D,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,6DAA6D;IAC7D,KAAK,EAAE,CAAC,KAAK,EAAE,aAAa,KAAK,MAAM,GAAG,IAAI,CAAC;IAC/C,sDAAsD;IACtD,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,KAAK,IAAI,CAAC;CAC1C;AAED,mFAAmF;AACnF,MAAM,MAAM,YAAY,GAAG,MAAM,GAAG,aAAa,GAAG,YAAY,CAAC;AAEjE,MAAM,WAAW,cAAc;IAC7B;oFACgF;IAChF,MAAM,CAAC,EAAE,YAAY,EAAE,EAAE,CAAC;IAC1B,wEAAwE;IACxE,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC;CACrC;AAED,+CAA+C;AAC/C,MAAM,WAAW,aAAa;IAC5B,OAAO,IAAI,IAAI,CAAC;CACjB;AA0ED;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,QAAQ,EAAE,UAAU,CAAC,OAAO,CAAC,GAAG,MAAM,EAAE,EAAE,CAK9E;AAED;;;;GAIG;AACH,wBAAgB,YAAY,CAC1B,IAAI,EAAE,WAAW,EACjB,MAAM,EAAE,YAAY,EACpB,OAAO,GAAE,cAAmB,GAC3B,aAAa,CA4Nf"}
1
+ {"version":3,"file":"toolbar.d.ts","sourceRoot":"","sources":["../../src/lib/toolbar.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,OAAO,EAAE,MAAM,kCAAkC,CAAC;AAC5E,OAAO,EACL,KAAK,YAAY,EACjB,KAAK,aAAa,EAEnB,MAAM,eAAe,CAAC;AAEvB;wCACwC;AACxC,MAAM,WAAW,WAAW;IAC1B,kCAAkC;IAClC,KAAK,EAAE,MAAM,CAAC;IACd,6CAA6C;IAC7C,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,mDAAmD;IACnD,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,6DAA6D;IAC7D,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED;;wEAEwE;AACxE,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,QAAQ,CAAC;IACf,kCAAkC;IAClC,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;IAC5C,qEAAqE;IACrE,KAAK,EAAE,CAAC,KAAK,EAAE,aAAa,KAAK,MAAM,CAAC;IACxC,0CAA0C;IAC1C,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAClC,4CAA4C;IAC5C,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED;8EAC8E;AAC9E,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,OAAO,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,iFAAiF;IACjF,KAAK,EAAE,MAAM,CAAC;IACd,oCAAoC;IACpC,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,+DAA+D;IAC/D,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,6DAA6D;IAC7D,KAAK,EAAE,CAAC,KAAK,EAAE,aAAa,KAAK,MAAM,GAAG,IAAI,CAAC;IAC/C,sDAAsD;IACtD,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,KAAK,IAAI,CAAC;CAC1C;AAED;iFACiF;AACjF,MAAM,WAAW,kBAAkB;IACjC,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC;IAC/B,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED;;qEAEqE;AACrE,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,OAAO,CAAC;IACd,sEAAsE;IACtE,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,kBAAkB,EAAE,CAAC;IAC9B,qEAAqE;IACrE,KAAK,EAAE,CAAC,KAAK,EAAE,aAAa,KAAK,MAAM,GAAG,IAAI,CAAC;IAC/C,yCAAyC;IACzC,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;CACnC;AAED,yFAAyF;AACzF,MAAM,MAAM,YAAY,GAAG,MAAM,GAAG,aAAa,GAAG,YAAY,GAAG,YAAY,CAAC;AAEhF,MAAM,WAAW,cAAc;IAC7B;oFACgF;IAChF,MAAM,CAAC,EAAE,YAAY,EAAE,EAAE,CAAC;IAC1B,wEAAwE;IACxE,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC;CACrC;AAED,+CAA+C;AAC/C,MAAM,WAAW,aAAa;IAC5B,OAAO,IAAI,IAAI,CAAC;CACjB;AAyHD;;;GAGG;AACH,wBAAgB,oBAAoB,CAClC,QAAQ,EAAE,UAAU,CAAC,OAAO,CAAC,GAC5B,MAAM,EAAE,EAAE,CAKZ;AAED;;;;GAIG;AACH,wBAAgB,YAAY,CAC1B,IAAI,EAAE,WAAW,EACjB,MAAM,EAAE,YAAY,EACpB,OAAO,GAAE,cAAmB,GAC3B,aAAa,CAwWf"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shadow-garden/bapbong-ui",
3
- "version": "0.1.1",
3
+ "version": "0.2.0",
4
4
  "description": "bapbong — framework-agnostic editor UI (toolbar/menubar) that mounts into a host element and binds to editor.commands",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -56,6 +56,6 @@
56
56
  }
57
57
  },
58
58
  "dependencies": {
59
- "@shadow-garden/bapbong-contracts": "^0.1.1"
59
+ "@shadow-garden/bapbong-contracts": "^0.2.0"
60
60
  }
61
61
  }