mnfst 0.5.218 → 0.5.219
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/lib/manifest.appwrite.data.js +1 -1
- package/lib/manifest.code.js +120 -46
- package/lib/manifest.css +83 -222
- package/lib/manifest.d.ts +2 -0
- package/lib/manifest.data.js +26 -3
- package/lib/manifest.defer.js +145 -34
- package/lib/manifest.edit.css +63 -27
- package/lib/manifest.edit.js +213 -28
- package/lib/manifest.integrity.json +11 -11
- package/lib/manifest.js +43 -2
- package/lib/manifest.markdown.js +11 -7
- package/lib/manifest.min.css +1 -1
- package/lib/manifest.router.js +58 -69
- package/lib/manifest.sw.js +1 -1
- package/lib/manifest.text.edit.css +20 -0
- package/lib/manifest.text.edit.js +151 -25
- package/lib/manifest.utilities.js +246 -100
- package/lib/manifest.utilities.node.mjs +195 -35
- package/package.json +1 -1
package/lib/manifest.code.js
CHANGED
|
@@ -265,6 +265,8 @@ async function setupInline(el, hljs) {
|
|
|
265
265
|
}
|
|
266
266
|
|
|
267
267
|
function setupInlineCopy(el) {
|
|
268
|
+
if (el._copyBound) return; // idempotent: fresh setup and the prerender re-bind share it
|
|
269
|
+
el._copyBound = true;
|
|
268
270
|
if (!el.hasAttribute('role')) el.setAttribute('role', 'button');
|
|
269
271
|
if (!el.hasAttribute('tabindex')) el.setAttribute('tabindex', '0');
|
|
270
272
|
if (!el.hasAttribute('aria-label')) el.setAttribute('aria-label', 'Click to copy');
|
|
@@ -350,10 +352,35 @@ async function setupBlock(pre, hljs) {
|
|
|
350
352
|
const lang = actualLang || resolveLanguage(hljs, requested);
|
|
351
353
|
setupEditor(pre, code, lang, hljs);
|
|
352
354
|
}
|
|
355
|
+
|
|
356
|
+
pre._blockBound = true; // built this session — the prerender re-bind skips it
|
|
353
357
|
}
|
|
354
358
|
|
|
355
|
-
|
|
356
|
-
|
|
359
|
+
// Re-attach behavior to a block adopted from prerendered output: its copy,
|
|
360
|
+
// expand and editor markup is baked, but listeners cannot survive
|
|
361
|
+
// serialization. Idempotent via _blockBound (set by a fresh setupBlock too).
|
|
362
|
+
function rebindBlock(pre) {
|
|
363
|
+
if (pre._blockBound) return;
|
|
364
|
+
pre._blockBound = true;
|
|
365
|
+
const code = pre.querySelector(':scope > code');
|
|
366
|
+
if (!code) return;
|
|
367
|
+
const copyBtn = pre.querySelector(':scope > button.copy');
|
|
368
|
+
if (copyBtn) setupBlockCopy(pre, code, copyBtn);
|
|
369
|
+
const expandBtn = pre.querySelector(':scope > button.expand');
|
|
370
|
+
if (expandBtn) {
|
|
371
|
+
const collapseAt = parseInt(pre.style.getPropertyValue('--collapse-lines'), 10) || 20;
|
|
372
|
+
const hiddenCount = Math.max(1, code.textContent.split('\n').length - collapseAt);
|
|
373
|
+
wireExpandButton(pre, expandBtn, hiddenCount);
|
|
374
|
+
}
|
|
375
|
+
if (pre.hasAttribute('edit')) {
|
|
376
|
+
const m = (code.className || '').match(/language-([\w-]+)/);
|
|
377
|
+
const lang = (m && m[1]) || pre.getAttribute('x-code') || pre.getAttribute('language') || '';
|
|
378
|
+
loadHighlightJS(lang).then(hljs => setupEditor(pre, code, lang, hljs)).catch(() => { });
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
function setupBlockCopy(pre, code, existingBtn) {
|
|
383
|
+
const btn = existingBtn || document.createElement('button');
|
|
357
384
|
btn.className = 'copy';
|
|
358
385
|
btn.type = 'button';
|
|
359
386
|
btn.setAttribute('aria-label', 'Copy code to clipboard');
|
|
@@ -364,7 +391,7 @@ function setupBlockCopy(pre, code) {
|
|
|
364
391
|
setTimeout(() => btn.classList.remove('copied'), 1500);
|
|
365
392
|
} catch { /* clipboard rejected (browser permissions) — fail silently */ }
|
|
366
393
|
});
|
|
367
|
-
pre.appendChild(btn);
|
|
394
|
+
if (!existingBtn) pre.appendChild(btn);
|
|
368
395
|
}
|
|
369
396
|
|
|
370
397
|
function setupCollapse(pre, code) {
|
|
@@ -382,9 +409,13 @@ function setupCollapse(pre, code) {
|
|
|
382
409
|
btn.className = 'expand';
|
|
383
410
|
btn.type = 'button';
|
|
384
411
|
btn.setAttribute('aria-expanded', 'false');
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
412
|
+
wireExpandButton(pre, btn, lineCount - collapseAt);
|
|
413
|
+
pre.appendChild(btn);
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
// Visual label is locale-safe ("+N" / "−"); screen readers get an explicit
|
|
417
|
+
// English aria-label. Shared by the build path and the prerender re-bind.
|
|
418
|
+
function wireExpandButton(pre, btn, hiddenCount) {
|
|
388
419
|
const updateLabel = () => {
|
|
389
420
|
const collapsed = pre.hasAttribute('data-collapsed');
|
|
390
421
|
btn.setAttribute('aria-expanded', collapsed ? 'false' : 'true');
|
|
@@ -399,7 +430,6 @@ function setupCollapse(pre, code) {
|
|
|
399
430
|
updateLabel();
|
|
400
431
|
});
|
|
401
432
|
updateLabel();
|
|
402
|
-
pre.appendChild(btn);
|
|
403
433
|
}
|
|
404
434
|
|
|
405
435
|
async function setupEditor(pre, code, lang, hljs) {
|
|
@@ -572,26 +602,9 @@ async function setupCodeGroup(group) {
|
|
|
572
602
|
btn.textContent = name;
|
|
573
603
|
btn.setAttribute('aria-selected', name === active ? 'true' : 'false');
|
|
574
604
|
btn.tabIndex = name === active ? 0 : -1;
|
|
575
|
-
btn.addEventListener('click', () => activate(name));
|
|
576
605
|
tablist.appendChild(btn);
|
|
577
606
|
return btn;
|
|
578
607
|
});
|
|
579
|
-
tabButtons.forEach((btn, idx) => {
|
|
580
|
-
btn.addEventListener('keydown', (ev) => {
|
|
581
|
-
if (ev.key === 'ArrowRight' || ev.key === 'ArrowLeft') {
|
|
582
|
-
ev.preventDefault();
|
|
583
|
-
const next = ev.key === 'ArrowRight'
|
|
584
|
-
? (idx + 1) % tabButtons.length
|
|
585
|
-
: (idx - 1 + tabButtons.length) % tabButtons.length;
|
|
586
|
-
tabButtons[next].focus();
|
|
587
|
-
tabButtons[next].click();
|
|
588
|
-
} else if (ev.key === 'Home') {
|
|
589
|
-
ev.preventDefault(); tabButtons[0].focus(); tabButtons[0].click();
|
|
590
|
-
} else if (ev.key === 'End') {
|
|
591
|
-
ev.preventDefault(); tabButtons[tabButtons.length - 1].focus(); tabButtons[tabButtons.length - 1].click();
|
|
592
|
-
}
|
|
593
|
-
});
|
|
594
|
-
});
|
|
595
608
|
}
|
|
596
609
|
if (header) pre.insertBefore(header, pre.firstChild);
|
|
597
610
|
|
|
@@ -614,37 +627,41 @@ async function setupCodeGroup(group) {
|
|
|
614
627
|
// top-end without competing with the header's scroll region.
|
|
615
628
|
const wrapperHasCopy = pre.hasAttribute('copy');
|
|
616
629
|
const anyPanelCopy = [...sourcePanels, ...ambientChildren].some(p => p.hasAttribute('copy'));
|
|
617
|
-
let copyBtn = null;
|
|
618
630
|
if (wrapperHasCopy || anyPanelCopy) {
|
|
619
|
-
copyBtn = document.createElement('button');
|
|
631
|
+
const copyBtn = document.createElement('button');
|
|
620
632
|
copyBtn.className = 'copy';
|
|
621
633
|
copyBtn.type = 'button';
|
|
622
634
|
copyBtn.setAttribute('aria-label', 'Copy code to clipboard');
|
|
623
|
-
copyBtn.addEventListener('click', async () => {
|
|
624
|
-
// Prefer the <pre x-code> panel for copy (its source, not a frame's
|
|
625
|
-
// rendered text). Headerless groups pick the first ambient <pre x-code>.
|
|
626
|
-
let activePanel;
|
|
627
|
-
if (isHeaderless) {
|
|
628
|
-
activePanel = ambientChildren.find(p => p.tagName === 'PRE' && p.hasAttribute('x-code'));
|
|
629
|
-
} else {
|
|
630
|
-
const sameName = sourcePanels.filter(p => p.getAttribute('name') === activeName);
|
|
631
|
-
activePanel = sameName.find(p => p.tagName === 'PRE' && p.hasAttribute('x-code')) || sameName[0];
|
|
632
|
-
}
|
|
633
|
-
if (!activePanel) return;
|
|
634
|
-
const code = activePanel.querySelector(':scope > code') || activePanel;
|
|
635
|
-
try {
|
|
636
|
-
await navigator.clipboard.writeText(code.textContent);
|
|
637
|
-
copyBtn.classList.add('copied');
|
|
638
|
-
setTimeout(() => copyBtn.classList.remove('copied'), 1500);
|
|
639
|
-
} catch { /* clipboard rejected (browser permissions) — fail silently */ }
|
|
640
|
-
});
|
|
641
635
|
pre.appendChild(copyBtn);
|
|
642
636
|
}
|
|
643
637
|
|
|
638
|
+
wireGroup(pre);
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
// Attach behavior to a built group: tab activation, keyboard nav, wrapper copy.
|
|
642
|
+
// Everything is derived from the built DOM, so this wires a group built this
|
|
643
|
+
// session AND one adopted from prerendered output (whose listeners cannot
|
|
644
|
+
// survive serialization). Idempotent via _groupBound.
|
|
645
|
+
function wireGroup(pre) {
|
|
646
|
+
if (pre._groupBound) return;
|
|
647
|
+
pre._groupBound = true;
|
|
648
|
+
|
|
649
|
+
const sourcePanels = Array.from(pre.children).filter(c => c.hasAttribute('name'));
|
|
650
|
+
const ambientCode = Array.from(pre.children).filter(c =>
|
|
651
|
+
!c.hasAttribute('name') && c.tagName === 'PRE' && c.hasAttribute('x-code'));
|
|
652
|
+
const tabButtons = Array.from(pre.querySelectorAll(':scope > header [role="tab"]'));
|
|
653
|
+
const copyBtn = pre.querySelector(':scope > button.copy');
|
|
654
|
+
const wrapperHasCopy = pre.hasAttribute('copy');
|
|
655
|
+
const isHeaderless = !pre.querySelector(':scope > header');
|
|
656
|
+
|
|
657
|
+
let activeName = (tabButtons.find(b => b.getAttribute('aria-selected') === 'true')
|
|
658
|
+
|| tabButtons[0] || {}).textContent
|
|
659
|
+
|| (sourcePanels[0] && sourcePanels[0].getAttribute('name'))
|
|
660
|
+
|| null;
|
|
661
|
+
|
|
644
662
|
// Visibility toggle via style.display (pre's display:flex outweighs the UA
|
|
645
663
|
// [hidden] rule). Also flips per-tab copy-button visibility when [copy] is
|
|
646
664
|
// per-panel rather than on the wrapper.
|
|
647
|
-
let activeName = active;
|
|
648
665
|
function activate(name) {
|
|
649
666
|
activeName = name;
|
|
650
667
|
tabButtons.forEach(btn => {
|
|
@@ -662,7 +679,47 @@ async function setupCodeGroup(group) {
|
|
|
662
679
|
copyBtn.style.display = activeCanCopy ? '' : 'none';
|
|
663
680
|
}
|
|
664
681
|
}
|
|
665
|
-
|
|
682
|
+
|
|
683
|
+
tabButtons.forEach(btn => btn.addEventListener('click', () => activate(btn.textContent)));
|
|
684
|
+
tabButtons.forEach((btn, idx) => {
|
|
685
|
+
btn.addEventListener('keydown', (ev) => {
|
|
686
|
+
if (ev.key === 'ArrowRight' || ev.key === 'ArrowLeft') {
|
|
687
|
+
ev.preventDefault();
|
|
688
|
+
const next = ev.key === 'ArrowRight'
|
|
689
|
+
? (idx + 1) % tabButtons.length
|
|
690
|
+
: (idx - 1 + tabButtons.length) % tabButtons.length;
|
|
691
|
+
tabButtons[next].focus();
|
|
692
|
+
tabButtons[next].click();
|
|
693
|
+
} else if (ev.key === 'Home') {
|
|
694
|
+
ev.preventDefault(); tabButtons[0].focus(); tabButtons[0].click();
|
|
695
|
+
} else if (ev.key === 'End') {
|
|
696
|
+
ev.preventDefault(); tabButtons[tabButtons.length - 1].focus(); tabButtons[tabButtons.length - 1].click();
|
|
697
|
+
}
|
|
698
|
+
});
|
|
699
|
+
});
|
|
700
|
+
|
|
701
|
+
if (copyBtn) {
|
|
702
|
+
copyBtn.addEventListener('click', async () => {
|
|
703
|
+
// Prefer the <pre x-code> panel for copy (its source, not a frame's
|
|
704
|
+
// rendered text). Headerless groups pick the first ambient <pre x-code>.
|
|
705
|
+
let activePanel;
|
|
706
|
+
if (isHeaderless) {
|
|
707
|
+
activePanel = ambientCode[0];
|
|
708
|
+
} else {
|
|
709
|
+
const sameName = sourcePanels.filter(p => p.getAttribute('name') === activeName);
|
|
710
|
+
activePanel = sameName.find(p => p.tagName === 'PRE' && p.hasAttribute('x-code')) || sameName[0];
|
|
711
|
+
}
|
|
712
|
+
if (!activePanel) return;
|
|
713
|
+
const code = activePanel.querySelector(':scope > code') || activePanel;
|
|
714
|
+
try {
|
|
715
|
+
await navigator.clipboard.writeText(code.textContent);
|
|
716
|
+
copyBtn.classList.add('copied');
|
|
717
|
+
setTimeout(() => copyBtn.classList.remove('copied'), 1500);
|
|
718
|
+
} catch { /* clipboard rejected (browser permissions) — fail silently */ }
|
|
719
|
+
});
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
if (!isHeaderless && activeName != null) activate(activeName);
|
|
666
723
|
}
|
|
667
724
|
|
|
668
725
|
// ─── Page scan + observation ─────────────────────────────────────────────────
|
|
@@ -754,10 +811,27 @@ function processOne(el) {
|
|
|
754
811
|
}
|
|
755
812
|
}
|
|
756
813
|
|
|
814
|
+
// Prerendered pages ship groups/blocks already BUILT (data-group-processed /
|
|
815
|
+
// data-code-block-built baked in), which the processing pipeline rightly skips —
|
|
816
|
+
// but their listeners cannot survive serialization, so tabs, copy, expand and
|
|
817
|
+
// editors arrive dead. Re-attach behavior to that adopted markup. Cheap (no
|
|
818
|
+
// highlighting), so it runs eagerly rather than through the IntersectionObserver.
|
|
819
|
+
function rebindAdopted(root = document) {
|
|
820
|
+
root.querySelectorAll('[x-code-group][data-group-processed="yes"]').forEach(g => {
|
|
821
|
+
wireGroup(g);
|
|
822
|
+
Array.from(g.children).forEach(c => { if (c.dataset && c.dataset.codeBlockBuilt === 'yes') rebindBlock(c); });
|
|
823
|
+
});
|
|
824
|
+
root.querySelectorAll('pre[data-code-block-built="yes"]').forEach(rebindBlock);
|
|
825
|
+
root.querySelectorAll('code[copy][data-code-processed="yes"]').forEach(el => {
|
|
826
|
+
if (!(el.parentElement && el.parentElement.tagName === 'PRE')) setupInlineCopy(el);
|
|
827
|
+
});
|
|
828
|
+
}
|
|
829
|
+
|
|
757
830
|
// Observe every unprocessed candidate in `root` (idempotent). Already-visible
|
|
758
831
|
// elements process immediately so markdown-injected blocks render synchronously
|
|
759
832
|
// instead of waiting for the next IO callback.
|
|
760
833
|
function observeAll(root = document) {
|
|
834
|
+
rebindAdopted(root);
|
|
761
835
|
const io = ensureObserver();
|
|
762
836
|
const candidates = [
|
|
763
837
|
...root.querySelectorAll('[x-code]:not([data-code-processed])'),
|
package/lib/manifest.css
CHANGED
|
@@ -2929,34 +2929,44 @@
|
|
|
2929
2929
|
|
|
2930
2930
|
@layer utilities {
|
|
2931
2931
|
|
|
2932
|
-
/* ----
|
|
2933
|
-
|
|
2934
|
-
|
|
2935
|
-
the region
|
|
2932
|
+
/* ---- Editing UI scope ----
|
|
2933
|
+
ALL chrome — region outline + label, sortable grips, movable/text outlines,
|
|
2934
|
+
size-handle hairlines, the floating toolbar — lives under ONE scope:
|
|
2935
|
+
[data-edit-ui], on the region itself or any ancestor (e.g. <body data-edit-ui>
|
|
2936
|
+
for a whole editor view). The plugin sets it on `.authoring` regions
|
|
2937
|
+
automatically; everything else opts in with the attribute. A region outside
|
|
2938
|
+
the scope is just as editable — it simply carries no visual cues. */
|
|
2939
|
+
|
|
2940
|
+
/* ---- Editable area (armed) ---- */
|
|
2936
2941
|
:where([data-edit-armed]) {
|
|
2937
2942
|
position: relative;
|
|
2938
2943
|
}
|
|
2939
2944
|
|
|
2940
2945
|
/* Quiet until touched. A page covered in dashed boxes reads as a diagram of
|
|
2941
|
-
itself; the affordance only has to be there when you go looking for it.
|
|
2942
|
-
|
|
2946
|
+
itself; the affordance only has to be there when you go looking for it.
|
|
2947
|
+
While an inner affordance is engaged (text caret, handle, row), the region
|
|
2948
|
+
outline stands down so only the element being edited is highlighted. */
|
|
2949
|
+
:where([data-edit-armed]):where([data-edit-ui], [data-edit-ui] *) {
|
|
2943
2950
|
outline: 1px dashed transparent;
|
|
2944
2951
|
outline-offset: 6px;
|
|
2945
2952
|
border-radius: var(--radius);
|
|
2946
2953
|
transition: outline-color 0.12s ease;
|
|
2947
2954
|
}
|
|
2948
2955
|
|
|
2949
|
-
:where([data-edit-armed][data-edit-
|
|
2950
|
-
:where([data-edit-armed][data-edit-authoring]):focus-within {
|
|
2956
|
+
:where([data-edit-armed]):where([data-edit-ui], [data-edit-ui] *):is(:hover, :focus-within):not(:has([contenteditable]:is(:hover, :focus), [data-edit-handle]:is(:hover, :focus), [data-edit-sortable]:is(:hover, :focus-within), [data-edit-movable]:is(:hover, :focus))) {
|
|
2951
2957
|
outline-color: var(--color-line);
|
|
2952
2958
|
}
|
|
2953
2959
|
|
|
2954
|
-
:where([data-edit-armed][data-edit-
|
|
2960
|
+
:where([data-edit-armed]):where([data-edit-ui], [data-edit-ui] *)::before {
|
|
2955
2961
|
content: attr(data-edit-label);
|
|
2956
2962
|
position: absolute;
|
|
2957
|
-
inset-block-start: -
|
|
2958
|
-
inset-inline-start: 0;
|
|
2963
|
+
inset-block-start: calc(-0.6em - 6px);
|
|
2964
|
+
inset-inline-start: 0.5rem;
|
|
2965
|
+
padding-inline: 0.3rem;
|
|
2966
|
+
background: var(--color-page);
|
|
2967
|
+
border-radius: 3px;
|
|
2959
2968
|
font-size: 0.7rem;
|
|
2969
|
+
line-height: 1.2;
|
|
2960
2970
|
letter-spacing: 0.04em;
|
|
2961
2971
|
text-transform: uppercase;
|
|
2962
2972
|
color: var(--color-content-subtle);
|
|
@@ -2965,8 +2975,7 @@
|
|
|
2965
2975
|
transition: opacity 0.12s ease;
|
|
2966
2976
|
}
|
|
2967
2977
|
|
|
2968
|
-
:where([data-edit-armed][data-edit-
|
|
2969
|
-
:where([data-edit-armed][data-edit-authoring]):focus-within::before {
|
|
2978
|
+
:where([data-edit-armed]):where([data-edit-ui], [data-edit-ui] *):is(:hover, :focus-within):not(:has([contenteditable]:is(:hover, :focus), [data-edit-handle]:is(:hover, :focus), [data-edit-sortable]:is(:hover, :focus-within), [data-edit-movable]:is(:hover, :focus)))::before {
|
|
2970
2979
|
opacity: 1;
|
|
2971
2980
|
}
|
|
2972
2981
|
|
|
@@ -2977,10 +2986,28 @@
|
|
|
2977
2986
|
transition: opacity 0.12s ease, outline-color 0.12s ease;
|
|
2978
2987
|
}
|
|
2979
2988
|
|
|
2980
|
-
:
|
|
2981
|
-
|
|
2982
|
-
|
|
2983
|
-
|
|
2989
|
+
/* The grip: the plugin's own inline drag affordance, sitting inside the row
|
|
2990
|
+
at its inline start. A dot grid, revealed on approach. --edit-grip sizes
|
|
2991
|
+
it; `--edit-grip: 0` removes it (e.g. for grid rows where an extra flex
|
|
2992
|
+
item would take a cell). No full-row outline — the grip and the cursor
|
|
2993
|
+
say "draggable" without boxing the content. */
|
|
2994
|
+
:where([data-edit-ui] [data-edit-sortable], [data-edit-ui][data-edit-sortable])::before {
|
|
2995
|
+
content: '';
|
|
2996
|
+
align-self: center;
|
|
2997
|
+
flex: 0 0 auto;
|
|
2998
|
+
width: var(--edit-grip, 0.6rem);
|
|
2999
|
+
height: 1rem;
|
|
3000
|
+
margin-inline-end: calc(var(--edit-grip, 0.6rem) / 2);
|
|
3001
|
+
background-image: radial-gradient(circle, var(--color-content-subtle) 1px, transparent 1px);
|
|
3002
|
+
background-size: 0.3rem 0.3rem;
|
|
3003
|
+
background-position: center;
|
|
3004
|
+
opacity: 0;
|
|
3005
|
+
transition: opacity 0.12s ease;
|
|
3006
|
+
pointer-events: none;
|
|
3007
|
+
}
|
|
3008
|
+
|
|
3009
|
+
:where([data-edit-ui] [data-edit-sortable], [data-edit-ui][data-edit-sortable]):is(:hover, :focus-visible)::before {
|
|
3010
|
+
opacity: 0.9;
|
|
2984
3011
|
}
|
|
2985
3012
|
|
|
2986
3013
|
:where([data-edit-sortable], [data-edit-movable]):focus-visible {
|
|
@@ -3025,25 +3052,30 @@
|
|
|
3025
3052
|
touch-action: none;
|
|
3026
3053
|
}
|
|
3027
3054
|
|
|
3028
|
-
:where([data-edit-movable]):hover {
|
|
3055
|
+
:where([data-edit-ui] [data-edit-movable], [data-edit-ui][data-edit-movable]):hover {
|
|
3029
3056
|
outline: 1px solid var(--edit-accent, var(--color-brand-content));
|
|
3030
3057
|
outline-offset: 2px;
|
|
3031
3058
|
}
|
|
3032
3059
|
|
|
3033
|
-
/* ---- Inline text ----
|
|
3034
|
-
|
|
3060
|
+
/* ---- Inline text ----
|
|
3061
|
+
One weight everywhere: hover hints and the focused caret both use the same
|
|
3062
|
+
1px line, so focusing doesn't make the page jump a weight class. */
|
|
3063
|
+
:where([data-edit-ui]) :where([data-edit-armed]) [contenteditable="true"],
|
|
3064
|
+
:where([data-edit-ui])[data-edit-armed] [contenteditable="true"] {
|
|
3035
3065
|
outline: 1px solid transparent;
|
|
3036
3066
|
outline-offset: 2px;
|
|
3037
3067
|
border-radius: 3px;
|
|
3038
3068
|
transition: outline-color 0.12s ease;
|
|
3039
3069
|
}
|
|
3040
3070
|
|
|
3041
|
-
:where([data-edit-armed]) [contenteditable="true"]:hover
|
|
3071
|
+
:where([data-edit-ui]) :where([data-edit-armed]) [contenteditable="true"]:hover,
|
|
3072
|
+
:where([data-edit-ui])[data-edit-armed] [contenteditable="true"]:hover {
|
|
3042
3073
|
outline-color: color-mix(in oklch, var(--edit-accent, var(--color-brand-content)) 45%, transparent);
|
|
3043
3074
|
}
|
|
3044
3075
|
|
|
3045
|
-
:where([data-edit-armed]) [contenteditable="true"]:focus
|
|
3046
|
-
|
|
3076
|
+
:where([data-edit-ui]) :where([data-edit-armed]) [contenteditable="true"]:focus,
|
|
3077
|
+
:where([data-edit-ui])[data-edit-armed] [contenteditable="true"]:focus {
|
|
3078
|
+
outline-color: var(--edit-accent, var(--color-brand-content));
|
|
3047
3079
|
}
|
|
3048
3080
|
|
|
3049
3081
|
/* ---- Size handles ----
|
|
@@ -3053,14 +3085,16 @@
|
|
|
3053
3085
|
position: relative;
|
|
3054
3086
|
}
|
|
3055
3087
|
|
|
3056
|
-
/* Handles reveal on approach rather than sitting on the element permanently
|
|
3088
|
+
/* Handles reveal on approach rather than sitting on the element permanently.
|
|
3089
|
+
The hairline is chrome, so it follows the [data-edit-ui] scope like every
|
|
3090
|
+
other affordance; outside the scope the handles still work (hit areas and
|
|
3091
|
+
resize cursors stay), they just draw nothing. */
|
|
3057
3092
|
:where([data-edit-sizable]) :where([data-edit-handle])::before {
|
|
3058
3093
|
opacity: 0;
|
|
3059
3094
|
transition: opacity 0.12s ease, background-color 0.15s ease;
|
|
3060
3095
|
}
|
|
3061
3096
|
|
|
3062
|
-
:where([data-edit-sizable]):hover :where([data-edit-handle])::before
|
|
3063
|
-
:where([data-edit-sizable]):focus-within :where([data-edit-handle])::before {
|
|
3097
|
+
:where([data-edit-ui] [data-edit-sizable], [data-edit-ui][data-edit-sizable]):is(:hover, :focus-within) :where([data-edit-handle])::before {
|
|
3064
3098
|
opacity: 1;
|
|
3065
3099
|
}
|
|
3066
3100
|
|
|
@@ -3220,8 +3254,10 @@
|
|
|
3220
3254
|
}
|
|
3221
3255
|
}
|
|
3222
3256
|
|
|
3257
|
+
/* --edit-toolbar: none (any scope up to :root) suppresses it — e.g. a site
|
|
3258
|
+
that demos .authoring regions but has its own publish flow. */
|
|
3223
3259
|
:root[data-edit-active] :where([data-edit-toolbar]) {
|
|
3224
|
-
display: flex;
|
|
3260
|
+
display: var(--edit-toolbar, flex);
|
|
3225
3261
|
}
|
|
3226
3262
|
|
|
3227
3263
|
/* A popover: the top layer sits above every stacking context, so no handle,
|
|
@@ -3918,201 +3954,6 @@
|
|
|
3918
3954
|
}
|
|
3919
3955
|
}
|
|
3920
3956
|
|
|
3921
|
-
/* Manifest Resize Handles
|
|
3922
|
-
|
|
3923
|
-
Superseded by x-edit.size (manifest.edit.css) — kept for existing projects.
|
|
3924
|
-
Self-contained: handle geometry lives here, not in a shared snippet. */
|
|
3925
|
-
|
|
3926
|
-
@layer base, components, utilities;
|
|
3927
|
-
|
|
3928
|
-
@layer utilities {
|
|
3929
|
-
|
|
3930
|
-
:where([x-resize]) {
|
|
3931
|
-
position: relative;
|
|
3932
|
-
|
|
3933
|
-
/* Resize handle area */
|
|
3934
|
-
.resize-handle {
|
|
3935
|
-
position: absolute;
|
|
3936
|
-
display: block;
|
|
3937
|
-
width: var(--h);
|
|
3938
|
-
height: var(--h);
|
|
3939
|
-
z-index: 100;
|
|
3940
|
-
background: transparent;
|
|
3941
|
-
border: none;
|
|
3942
|
-
outline: none;
|
|
3943
|
-
--h: var(--spacing-resize-handle, 1rem);
|
|
3944
|
-
|
|
3945
|
-
/* Visible handle */
|
|
3946
|
-
&::before {
|
|
3947
|
-
content: '';
|
|
3948
|
-
position: absolute;
|
|
3949
|
-
top: 50%;
|
|
3950
|
-
left: 50%;
|
|
3951
|
-
transform: translate(-50%, -50%);
|
|
3952
|
-
width: 1px;
|
|
3953
|
-
height: 1px;
|
|
3954
|
-
background: transparent;
|
|
3955
|
-
transition: background-color 0.2s ease;
|
|
3956
|
-
pointer-events: none
|
|
3957
|
-
}
|
|
3958
|
-
|
|
3959
|
-
&:hover::before,
|
|
3960
|
-
&:active::before {
|
|
3961
|
-
background-color: var(--color-line, color-mix(in oklch, oklch(20.5% 0 0) 10%, transparent));
|
|
3962
|
-
}
|
|
3963
|
-
}
|
|
3964
|
-
|
|
3965
|
-
/* Handle geometry — edges span their axis, corners stay square */
|
|
3966
|
-
.resize-handle-top,
|
|
3967
|
-
.resize-handle-bottom {
|
|
3968
|
-
left: 0;
|
|
3969
|
-
width: 100%;
|
|
3970
|
-
height: var(--h);
|
|
3971
|
-
cursor: ns-resize;
|
|
3972
|
-
|
|
3973
|
-
&::before {
|
|
3974
|
-
width: 100%
|
|
3975
|
-
}
|
|
3976
|
-
}
|
|
3977
|
-
|
|
3978
|
-
.resize-handle-left,
|
|
3979
|
-
.resize-handle-right,
|
|
3980
|
-
.resize-handle-start,
|
|
3981
|
-
.resize-handle-end {
|
|
3982
|
-
top: 0;
|
|
3983
|
-
width: var(--h);
|
|
3984
|
-
height: 100%;
|
|
3985
|
-
cursor: ew-resize;
|
|
3986
|
-
|
|
3987
|
-
&::before {
|
|
3988
|
-
height: 100%
|
|
3989
|
-
}
|
|
3990
|
-
}
|
|
3991
|
-
|
|
3992
|
-
.resize-handle-top {
|
|
3993
|
-
top: calc(var(--h) * -0.5)
|
|
3994
|
-
}
|
|
3995
|
-
|
|
3996
|
-
.resize-handle-bottom {
|
|
3997
|
-
bottom: calc(var(--h) * -0.5)
|
|
3998
|
-
}
|
|
3999
|
-
|
|
4000
|
-
.resize-handle-left,
|
|
4001
|
-
.resize-handle-top-left,
|
|
4002
|
-
.resize-handle-bottom-left {
|
|
4003
|
-
left: calc(var(--h) * -0.5)
|
|
4004
|
-
}
|
|
4005
|
-
|
|
4006
|
-
.resize-handle-right,
|
|
4007
|
-
.resize-handle-top-right,
|
|
4008
|
-
.resize-handle-bottom-right {
|
|
4009
|
-
right: calc(var(--h) * -0.5)
|
|
4010
|
-
}
|
|
4011
|
-
|
|
4012
|
-
/* Logical edges — inset-inline-* auto-flips for RTL */
|
|
4013
|
-
.resize-handle-start,
|
|
4014
|
-
.resize-handle-top-start,
|
|
4015
|
-
.resize-handle-bottom-start {
|
|
4016
|
-
inset-inline-start: calc(var(--h) * -0.5)
|
|
4017
|
-
}
|
|
4018
|
-
|
|
4019
|
-
.resize-handle-end,
|
|
4020
|
-
.resize-handle-top-end,
|
|
4021
|
-
.resize-handle-bottom-end {
|
|
4022
|
-
inset-inline-end: calc(var(--h) * -0.5)
|
|
4023
|
-
}
|
|
4024
|
-
|
|
4025
|
-
.resize-handle-top-left,
|
|
4026
|
-
.resize-handle-top-right,
|
|
4027
|
-
.resize-handle-top-start,
|
|
4028
|
-
.resize-handle-top-end {
|
|
4029
|
-
top: calc(var(--h) * -0.5)
|
|
4030
|
-
}
|
|
4031
|
-
|
|
4032
|
-
.resize-handle-bottom-left,
|
|
4033
|
-
.resize-handle-bottom-right,
|
|
4034
|
-
.resize-handle-bottom-start,
|
|
4035
|
-
.resize-handle-bottom-end {
|
|
4036
|
-
bottom: calc(var(--h) * -0.5)
|
|
4037
|
-
}
|
|
4038
|
-
|
|
4039
|
-
/* Corner cursors */
|
|
4040
|
-
.resize-handle-top-left,
|
|
4041
|
-
.resize-handle-top-start {
|
|
4042
|
-
cursor: nw-resize;
|
|
4043
|
-
}
|
|
4044
|
-
|
|
4045
|
-
.resize-handle-top-right,
|
|
4046
|
-
.resize-handle-top-end {
|
|
4047
|
-
cursor: ne-resize;
|
|
4048
|
-
}
|
|
4049
|
-
|
|
4050
|
-
.resize-handle-bottom-left,
|
|
4051
|
-
.resize-handle-bottom-start {
|
|
4052
|
-
cursor: sw-resize;
|
|
4053
|
-
}
|
|
4054
|
-
|
|
4055
|
-
.resize-handle-bottom-right,
|
|
4056
|
-
.resize-handle-bottom-end {
|
|
4057
|
-
cursor: se-resize;
|
|
4058
|
-
}
|
|
4059
|
-
|
|
4060
|
-
/* Screen overlay to get mouse coordinates */
|
|
4061
|
-
.resize-overlay {
|
|
4062
|
-
position: fixed;
|
|
4063
|
-
top: 0;
|
|
4064
|
-
left: 0;
|
|
4065
|
-
width: 100vw;
|
|
4066
|
-
height: 100vh;
|
|
4067
|
-
z-index: 9999;
|
|
4068
|
-
display: none;
|
|
4069
|
-
background: transparent
|
|
4070
|
-
}
|
|
4071
|
-
|
|
4072
|
-
/* Resize states */
|
|
4073
|
-
.resizable-closing {
|
|
4074
|
-
opacity: 0.5;
|
|
4075
|
-
transition: opacity 0.2s ease
|
|
4076
|
-
}
|
|
4077
|
-
|
|
4078
|
-
.resizable-closed {
|
|
4079
|
-
display: none !important
|
|
4080
|
-
}
|
|
4081
|
-
|
|
4082
|
-
/* Focus states for accessibility — framework ring (manifest.theme.css) */
|
|
4083
|
-
.resize-handle:focus {
|
|
4084
|
-
outline: none;
|
|
4085
|
-
box-shadow: 0 0 0 2px color-mix(in oklch, var(--color-content-stark) 30%, transparent)
|
|
4086
|
-
}
|
|
4087
|
-
|
|
4088
|
-
/* Reduced motion support */
|
|
4089
|
-
@media (prefers-reduced-motion: reduce) {
|
|
4090
|
-
|
|
4091
|
-
:where(.resizable-closing,
|
|
4092
|
-
.resize-handle) {
|
|
4093
|
-
transition: none
|
|
4094
|
-
}
|
|
4095
|
-
}
|
|
4096
|
-
}
|
|
4097
|
-
|
|
4098
|
-
/* RTL cursor flips — placement follows the logical insets in the shared geometry */
|
|
4099
|
-
[dir=rtl] :where([x-resize]) .resize-handle-top-start {
|
|
4100
|
-
cursor: ne-resize;
|
|
4101
|
-
}
|
|
4102
|
-
|
|
4103
|
-
[dir=rtl] :where([x-resize]) .resize-handle-top-end {
|
|
4104
|
-
cursor: nw-resize;
|
|
4105
|
-
}
|
|
4106
|
-
|
|
4107
|
-
[dir=rtl] :where([x-resize]) .resize-handle-bottom-start {
|
|
4108
|
-
cursor: se-resize;
|
|
4109
|
-
}
|
|
4110
|
-
|
|
4111
|
-
[dir=rtl] :where([x-resize]) .resize-handle-bottom-end {
|
|
4112
|
-
cursor: sw-resize;
|
|
4113
|
-
}
|
|
4114
|
-
}
|
|
4115
|
-
|
|
4116
3957
|
/* Manifest Sidebars */
|
|
4117
3958
|
|
|
4118
3959
|
@layer base, components, utilities;
|
|
@@ -4450,6 +4291,26 @@
|
|
|
4450
4291
|
}
|
|
4451
4292
|
}
|
|
4452
4293
|
|
|
4294
|
+
/* ---- Selection menu (x-text-edit-menu) ----
|
|
4295
|
+
Positioned by the --text-edit-selection-* variables the plugin writes to
|
|
4296
|
+
:root, so the default needs no script; restyle or reposition freely. */
|
|
4297
|
+
:where([data-text-edit-menu]) {
|
|
4298
|
+
position: fixed;
|
|
4299
|
+
margin: 0;
|
|
4300
|
+
inset: auto;
|
|
4301
|
+
left: var(--text-edit-selection-center);
|
|
4302
|
+
top: var(--text-edit-selection-y);
|
|
4303
|
+
transform: translate(-50%, calc(-100% - 0.5rem));
|
|
4304
|
+
display: flex;
|
|
4305
|
+
gap: 0.25rem;
|
|
4306
|
+
align-items: center;
|
|
4307
|
+
padding: 0.25rem;
|
|
4308
|
+
background: var(--color-surface-1);
|
|
4309
|
+
border: 1px solid var(--color-line);
|
|
4310
|
+
border-radius: var(--radius);
|
|
4311
|
+
box-shadow: 0 8px 30px rgb(0 0 0 / 0.2);
|
|
4312
|
+
}
|
|
4313
|
+
|
|
4453
4314
|
/* ---- Controls ----
|
|
4454
4315
|
A control is marked wherever it lives, so a toolbar is optional markup and
|
|
4455
4316
|
never a requirement. The reset already dims [aria-disabled=true]. */
|
package/lib/manifest.d.ts
CHANGED
|
@@ -384,6 +384,8 @@ export interface ManifestEdit {
|
|
|
384
384
|
paste(el?: Element): boolean;
|
|
385
385
|
duplicate(el?: Element): boolean;
|
|
386
386
|
remove(el?: Element): boolean;
|
|
387
|
+
/** Move a block into another static region (area element or x-edit key), before `ref` or at the end. */
|
|
388
|
+
move(el: Element | null, dest: Element | string, before?: Element | null): boolean;
|
|
387
389
|
/** The addressable block containing a node, or null. */
|
|
388
390
|
block(node: Element): Element | null;
|
|
389
391
|
}
|