@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 +397 -22
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +396 -22
- package/dist/lib/context-menu.d.ts.map +1 -1
- package/dist/lib/link-panel.d.ts +47 -0
- package/dist/lib/link-panel.d.ts.map +1 -0
- package/dist/lib/toolbar.d.ts +22 -2
- package/dist/lib/toolbar.d.ts.map +1 -1
- package/package.json +2 -2
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(
|
|
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": {
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
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;
|
|
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(
|
|
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
|
|
309
|
-
|
|
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
|
-
|
|
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 (!
|
|
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
|
-
|
|
365
|
-
|
|
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(
|
|
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)
|
|
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
|
-
|
|
860
|
-
|
|
861
|
-
const
|
|
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
|
|
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",
|
|
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
|
|
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",
|
|
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
|
package/dist/index.d.ts.map
CHANGED
|
@@ -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(
|
|
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": {
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
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;
|
|
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(
|
|
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
|
|
274
|
-
|
|
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
|
-
|
|
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 (!
|
|
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
|
-
|
|
330
|
-
|
|
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(
|
|
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)
|
|
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
|
-
|
|
825
|
-
|
|
826
|
-
const
|
|
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
|
|
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",
|
|
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
|
|
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",
|
|
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,
|
|
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"}
|
package/dist/lib/toolbar.d.ts
CHANGED
|
@@ -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
|
-
/**
|
|
50
|
-
|
|
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,
|
|
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.
|
|
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.
|
|
59
|
+
"@shadow-garden/bapbong-contracts": "^0.2.0"
|
|
60
60
|
}
|
|
61
61
|
}
|