mnfst 0.5.195 → 0.5.197
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.chat.js +15 -2
- package/lib/manifest.colorpicker.css +7 -2
- package/lib/manifest.colorpicker.js +8 -1
- package/lib/manifest.css +537 -189
- package/lib/manifest.d.ts +80 -1
- package/lib/manifest.datepicker.css +7 -2
- package/lib/manifest.datepicker.js +11 -2
- package/lib/manifest.dialog.css +12 -4
- package/lib/manifest.dropdown.css +7 -2
- package/lib/manifest.dropdowns.js +9 -1
- package/lib/manifest.edit.css +335 -0
- package/lib/manifest.edit.js +1373 -0
- package/lib/manifest.form.css +6 -4
- package/lib/manifest.integrity.json +7 -5
- package/lib/manifest.js +6 -1
- package/lib/manifest.min.css +1 -1
- package/lib/manifest.resize.css +81 -120
- package/lib/manifest.sidebar.css +7 -2
- package/lib/manifest.text.edit.css +204 -0
- package/lib/manifest.text.edit.js +2020 -0
- package/package.json +2 -1
package/lib/manifest.d.ts
CHANGED
|
@@ -331,6 +331,80 @@ export interface ManifestCamera {
|
|
|
331
331
|
pick(opts?: Record<string, unknown>): Promise<unknown>;
|
|
332
332
|
}
|
|
333
333
|
|
|
334
|
+
/** Element editor (`x-edit`) — opt-in plugin. Sortable lists and resizable elements
|
|
335
|
+
* by default; add `.authoring` where a region is a page being edited. */
|
|
336
|
+
export interface ManifestEdit {
|
|
337
|
+
/** True while a `.gated` area is editable. */
|
|
338
|
+
active: boolean;
|
|
339
|
+
canUndo: boolean;
|
|
340
|
+
canRedo: boolean;
|
|
341
|
+
/** Set a handler to route patches somewhere other than the dev server. */
|
|
342
|
+
onPublish: ((patches: unknown[], state: { log: unknown[]; cursor: number }) => unknown) | null;
|
|
343
|
+
toggle(): void;
|
|
344
|
+
on(): void;
|
|
345
|
+
off(): void;
|
|
346
|
+
undo(): void;
|
|
347
|
+
redo(): void;
|
|
348
|
+
/** Lock an element and its subtree out of editing (runtime, not logged). */
|
|
349
|
+
lock(el: Element): void;
|
|
350
|
+
unlock(el: Element): void;
|
|
351
|
+
/** Resolve the edit log into source patches and send them. */
|
|
352
|
+
publish(): Promise<unknown>;
|
|
353
|
+
/** The resolved source patches, without sending them. */
|
|
354
|
+
patches(): unknown[];
|
|
355
|
+
/** The raw delta log — persist it yourself to move edits between devices. */
|
|
356
|
+
export(): { log: unknown[]; cursor: number };
|
|
357
|
+
/** The block a context menu is acting on — set by the last right-click, or
|
|
358
|
+
* assignable. Block operations default to it when called with no element. */
|
|
359
|
+
target: Element | null;
|
|
360
|
+
/** Whether an operation applies right now: copy, cut, paste, duplicate, remove. */
|
|
361
|
+
can(op: string, el?: Element): boolean;
|
|
362
|
+
copy(el?: Element): boolean;
|
|
363
|
+
cut(el?: Element): boolean;
|
|
364
|
+
paste(el?: Element): boolean;
|
|
365
|
+
duplicate(el?: Element): boolean;
|
|
366
|
+
remove(el?: Element): boolean;
|
|
367
|
+
/** The addressable block containing a node, or null. */
|
|
368
|
+
block(node: Element): Element | null;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
/** Text editor (`x-text-edit`) — the area this element sits in, or the last focused one. */
|
|
372
|
+
export interface ManifestTextEdit {
|
|
373
|
+
/** The stored value, in the mode the directive was given. Assigning replaces the
|
|
374
|
+
* document and resets undo history. */
|
|
375
|
+
value: string;
|
|
376
|
+
focus(): void;
|
|
377
|
+
/** The href at the caret; assign to set it, assign '' to unlink. */
|
|
378
|
+
link: string;
|
|
379
|
+
/** Page-level styles (font, size, leading, align, color, background) as CSS
|
|
380
|
+
* custom properties on the area. Presentation, not document content — persist
|
|
381
|
+
* it yourself. Assign `{}` to clear. */
|
|
382
|
+
page: Record<string, string>;
|
|
383
|
+
/** Select the whole document. */
|
|
384
|
+
selectAll(): void;
|
|
385
|
+
/** The current selection and its geometry, or null. Also emitted as a
|
|
386
|
+
* `text-edit:selection` event and as --text-edit-selection-* custom properties
|
|
387
|
+
* on the area, for anchoring a menu to it. */
|
|
388
|
+
selection: {
|
|
389
|
+
collapsed: boolean; text: string;
|
|
390
|
+
x: number; y: number; width: number; height: number;
|
|
391
|
+
top: number; bottom: number; left: number; right: number;
|
|
392
|
+
} | null;
|
|
393
|
+
/** Apply a command. Commands are named for the tag they produce — 'strong',
|
|
394
|
+
* 'blockquote', 'h2', 'ul', 'a', 'img', 'table' — plus the operations with no
|
|
395
|
+
* tag of their own: 'indent', 'outdent', 'align', 'color', 'background',
|
|
396
|
+
* 'font', 'size', 'checklist', 'clear', 'undo', 'redo', 'block'. */
|
|
397
|
+
run(command: string, arg?: string | number): void;
|
|
398
|
+
/** Whether that command is active at the caret. */
|
|
399
|
+
active(command: string): boolean;
|
|
400
|
+
/** Whether that command is available in this area's mode. */
|
|
401
|
+
can(command: string): boolean;
|
|
402
|
+
/** Read the content as markdown regardless of the storage mode. */
|
|
403
|
+
markdown(): string;
|
|
404
|
+
/** Read the content as sanitized HTML regardless of the storage mode. */
|
|
405
|
+
html(): string;
|
|
406
|
+
}
|
|
407
|
+
|
|
334
408
|
/**
|
|
335
409
|
* Manifest's custom-element/attribute authoring surface (documentation aid —
|
|
336
410
|
* these are HTML attributes, not JS globals). Element styles pair with most.
|
|
@@ -340,7 +414,8 @@ export type ManifestDirective =
|
|
|
340
414
|
| 'x-icon' | 'x-svg' | 'x-markdown' | 'x-code' | 'x-code-group'
|
|
341
415
|
| 'x-toast' | 'x-tooltip' | 'x-carousel' | 'x-virtual' | 'x-resize'
|
|
342
416
|
| 'x-chart' | 'x-date' | 'x-color' | 'x-colorpicker' | 'x-combobox'
|
|
343
|
-
| 'x-export' | 'x-pay' | 'x-files' | 'x-data-files'
|
|
417
|
+
| 'x-export' | 'x-pay' | 'x-files' | 'x-data-files'
|
|
418
|
+
| 'x-edit' | 'x-text-edit';
|
|
344
419
|
|
|
345
420
|
// ---------------------------------------------------------------------------
|
|
346
421
|
// Global declarations (what Alpine exposes inside `x-data`, `x-show`, etc.)
|
|
@@ -381,6 +456,10 @@ declare global {
|
|
|
381
456
|
const $chart: ManifestChart;
|
|
382
457
|
/** Export plugin. */
|
|
383
458
|
const $export: ManifestExport;
|
|
459
|
+
/** Element editor (edit plugin; opt-in). */
|
|
460
|
+
const $edit: ManifestEdit;
|
|
461
|
+
/** Text editor (text-edit plugin). */
|
|
462
|
+
const $text: ManifestTextEdit;
|
|
384
463
|
/** Device/platform info (utilities; enriched by the native plugin). */
|
|
385
464
|
const $device: ManifestDevice;
|
|
386
465
|
/** Native share (native plugin). */
|
|
@@ -10,13 +10,18 @@
|
|
|
10
10
|
|
|
11
11
|
@layer base {
|
|
12
12
|
|
|
13
|
+
/* Animates `scale`, not `transform`. With fill mode `both` a transform
|
|
14
|
+
animation keeps its final value forever, which silently overrides any
|
|
15
|
+
transform an author put on a popover — so a menu positioned with
|
|
16
|
+
translate(-50%) simply would not move. The standalone property leaves
|
|
17
|
+
transform free. */
|
|
13
18
|
@keyframes mnfst-popover-in {
|
|
14
19
|
from {
|
|
15
|
-
|
|
20
|
+
scale: .9
|
|
16
21
|
}
|
|
17
22
|
|
|
18
23
|
to {
|
|
19
|
-
|
|
24
|
+
scale: 1
|
|
20
25
|
}
|
|
21
26
|
}
|
|
22
27
|
|
|
@@ -454,7 +454,13 @@
|
|
|
454
454
|
const t = this.rootEl;
|
|
455
455
|
if (t.matches(':popover-open')) { this.close(); return; } // toggle
|
|
456
456
|
this.render();
|
|
457
|
-
|
|
457
|
+
// Body-appended, so neither the popover tree nor manifest.dialog.css can see
|
|
458
|
+
// that this calendar belongs to the field's dialog or menu. `source` names the
|
|
459
|
+
// invoker (a text input can't carry popovertarget), without which opening the
|
|
460
|
+
// calendar light-dismisses the very thing it was opened from; the attribute
|
|
461
|
+
// exempts it from the stray-dropdown rule that would otherwise fade it out.
|
|
462
|
+
t.toggleAttribute('data-popover-nested', !!(this.field && this.field.closest('[popover]')));
|
|
463
|
+
try { t.showPopover({ source: this.field }); } catch (_) { }
|
|
458
464
|
},
|
|
459
465
|
close() {
|
|
460
466
|
if (this._closeTimeMenu) this._closeTimeMenu();
|
|
@@ -802,9 +808,12 @@
|
|
|
802
808
|
segs = { hIn, mIn, apBtn };
|
|
803
809
|
|
|
804
810
|
// Columns menu — body-appended popover so the calendar's overflow can't clip it,
|
|
805
|
-
// yet nests under the in-calendar invoker (won't dismiss the calendar).
|
|
811
|
+
// yet nests under the in-calendar invoker (won't dismiss the calendar). Its owner
|
|
812
|
+
// is always a calendar, never the page, so it is never a stray dropdown for
|
|
813
|
+
// manifest.dialog.css to fade out.
|
|
806
814
|
const menu = buildColumns(document.createElement('menu'), false);
|
|
807
815
|
menu.setAttribute('popover', '');
|
|
816
|
+
menu.setAttribute('data-popover-nested', '');
|
|
808
817
|
menu.id = 'mnfst-dp-time-' + (++_uid);
|
|
809
818
|
if (state._timeMenuEl && state._timeMenuEl.isConnected) state._timeMenuEl.remove();
|
|
810
819
|
document.body.appendChild(menu);
|
package/lib/manifest.dialog.css
CHANGED
|
@@ -10,13 +10,18 @@
|
|
|
10
10
|
|
|
11
11
|
@layer base {
|
|
12
12
|
|
|
13
|
+
/* Animates `scale`, not `transform`. With fill mode `both` a transform
|
|
14
|
+
animation keeps its final value forever, which silently overrides any
|
|
15
|
+
transform an author put on a popover — so a menu positioned with
|
|
16
|
+
translate(-50%) simply would not move. The standalone property leaves
|
|
17
|
+
transform free. */
|
|
13
18
|
@keyframes mnfst-popover-in {
|
|
14
19
|
from {
|
|
15
|
-
|
|
20
|
+
scale: .9
|
|
16
21
|
}
|
|
17
22
|
|
|
18
23
|
to {
|
|
19
|
-
|
|
24
|
+
scale: 1
|
|
20
25
|
}
|
|
21
26
|
}
|
|
22
27
|
|
|
@@ -101,8 +106,11 @@
|
|
|
101
106
|
/* Hide dropdowns when dialogs are open */
|
|
102
107
|
html:has(dialog:popover-open) {
|
|
103
108
|
|
|
104
|
-
/* Hide dropdowns outside of any dialog
|
|
105
|
-
|
|
109
|
+
/* Hide dropdowns outside of any dialog. A generated popover (a datepicker
|
|
110
|
+
calendar, a colorpicker panel) is appended to <body>, so DOM position alone
|
|
111
|
+
can't tell page chrome from a menu the dialog itself opened —
|
|
112
|
+
data-popover-nested marks the latter, which CSS has no way to work out. */
|
|
113
|
+
& menu[popover]:not(dialog *):not([data-popover-nested]) {
|
|
106
114
|
opacity: 0;
|
|
107
115
|
scale: .9;
|
|
108
116
|
pointer-events: none;
|
|
@@ -10,13 +10,18 @@
|
|
|
10
10
|
|
|
11
11
|
@layer base {
|
|
12
12
|
|
|
13
|
+
/* Animates `scale`, not `transform`. With fill mode `both` a transform
|
|
14
|
+
animation keeps its final value forever, which silently overrides any
|
|
15
|
+
transform an author put on a popover — so a menu positioned with
|
|
16
|
+
translate(-50%) simply would not move. The standalone property leaves
|
|
17
|
+
transform free. */
|
|
13
18
|
@keyframes mnfst-popover-in {
|
|
14
19
|
from {
|
|
15
|
-
|
|
20
|
+
scale: .9
|
|
16
21
|
}
|
|
17
22
|
|
|
18
23
|
to {
|
|
19
|
-
|
|
24
|
+
scale: 1
|
|
20
25
|
}
|
|
21
26
|
}
|
|
22
27
|
|
|
@@ -97,6 +97,10 @@ function initializeDropdownPlugin() {
|
|
|
97
97
|
const uniqueDropdownId = `dropdown-${anchorCode}`;
|
|
98
98
|
menu.setAttribute('id', uniqueDropdownId);
|
|
99
99
|
document.body.appendChild(menu);
|
|
100
|
+
// Appending to <body> loses the fact that the trigger sits inside a
|
|
101
|
+
// dialog, which is how manifest.dialog.css tells a dialog's own menus
|
|
102
|
+
// from the page's. An authored menu keeps its place and needs no mark.
|
|
103
|
+
if (el.closest('[popover]')) menu.setAttribute('data-popover-nested', '');
|
|
100
104
|
if (!modifiers.includes('context')) el.setAttribute('popovertarget', uniqueDropdownId);
|
|
101
105
|
|
|
102
106
|
// Initialize Alpine on the cloned menu
|
|
@@ -127,6 +131,7 @@ function initializeDropdownPlugin() {
|
|
|
127
131
|
menu = menuElement.cloneNode(true);
|
|
128
132
|
menu.setAttribute('id', dropdownId);
|
|
129
133
|
document.body.appendChild(menu);
|
|
134
|
+
if (el.closest('[popover]')) menu.setAttribute('data-popover-nested', '');
|
|
130
135
|
if (!modifiers.includes('context')) el.setAttribute('popovertarget', dropdownId);
|
|
131
136
|
|
|
132
137
|
// Initialize Alpine on the menu
|
|
@@ -220,7 +225,10 @@ function initializeDropdownPlugin() {
|
|
|
220
225
|
clearTimeout(autoCloseTimeout);
|
|
221
226
|
|
|
222
227
|
pointMenuHere();
|
|
223
|
-
|
|
228
|
+
// A hover open never runs the trigger's activation behaviour, so
|
|
229
|
+
// popovertarget alone doesn't register it as the invoker — say so
|
|
230
|
+
// here, or the menu light-dismisses whatever contains its trigger.
|
|
231
|
+
menu.showPopover({ source: el });
|
|
224
232
|
}
|
|
225
233
|
};
|
|
226
234
|
|
|
@@ -0,0 +1,335 @@
|
|
|
1
|
+
/* Manifest Edit */
|
|
2
|
+
|
|
3
|
+
@layer base, components, utilities;
|
|
4
|
+
|
|
5
|
+
/* Self-contained: every rule the plugin needs lives here, so this stylesheet works
|
|
6
|
+
on its own. Authors override without specificity battles — rules are :where()
|
|
7
|
+
wrapped and keyed off data-edit-* attributes, including the injected affordances
|
|
8
|
+
(handles, overlay, live region, toolbar, menu). No class names to collide with. */
|
|
9
|
+
|
|
10
|
+
@layer utilities {
|
|
11
|
+
|
|
12
|
+
/* ---- Editable area (armed) ----
|
|
13
|
+
Chrome is opt-in. Most uses of this plugin are a resize handle or a sortable
|
|
14
|
+
list, which should look like themselves; only .authoring gets the outline and
|
|
15
|
+
the region label. */
|
|
16
|
+
:where([data-edit-armed]) {
|
|
17
|
+
position: relative;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/* Quiet until touched. A page covered in dashed boxes reads as a diagram of
|
|
21
|
+
itself; the affordance only has to be there when you go looking for it. */
|
|
22
|
+
:where([data-edit-armed][data-edit-authoring]) {
|
|
23
|
+
outline: 1px dashed transparent;
|
|
24
|
+
outline-offset: 6px;
|
|
25
|
+
border-radius: var(--radius);
|
|
26
|
+
transition: outline-color 0.12s ease;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
:where([data-edit-armed][data-edit-authoring]):hover,
|
|
30
|
+
:where([data-edit-armed][data-edit-authoring]):focus-within {
|
|
31
|
+
outline-color: var(--color-line);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
:where([data-edit-armed][data-edit-authoring])::before {
|
|
35
|
+
content: attr(data-edit-label);
|
|
36
|
+
position: absolute;
|
|
37
|
+
inset-block-start: -1.5rem;
|
|
38
|
+
inset-inline-start: 0;
|
|
39
|
+
font-size: 0.7rem;
|
|
40
|
+
letter-spacing: 0.04em;
|
|
41
|
+
text-transform: uppercase;
|
|
42
|
+
color: var(--color-content-subtle);
|
|
43
|
+
pointer-events: none;
|
|
44
|
+
opacity: 0;
|
|
45
|
+
transition: opacity 0.12s ease;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
:where([data-edit-armed][data-edit-authoring]):hover::before,
|
|
49
|
+
:where([data-edit-armed][data-edit-authoring]):focus-within::before {
|
|
50
|
+
opacity: 1;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/* ---- Reorder (pointer + keyboard; touch-action:none lets touch-drag beat scroll) ---- */
|
|
54
|
+
:where([data-edit-sortable]) {
|
|
55
|
+
cursor: grab;
|
|
56
|
+
touch-action: none;
|
|
57
|
+
transition: opacity 0.12s ease, outline-color 0.12s ease;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
:where([data-edit-sortable]):hover,
|
|
61
|
+
:where([data-edit-sortable]):focus-within {
|
|
62
|
+
outline: 1px solid var(--edit-accent, var(--color-brand-content));
|
|
63
|
+
outline-offset: 3px;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
:where([data-edit-sortable], [data-edit-movable]):focus-visible {
|
|
67
|
+
outline: 2px solid var(--edit-accent, var(--color-brand-content));
|
|
68
|
+
outline-offset: 3px;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/* Lifted out of the flow and following the pointer. */
|
|
72
|
+
:where([data-edit-sortable][data-edit-dragging]) {
|
|
73
|
+
z-index: 1000;
|
|
74
|
+
cursor: grabbing;
|
|
75
|
+
box-shadow: 0 8px 24px rgb(0 0 0 / 0.18);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/* The stand-in holding the slot the drop would land in — a translucent copy of
|
|
79
|
+
the element by default, because the clearest preview of where it lands is the
|
|
80
|
+
thing itself. Restyle it freely: hide its contents for an empty outlined slot,
|
|
81
|
+
give it a background, animate it. */
|
|
82
|
+
:where([data-edit-ghost]) {
|
|
83
|
+
opacity: var(--edit-ghost-opacity, 0.4);
|
|
84
|
+
pointer-events: none;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/* Nothing else should react while a drag is in progress */
|
|
88
|
+
:where([data-edit-dragging-in]) {
|
|
89
|
+
cursor: grabbing;
|
|
90
|
+
user-select: none;
|
|
91
|
+
|
|
92
|
+
& :where([data-edit-sortable]:not([data-edit-dragging])) {
|
|
93
|
+
cursor: grabbing;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
:where([data-edit-grabbed]) {
|
|
98
|
+
outline: 2px dashed var(--edit-accent, var(--color-brand-content));
|
|
99
|
+
outline-offset: 3px;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/* ---- Move (positioned children) ---- */
|
|
103
|
+
:where([data-edit-movable]) {
|
|
104
|
+
cursor: move;
|
|
105
|
+
touch-action: none;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
:where([data-edit-movable]):hover {
|
|
109
|
+
outline: 1px solid var(--edit-accent, var(--color-brand-content));
|
|
110
|
+
outline-offset: 2px;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/* ---- Inline text ---- */
|
|
114
|
+
:where([data-edit-armed]) [contenteditable="true"] {
|
|
115
|
+
outline: 1px solid transparent;
|
|
116
|
+
outline-offset: 2px;
|
|
117
|
+
border-radius: 3px;
|
|
118
|
+
transition: outline-color 0.12s ease;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
:where([data-edit-armed]) [contenteditable="true"]:hover {
|
|
122
|
+
outline-color: color-mix(in oklch, var(--edit-accent, var(--color-brand-content)) 45%, transparent);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
:where([data-edit-armed]) [contenteditable="true"]:focus {
|
|
126
|
+
outline: 2px solid var(--edit-accent, var(--color-brand-content));
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/* ---- Size handles ----
|
|
130
|
+
Injected as siblings of the element's own markup, so its native :hover and
|
|
131
|
+
:focus still fire and its body stays interactive. */
|
|
132
|
+
:where([data-edit-sizable]) {
|
|
133
|
+
position: relative;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/* Handles reveal on approach rather than sitting on the element permanently */
|
|
137
|
+
:where([data-edit-sizable]) :where([data-edit-handle])::before {
|
|
138
|
+
opacity: 0;
|
|
139
|
+
transition: opacity 0.12s ease, background-color 0.15s ease;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
:where([data-edit-sizable]):hover :where([data-edit-handle])::before,
|
|
143
|
+
:where([data-edit-sizable]):focus-within :where([data-edit-handle])::before {
|
|
144
|
+
opacity: 1;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
:where([data-edit-handle]) {
|
|
148
|
+
position: absolute;
|
|
149
|
+
z-index: 100;
|
|
150
|
+
background: transparent;
|
|
151
|
+
--h: var(--edit-size-handle, 1rem);
|
|
152
|
+
|
|
153
|
+
/* Hairline, revealed on hover */
|
|
154
|
+
&::before {
|
|
155
|
+
content: '';
|
|
156
|
+
position: absolute;
|
|
157
|
+
top: 50%;
|
|
158
|
+
left: 50%;
|
|
159
|
+
transform: translate(-50%, -50%);
|
|
160
|
+
width: 1px;
|
|
161
|
+
height: 1px;
|
|
162
|
+
background: transparent;
|
|
163
|
+
transition: background-color 0.15s ease;
|
|
164
|
+
pointer-events: none;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
&:hover::before,
|
|
168
|
+
&:active::before {
|
|
169
|
+
background-color: var(--edit-accent, var(--color-brand-content));
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
:where([data-edit-handle]):focus-visible {
|
|
174
|
+
outline: 2px solid var(--edit-accent, var(--color-brand-content));
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/* Edges span their axis; corners are square. A corner value contains a hyphen. */
|
|
178
|
+
:where([data-edit-handle="top"], [data-edit-handle="bottom"]) {
|
|
179
|
+
left: 0;
|
|
180
|
+
width: 100%;
|
|
181
|
+
height: var(--h);
|
|
182
|
+
cursor: ns-resize;
|
|
183
|
+
|
|
184
|
+
&::before {
|
|
185
|
+
width: 100%;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
:where([data-edit-handle="left"], [data-edit-handle="right"], [data-edit-handle="start"], [data-edit-handle="end"]) {
|
|
190
|
+
top: 0;
|
|
191
|
+
width: var(--h);
|
|
192
|
+
height: 100%;
|
|
193
|
+
cursor: ew-resize;
|
|
194
|
+
|
|
195
|
+
&::before {
|
|
196
|
+
height: 100%;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
:where([data-edit-handle*="-"]) {
|
|
201
|
+
width: var(--h);
|
|
202
|
+
height: var(--h);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/* Inset — each handle straddles its edge; a corner matches one rule per axis.
|
|
206
|
+
inset-inline-* carries the logical start/end values, so they flip for RTL. */
|
|
207
|
+
:where([data-edit-handle^="top"]) {
|
|
208
|
+
top: calc(var(--h) * -0.5);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
:where([data-edit-handle^="bottom"]) {
|
|
212
|
+
bottom: calc(var(--h) * -0.5);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
:where([data-edit-handle$="left"]) {
|
|
216
|
+
left: calc(var(--h) * -0.5);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
:where([data-edit-handle$="right"]) {
|
|
220
|
+
right: calc(var(--h) * -0.5);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
:where([data-edit-handle$="start"]) {
|
|
224
|
+
inset-inline-start: calc(var(--h) * -0.5);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
:where([data-edit-handle$="end"]) {
|
|
228
|
+
inset-inline-end: calc(var(--h) * -0.5);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/* Corner cursors; the logical pair flips with direction. */
|
|
232
|
+
:where([data-edit-handle="top-left"], [data-edit-handle="bottom-right"], [data-edit-handle="top-start"], [data-edit-handle="bottom-end"]) {
|
|
233
|
+
cursor: nwse-resize;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
:where([data-edit-handle="top-right"], [data-edit-handle="bottom-left"], [data-edit-handle="top-end"], [data-edit-handle="bottom-start"]) {
|
|
237
|
+
cursor: nesw-resize;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
[dir=rtl] :where([data-edit-handle="top-start"], [data-edit-handle="bottom-end"]) {
|
|
241
|
+
cursor: nesw-resize;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
[dir=rtl] :where([data-edit-handle="top-end"], [data-edit-handle="bottom-start"]) {
|
|
245
|
+
cursor: nwse-resize;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/* Collapsed past --edit-size-collapse-* — authors restyle freely */
|
|
249
|
+
:where([data-edit-collapsed]) {
|
|
250
|
+
opacity: 0.45;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/* Full-viewport capture layer during a size or move drag */
|
|
254
|
+
:where([data-edit-overlay]) {
|
|
255
|
+
position: fixed;
|
|
256
|
+
inset: 0;
|
|
257
|
+
z-index: 9999;
|
|
258
|
+
background: transparent;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
/* Screen-reader announcements (keyboard grab / move / drop) */
|
|
262
|
+
:where([data-edit-live]) {
|
|
263
|
+
position: absolute;
|
|
264
|
+
width: 1px;
|
|
265
|
+
height: 1px;
|
|
266
|
+
margin: -1px;
|
|
267
|
+
padding: 0;
|
|
268
|
+
border: 0;
|
|
269
|
+
overflow: hidden;
|
|
270
|
+
clip-path: inset(50%);
|
|
271
|
+
white-space: nowrap;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
@media (prefers-reduced-motion: reduce) {
|
|
275
|
+
|
|
276
|
+
:where([data-edit-armed]) > [draggable="true"],
|
|
277
|
+
:where([data-edit-handle])::before {
|
|
278
|
+
transition: none;
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
/* ---- Authoring chrome (toolbar + class/scope menu) ---- */
|
|
284
|
+
@layer components {
|
|
285
|
+
|
|
286
|
+
/* Presence is script's business — it knows which route is on screen. Whether it
|
|
287
|
+
is seen is not, so the toolbar is styled and revealed here. */
|
|
288
|
+
:where([data-edit-toolbar]) {
|
|
289
|
+
display: none;
|
|
290
|
+
position: fixed;
|
|
291
|
+
inset-block-end: 1rem;
|
|
292
|
+
inset-inline-end: 1rem;
|
|
293
|
+
z-index: 9999;
|
|
294
|
+
gap: 0.5rem;
|
|
295
|
+
align-items: center;
|
|
296
|
+
|
|
297
|
+
& button[disabled] {
|
|
298
|
+
opacity: 0.35;
|
|
299
|
+
pointer-events: none;
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
:root[data-edit-active] :where([data-edit-toolbar]) {
|
|
304
|
+
display: flex;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
/* A popover: the top layer sits above every stacking context, so no handle,
|
|
308
|
+
overlay or z-index anywhere on the page can paint over it. */
|
|
309
|
+
:where([data-edit-menu]) {
|
|
310
|
+
position: fixed;
|
|
311
|
+
margin: 0;
|
|
312
|
+
width: 16rem;
|
|
313
|
+
padding: 0.6rem;
|
|
314
|
+
display: flex;
|
|
315
|
+
flex-direction: column;
|
|
316
|
+
gap: 0.4rem;
|
|
317
|
+
background: var(--color-surface-1);
|
|
318
|
+
border: 1px solid var(--color-line);
|
|
319
|
+
border-radius: var(--radius);
|
|
320
|
+
box-shadow: 0 8px 30px rgb(0 0 0 / 0.2);
|
|
321
|
+
|
|
322
|
+
& input {
|
|
323
|
+
width: 100%;
|
|
324
|
+
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
|
|
325
|
+
font-size: 0.75rem;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
& small {
|
|
329
|
+
font-size: 0.7rem;
|
|
330
|
+
text-transform: uppercase;
|
|
331
|
+
letter-spacing: 0.05em;
|
|
332
|
+
color: var(--color-content-subtle);
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
}
|