mnfst 0.5.220 → 0.5.221
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.css +56 -41
- package/lib/manifest.data.js +1 -1
- package/lib/manifest.edit.css +16 -29
- package/lib/manifest.edit.js +50 -12
- package/lib/manifest.integrity.json +5 -5
- package/lib/manifest.min.css +1 -1
- package/lib/manifest.sw.js +1 -1
- package/lib/manifest.text.edit.css +40 -12
- package/lib/manifest.text.edit.js +36 -2
- package/package.json +1 -1
package/lib/manifest.css
CHANGED
|
@@ -2946,18 +2946,18 @@
|
|
|
2946
2946
|
itself; the affordance only has to be there when you go looking for it.
|
|
2947
2947
|
While an inner affordance is engaged (text caret, handle, row), the region
|
|
2948
2948
|
outline stands down so only the element being edited is highlighted. */
|
|
2949
|
-
:where([data-edit-armed]):where([data-edit-ui], [data-edit-ui] *) {
|
|
2949
|
+
:where([data-edit-armed][data-edit-authoring]):where([data-edit-ui], [data-edit-ui] *) {
|
|
2950
2950
|
outline: 1px dashed transparent;
|
|
2951
2951
|
outline-offset: 6px;
|
|
2952
2952
|
border-radius: var(--radius);
|
|
2953
2953
|
transition: outline-color 0.12s ease;
|
|
2954
2954
|
}
|
|
2955
2955
|
|
|
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))) {
|
|
2956
|
+
:where([data-edit-armed][data-edit-authoring]):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))) {
|
|
2957
2957
|
outline-color: var(--color-line);
|
|
2958
2958
|
}
|
|
2959
2959
|
|
|
2960
|
-
:where([data-edit-armed]):where([data-edit-ui], [data-edit-ui] *)::before {
|
|
2960
|
+
:where([data-edit-armed][data-edit-authoring]):where([data-edit-ui], [data-edit-ui] *)::before {
|
|
2961
2961
|
content: attr(data-edit-label);
|
|
2962
2962
|
position: absolute;
|
|
2963
2963
|
inset-block-start: calc(-0.6em - 6px);
|
|
@@ -2975,7 +2975,7 @@
|
|
|
2975
2975
|
transition: opacity 0.12s ease;
|
|
2976
2976
|
}
|
|
2977
2977
|
|
|
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 {
|
|
2978
|
+
:where([data-edit-armed][data-edit-authoring]):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 {
|
|
2979
2979
|
opacity: 1;
|
|
2980
2980
|
}
|
|
2981
2981
|
|
|
@@ -2986,31 +2986,9 @@
|
|
|
2986
2986
|
transition: opacity 0.12s ease, outline-color 0.12s ease;
|
|
2987
2987
|
}
|
|
2988
2988
|
|
|
2989
|
-
/*
|
|
2990
|
-
|
|
2991
|
-
|
|
2992
|
-
would still occupy a grid cell). No full-row outline — the grip and the
|
|
2993
|
-
cursor say "draggable" without boxing the content. */
|
|
2994
|
-
:where([data-edit-ui] [data-edit-sortable], [data-edit-ui][data-edit-sortable])::before {
|
|
2995
|
-
content: '';
|
|
2996
|
-
display: var(--edit-grip-display, inline-block);
|
|
2997
|
-
vertical-align: middle;
|
|
2998
|
-
align-self: center;
|
|
2999
|
-
flex: 0 0 auto;
|
|
3000
|
-
width: var(--edit-grip, 0.6rem);
|
|
3001
|
-
height: 1rem;
|
|
3002
|
-
margin-inline-end: calc(var(--edit-grip, 0.6rem) / 2);
|
|
3003
|
-
background-image: radial-gradient(circle, var(--color-content-subtle) 1px, transparent 1px);
|
|
3004
|
-
background-size: 0.3rem 0.3rem;
|
|
3005
|
-
background-position: center;
|
|
3006
|
-
opacity: 0;
|
|
3007
|
-
transition: opacity 0.12s ease;
|
|
3008
|
-
pointer-events: none;
|
|
3009
|
-
}
|
|
3010
|
-
|
|
3011
|
-
:where([data-edit-ui] [data-edit-sortable], [data-edit-ui][data-edit-sortable]):is(:hover, :focus-visible)::before {
|
|
3012
|
-
opacity: 0.9;
|
|
3013
|
-
}
|
|
2989
|
+
/* No injected grip: the row itself is the handle (grab cursor), while text
|
|
2990
|
+
inside stays a text surface (text cursor, below). Rows that are pure text
|
|
2991
|
+
still reorder by keyboard. */
|
|
3014
2992
|
|
|
3015
2993
|
:where([data-edit-sortable], [data-edit-movable]):focus-visible {
|
|
3016
2994
|
outline: 2px solid var(--edit-accent, var(--color-brand-content));
|
|
@@ -3033,6 +3011,14 @@
|
|
|
3033
3011
|
pointer-events: none;
|
|
3034
3012
|
}
|
|
3035
3013
|
|
|
3014
|
+
/* Valid cross-region destinations during a drag: a dashed invitation, and
|
|
3015
|
+
enough height that an EMPTY region can be dropped onto at all. */
|
|
3016
|
+
:where([data-edit-drop-target]) {
|
|
3017
|
+
outline: 1px dashed color-mix(in oklch, var(--edit-accent, var(--color-brand-content)) 55%, transparent);
|
|
3018
|
+
outline-offset: 4px;
|
|
3019
|
+
min-height: 2.5rem;
|
|
3020
|
+
}
|
|
3021
|
+
|
|
3036
3022
|
/* Nothing else should react while a drag is in progress */
|
|
3037
3023
|
:where([data-edit-dragging-in]) {
|
|
3038
3024
|
cursor: grabbing;
|
|
@@ -3064,6 +3050,7 @@
|
|
|
3064
3050
|
1px line, so focusing doesn't make the page jump a weight class. */
|
|
3065
3051
|
:where([data-edit-ui]) :where([data-edit-armed]) [contenteditable="true"],
|
|
3066
3052
|
:where([data-edit-ui])[data-edit-armed] [contenteditable="true"] {
|
|
3053
|
+
cursor: text;
|
|
3067
3054
|
outline: 1px solid transparent;
|
|
3068
3055
|
outline-offset: 2px;
|
|
3069
3056
|
border-radius: 3px;
|
|
@@ -4263,13 +4250,9 @@
|
|
|
4263
4250
|
@layer components {
|
|
4264
4251
|
|
|
4265
4252
|
:where([data-text-edit]) {
|
|
4266
|
-
|
|
4267
|
-
max-height: var(--text-edit-max-height, none);
|
|
4253
|
+
position: relative;
|
|
4268
4254
|
overflow-y: auto;
|
|
4269
|
-
|
|
4270
|
-
background: var(--text-edit-background, var(--color-surface-1));
|
|
4271
|
-
border: 1px solid var(--color-line);
|
|
4272
|
-
border-radius: var(--radius);
|
|
4255
|
+
max-height: var(--text-edit-max-height, none);
|
|
4273
4256
|
outline: none;
|
|
4274
4257
|
|
|
4275
4258
|
/* Page-level styling — set by .page controls, read back by $text.page.
|
|
@@ -4280,19 +4263,45 @@
|
|
|
4280
4263
|
text-align: var(--text-edit-align, start);
|
|
4281
4264
|
color: var(--text-edit-color, inherit);
|
|
4282
4265
|
|
|
4283
|
-
&:focus {
|
|
4284
|
-
border-color: var(--color-content-subtle);
|
|
4285
|
-
}
|
|
4286
|
-
|
|
4287
4266
|
/* Flagged from script: :empty never matches the <br> a contenteditable
|
|
4288
|
-
keeps, and the tag holding it is not predictable after a wipe.
|
|
4267
|
+
keeps, and the tag holding it is not predictable after a wipe. The
|
|
4268
|
+
placeholder OVERLAYS the first line (it is not a line of its own), so
|
|
4269
|
+
the caret sits on top of it and nothing jumps when typing starts. */
|
|
4289
4270
|
&[data-text-edit-empty]::before {
|
|
4290
4271
|
content: attr(placeholder);
|
|
4272
|
+
position: absolute;
|
|
4273
|
+
inset-block-start: var(--text-edit-padding, 0.75rem);
|
|
4274
|
+
inset-inline: var(--text-edit-padding, 0.75rem);
|
|
4291
4275
|
color: var(--color-content-subtle);
|
|
4292
4276
|
pointer-events: none;
|
|
4293
4277
|
}
|
|
4294
4278
|
}
|
|
4295
4279
|
|
|
4280
|
+
/* An unstyled area has no field padding, so its placeholder hugs the line. */
|
|
4281
|
+
:where([data-text-edit].unstyle)[data-text-edit-empty]::before {
|
|
4282
|
+
inset-block-start: 0;
|
|
4283
|
+
inset-inline: 0;
|
|
4284
|
+
}
|
|
4285
|
+
|
|
4286
|
+
/* The FIELD look. `.unstyle` drops it so an area can sit seamlessly in a
|
|
4287
|
+
page — a WYSIWYG document inherits its element's own type and colours;
|
|
4288
|
+
everything above (caret custody, placeholder, commands) still works. */
|
|
4289
|
+
:where([data-text-edit]:not(.unstyle)) {
|
|
4290
|
+
min-height: var(--text-edit-min-height, 8rem);
|
|
4291
|
+
padding: var(--text-edit-padding, 0.75rem);
|
|
4292
|
+
background: var(--text-edit-background, var(--color-surface-1));
|
|
4293
|
+
border: 1px solid var(--color-line);
|
|
4294
|
+
border-radius: var(--radius);
|
|
4295
|
+
|
|
4296
|
+
/* Held while the area owns the editing session — a <select> or colour
|
|
4297
|
+
control takes NATIVE focus by design, and the ring must not flicker
|
|
4298
|
+
off while the writer is mid-command. */
|
|
4299
|
+
&:focus,
|
|
4300
|
+
&[data-text-edit-focused] {
|
|
4301
|
+
border-color: var(--color-content-subtle);
|
|
4302
|
+
}
|
|
4303
|
+
}
|
|
4304
|
+
|
|
4296
4305
|
/* ---- Selection menu (x-text-edit-menu) ----
|
|
4297
4306
|
Positioned by the --text-edit-selection-* variables the plugin writes to
|
|
4298
4307
|
:root, so the default needs no script; restyle or reposition freely. */
|
|
@@ -4303,7 +4312,6 @@
|
|
|
4303
4312
|
left: var(--text-edit-selection-center);
|
|
4304
4313
|
top: var(--text-edit-selection-y);
|
|
4305
4314
|
transform: translate(-50%, calc(-100% - 0.5rem));
|
|
4306
|
-
display: flex;
|
|
4307
4315
|
gap: 0.25rem;
|
|
4308
4316
|
align-items: center;
|
|
4309
4317
|
padding: 0.25rem;
|
|
@@ -4313,6 +4321,13 @@
|
|
|
4313
4321
|
box-shadow: 0 8px 30px rgb(0 0 0 / 0.2);
|
|
4314
4322
|
}
|
|
4315
4323
|
|
|
4324
|
+
/* display only while OPEN: an author display on the base rule would beat
|
|
4325
|
+
the UA's closed-popover display:none, leaving the menu floating fixed
|
|
4326
|
+
over the page before any selection exists. */
|
|
4327
|
+
:where([data-text-edit-menu]:popover-open) {
|
|
4328
|
+
display: flex;
|
|
4329
|
+
}
|
|
4330
|
+
|
|
4316
4331
|
/* Without the Popover API the attribute is inert and the menu would render
|
|
4317
4332
|
permanently fixed on the page — hide it there; the bubble is progressive
|
|
4318
4333
|
enhancement. (In supporting browsers a closed popover is hidden natively.) */
|
package/lib/manifest.data.js
CHANGED
package/lib/manifest.edit.css
CHANGED
|
@@ -26,18 +26,18 @@
|
|
|
26
26
|
itself; the affordance only has to be there when you go looking for it.
|
|
27
27
|
While an inner affordance is engaged (text caret, handle, row), the region
|
|
28
28
|
outline stands down so only the element being edited is highlighted. */
|
|
29
|
-
:where([data-edit-armed]):where([data-edit-ui], [data-edit-ui] *) {
|
|
29
|
+
:where([data-edit-armed][data-edit-authoring]):where([data-edit-ui], [data-edit-ui] *) {
|
|
30
30
|
outline: 1px dashed transparent;
|
|
31
31
|
outline-offset: 6px;
|
|
32
32
|
border-radius: var(--radius);
|
|
33
33
|
transition: outline-color 0.12s ease;
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
-
: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))) {
|
|
36
|
+
:where([data-edit-armed][data-edit-authoring]):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))) {
|
|
37
37
|
outline-color: var(--color-line);
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
-
:where([data-edit-armed]):where([data-edit-ui], [data-edit-ui] *)::before {
|
|
40
|
+
:where([data-edit-armed][data-edit-authoring]):where([data-edit-ui], [data-edit-ui] *)::before {
|
|
41
41
|
content: attr(data-edit-label);
|
|
42
42
|
position: absolute;
|
|
43
43
|
inset-block-start: calc(-0.6em - 6px);
|
|
@@ -55,7 +55,7 @@
|
|
|
55
55
|
transition: opacity 0.12s ease;
|
|
56
56
|
}
|
|
57
57
|
|
|
58
|
-
: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 {
|
|
58
|
+
:where([data-edit-armed][data-edit-authoring]):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 {
|
|
59
59
|
opacity: 1;
|
|
60
60
|
}
|
|
61
61
|
|
|
@@ -66,31 +66,9 @@
|
|
|
66
66
|
transition: opacity 0.12s ease, outline-color 0.12s ease;
|
|
67
67
|
}
|
|
68
68
|
|
|
69
|
-
/*
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
would still occupy a grid cell). No full-row outline — the grip and the
|
|
73
|
-
cursor say "draggable" without boxing the content. */
|
|
74
|
-
:where([data-edit-ui] [data-edit-sortable], [data-edit-ui][data-edit-sortable])::before {
|
|
75
|
-
content: '';
|
|
76
|
-
display: var(--edit-grip-display, inline-block);
|
|
77
|
-
vertical-align: middle;
|
|
78
|
-
align-self: center;
|
|
79
|
-
flex: 0 0 auto;
|
|
80
|
-
width: var(--edit-grip, 0.6rem);
|
|
81
|
-
height: 1rem;
|
|
82
|
-
margin-inline-end: calc(var(--edit-grip, 0.6rem) / 2);
|
|
83
|
-
background-image: radial-gradient(circle, var(--color-content-subtle) 1px, transparent 1px);
|
|
84
|
-
background-size: 0.3rem 0.3rem;
|
|
85
|
-
background-position: center;
|
|
86
|
-
opacity: 0;
|
|
87
|
-
transition: opacity 0.12s ease;
|
|
88
|
-
pointer-events: none;
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
:where([data-edit-ui] [data-edit-sortable], [data-edit-ui][data-edit-sortable]):is(:hover, :focus-visible)::before {
|
|
92
|
-
opacity: 0.9;
|
|
93
|
-
}
|
|
69
|
+
/* No injected grip: the row itself is the handle (grab cursor), while text
|
|
70
|
+
inside stays a text surface (text cursor, below). Rows that are pure text
|
|
71
|
+
still reorder by keyboard. */
|
|
94
72
|
|
|
95
73
|
:where([data-edit-sortable], [data-edit-movable]):focus-visible {
|
|
96
74
|
outline: 2px solid var(--edit-accent, var(--color-brand-content));
|
|
@@ -113,6 +91,14 @@
|
|
|
113
91
|
pointer-events: none;
|
|
114
92
|
}
|
|
115
93
|
|
|
94
|
+
/* Valid cross-region destinations during a drag: a dashed invitation, and
|
|
95
|
+
enough height that an EMPTY region can be dropped onto at all. */
|
|
96
|
+
:where([data-edit-drop-target]) {
|
|
97
|
+
outline: 1px dashed color-mix(in oklch, var(--edit-accent, var(--color-brand-content)) 55%, transparent);
|
|
98
|
+
outline-offset: 4px;
|
|
99
|
+
min-height: 2.5rem;
|
|
100
|
+
}
|
|
101
|
+
|
|
116
102
|
/* Nothing else should react while a drag is in progress */
|
|
117
103
|
:where([data-edit-dragging-in]) {
|
|
118
104
|
cursor: grabbing;
|
|
@@ -144,6 +130,7 @@
|
|
|
144
130
|
1px line, so focusing doesn't make the page jump a weight class. */
|
|
145
131
|
:where([data-edit-ui]) :where([data-edit-armed]) [contenteditable="true"],
|
|
146
132
|
:where([data-edit-ui])[data-edit-armed] [contenteditable="true"] {
|
|
133
|
+
cursor: text;
|
|
147
134
|
outline: 1px solid transparent;
|
|
148
135
|
outline-offset: 2px;
|
|
149
136
|
border-radius: 3px;
|
package/lib/manifest.edit.js
CHANGED
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
|
|
18
18
|
(function () {
|
|
19
19
|
const LS_KEY = 'mnfst-edit-log';
|
|
20
|
-
const SCHEMA =
|
|
20
|
+
const SCHEMA = 7; // overlay schema version — bump when the delta shape changes
|
|
21
21
|
const HISTORY_CAP = 400; // cap the append-only log so long sessions don't grow unbounded
|
|
22
22
|
const ALL_CAPS = ['sort', 'text', 'style', 'size', 'data']; // 'data' = edit $x field values (opt-in)
|
|
23
23
|
let dragged = null, autoN = 0;
|
|
@@ -367,7 +367,12 @@
|
|
|
367
367
|
const region = key(area), path = el.getAttribute('data-edit-key');
|
|
368
368
|
if (!path) return; // not an addressable node
|
|
369
369
|
if (prop === 'text') value = sanitizeFor(el, value);
|
|
370
|
-
|
|
370
|
+
// _pre* comes from the focus/open listener; when an edit arrives without
|
|
371
|
+
// one (programmatic focus, scripted writes), the arm-time baseline keeps
|
|
372
|
+
// `before` truthful so undo can still reach the original.
|
|
373
|
+
const fallback = prop === 'text' ? area._baseText && area._baseText[path] : prop === 'class' ? area._baseClass && area._baseClass[path] : area._baseStyle && area._baseStyle[path];
|
|
374
|
+
const pre = prop === 'text' ? el._preEdit : prop === 'class' ? el._preClass : el._preStyle;
|
|
375
|
+
const before = pre !== undefined ? pre : fallback;
|
|
371
376
|
if (before === value) return;
|
|
372
377
|
log.splice(cursor); log.push({ kind: 'st-node', region, path, prop, value, before, sig: nodeSig(el) }); cursor = log.length; saveState(); refresh();
|
|
373
378
|
}
|
|
@@ -421,7 +426,7 @@
|
|
|
421
426
|
if (el.hasAttribute('data-edit-handle') || el.hasAttribute('data-edit-ghost')) continue;
|
|
422
427
|
if (el.parentElement && el.parentElement.closest('[data-text-edit]')) continue;
|
|
423
428
|
const cur = el.getAttribute('data-edit-key');
|
|
424
|
-
const base = cur ?
|
|
429
|
+
const base = cur ? ((/^([\s\S]*)#\d+$/.exec(cur) || [, cur])[1]) : staticKey(el); // ordinal = last #digits suffix
|
|
425
430
|
const n = seen[base] = (seen[base] || 0) + 1;
|
|
426
431
|
const k = n > 1 ? base + '#' + n : base;
|
|
427
432
|
if (cur !== k) { el.setAttribute('data-edit-key', k); if (cur) changes.push([cur, k]); }
|
|
@@ -775,6 +780,12 @@
|
|
|
775
780
|
// opens is a real element the author can style — by default a translucent
|
|
776
781
|
// copy of what is being dragged, showing exactly where it would land.
|
|
777
782
|
const lift = (ev) => {
|
|
783
|
+
// Valid cross-region destinations announce themselves for the whole
|
|
784
|
+
// drag: a dashed target outline, and a min-height (via CSS) so an
|
|
785
|
+
// EMPTY region has a surface to drop onto at all.
|
|
786
|
+
if (crossOK) areas().forEach(a => {
|
|
787
|
+
if (a !== area && isActive(a) && !locked(a) && capOf(a, 'sort') && classify(a) === 'static' && topArea(a)) a.setAttribute('data-edit-drop-target', '');
|
|
788
|
+
});
|
|
778
789
|
ghost = item.cloneNode(true);
|
|
779
790
|
ghost.setAttribute('data-edit-ghost', '');
|
|
780
791
|
ghost.setAttribute('x-ignore', ''); // a clone must not re-bind
|
|
@@ -822,6 +833,7 @@
|
|
|
822
833
|
const settle = (cancelled) => {
|
|
823
834
|
document.removeEventListener('pointermove', onMove);
|
|
824
835
|
document.removeEventListener('pointerup', onUp);
|
|
836
|
+
document.removeEventListener('pointercancel', onCancel);
|
|
825
837
|
document.removeEventListener('keydown', onKey, true);
|
|
826
838
|
if (frame) cancelAnimationFrame(frame);
|
|
827
839
|
if (!active) return;
|
|
@@ -832,6 +844,7 @@
|
|
|
832
844
|
if (preStyle == null) item.removeAttribute('style'); else item.setAttribute('style', preStyle);
|
|
833
845
|
item.removeAttribute('data-edit-dragging');
|
|
834
846
|
area.removeAttribute('data-edit-dragging-in');
|
|
847
|
+
document.querySelectorAll('[data-edit-drop-target]').forEach(a => a.removeAttribute('data-edit-drop-target'));
|
|
835
848
|
if (hovered !== container) hovered.removeAttribute('data-edit-dragging-in');
|
|
836
849
|
ghost = null; dragged = null;
|
|
837
850
|
if (cancelled) { announce('Cancelled'); return; }
|
|
@@ -845,10 +858,14 @@
|
|
|
845
858
|
else finishReorder(area);
|
|
846
859
|
};
|
|
847
860
|
const onUp = () => settle(false);
|
|
848
|
-
// Escape puts it back, the way every drag is expected to be escapable.
|
|
861
|
+
// Escape puts it back, the way every drag is expected to be escapable. A
|
|
862
|
+
// cancelled pointer (OS gesture, alert, pen out of range) also reverts —
|
|
863
|
+
// otherwise the item wedges position:fixed with drop targets still lit.
|
|
864
|
+
const onCancel = () => settle(true);
|
|
849
865
|
const onKey = (ev) => { if (ev.key !== 'Escape') return; ev.preventDefault(); ev.stopPropagation(); settle(true); };
|
|
850
866
|
document.addEventListener('pointermove', onMove);
|
|
851
867
|
document.addEventListener('pointerup', onUp);
|
|
868
|
+
document.addEventListener('pointercancel', onCancel);
|
|
852
869
|
document.addEventListener('keydown', onKey, true);
|
|
853
870
|
}
|
|
854
871
|
function reorderOver(container, x, y) {
|
|
@@ -910,7 +927,9 @@
|
|
|
910
927
|
const lu = unitOf(el.style.left) || 'px', tu = unitOf(el.style.top) || 'px';
|
|
911
928
|
const baseL = parseFloat(cs.left) || 0, baseT = parseFloat(cs.top) || 0, sx = e.clientX, sy = e.clientY;
|
|
912
929
|
const ov = showOverlay();
|
|
913
|
-
|
|
930
|
+
// Clamped to the containing block: a freeform card must not leave its canvas.
|
|
931
|
+
const maxL = () => Math.max(0, parent.clientWidth - el.offsetWidth), maxT = () => Math.max(0, parent.clientHeight - el.offsetHeight);
|
|
932
|
+
const move = (ev) => { ev.preventDefault(); el.style.left = toUnit(Math.min(maxL(), Math.max(0, baseL + (ev.clientX - sx))), lu, el, parent, 'w') + lu; el.style.top = toUnit(Math.min(maxT(), Math.max(0, baseT + (ev.clientY - sy))), tu, el, parent, 'h') + tu; };
|
|
914
933
|
const up = () => { document.removeEventListener('pointermove', move); document.removeEventListener('pointerup', up); hideOverlay(ov); commitStyle(area, el); };
|
|
915
934
|
document.addEventListener('pointermove', move); document.addEventListener('pointerup', up);
|
|
916
935
|
});
|
|
@@ -921,8 +940,9 @@
|
|
|
921
940
|
if (!el._moveTimer) el._preStyle = el.getAttribute('style') || ''; // baseline at start of a key burst
|
|
922
941
|
const step = e.shiftKey ? 16 : 4, cs = getComputedStyle(el), parent = el.offsetParent || el.parentElement;
|
|
923
942
|
const lu = unitOf(el.style.left) || 'px', tu = unitOf(el.style.top) || 'px';
|
|
924
|
-
|
|
925
|
-
if (d[
|
|
943
|
+
const maxL = Math.max(0, parent.clientWidth - el.offsetWidth), maxT = Math.max(0, parent.clientHeight - el.offsetHeight);
|
|
944
|
+
if (d[0]) el.style.left = toUnit(Math.min(maxL, Math.max(0, (parseFloat(cs.left) || 0) + d[0] * step)), lu, el, parent, 'w') + lu;
|
|
945
|
+
if (d[1]) el.style.top = toUnit(Math.min(maxT, Math.max(0, (parseFloat(cs.top) || 0) + d[1] * step)), tu, el, parent, 'h') + tu;
|
|
926
946
|
clearTimeout(el._moveTimer); el._moveTimer = setTimeout(() => { el._moveTimer = null; commitStyle(area, el); }, 350);
|
|
927
947
|
});
|
|
928
948
|
}
|
|
@@ -1042,6 +1062,7 @@
|
|
|
1042
1062
|
// own caret placement; a second click still collapses to a caret for fine edits.
|
|
1043
1063
|
function selectAllIn(el) {
|
|
1044
1064
|
requestAnimationFrame(() => {
|
|
1065
|
+
if (dragged || grabbed) return; // reordering, not editing
|
|
1045
1066
|
if (document.activeElement !== el) return;
|
|
1046
1067
|
try { const r = document.createRange(); r.selectNodeContents(el); const s = window.getSelection(); s.removeAllRanges(); s.addRange(r); } catch { }
|
|
1047
1068
|
});
|
|
@@ -1066,14 +1087,19 @@
|
|
|
1066
1087
|
// STATIC: literal leaves only (skip bound nodes; those belong to data/component).
|
|
1067
1088
|
function armText(area) {
|
|
1068
1089
|
armRichText(area);
|
|
1069
|
-
|
|
1070
|
-
|
|
1090
|
+
// A region whose root IS the text — <p x-edit.text="note">…</p> — has no
|
|
1091
|
+
// descendant leaves; the area itself is the editable leaf.
|
|
1092
|
+
const nodes = [...area.querySelectorAll('*')];
|
|
1093
|
+
if (!realChildren(area).length) nodes.unshift(area); // size handles are children too
|
|
1094
|
+
nodes.forEach(el => {
|
|
1095
|
+
if (realChildren(el).length || !el.textContent.trim() || el.closest('template') || el.hasAttribute('data-edit-handle')) return;
|
|
1071
1096
|
if (el.closest('[data-text-edit]')) return; // the rich editor owns this subtree
|
|
1072
1097
|
if (!capOf(el, 'text') || el.hasAttribute('x-text') || el.hasAttribute('x-html')) return;
|
|
1073
1098
|
el.setAttribute('contenteditable', 'true');
|
|
1074
1099
|
if (el._textBound) return; el._textBound = true;
|
|
1075
|
-
el.addEventListener('focus', () => { el._preEdit = el.innerHTML.trim();
|
|
1076
|
-
el.addEventListener('
|
|
1100
|
+
el.addEventListener('focus', () => { el._preEdit = el.innerHTML.trim(); selectAllIn(el); });
|
|
1101
|
+
el.addEventListener('keydown', (e) => { if (e.key === 'Escape') { e.preventDefault(); e.stopPropagation(); el.blur(); } }); // Escape finishes the edit, like the rich editor
|
|
1102
|
+
el.addEventListener('blur', () => commitStaticNode(regionOf(el) || area, el, 'text', el.innerHTML.trim())); // live region: the element may have moved since arming
|
|
1077
1103
|
});
|
|
1078
1104
|
}
|
|
1079
1105
|
|
|
@@ -1321,7 +1347,9 @@
|
|
|
1321
1347
|
if (k) {
|
|
1322
1348
|
// Keep the tally in step so a new element never claims an ordinal
|
|
1323
1349
|
// an existing one already holds.
|
|
1324
|
-
|
|
1350
|
+
// The ordinal is the LAST #digits suffix — authored text may contain '#'.
|
|
1351
|
+
const m = /^([\s\S]*)#(\d+)$/.exec(k);
|
|
1352
|
+
const base = m ? m[1] : k, n = m ? m[2] : undefined;
|
|
1325
1353
|
seen[base] = Math.max(seen[base] || 0, n ? +n : 1);
|
|
1326
1354
|
} else {
|
|
1327
1355
|
const base = el.tagName + ':' + (el.textContent || '').replace(/\s+/g, ' ').trim().slice(0, 24);
|
|
@@ -1332,6 +1360,7 @@
|
|
|
1332
1360
|
el.setAttribute('data-edit-path', pathOf(el, area)); // still what the source writer navigates by
|
|
1333
1361
|
if (!(k in area._baseClass)) area._baseClass[k] = el.getAttribute('class') || '';
|
|
1334
1362
|
if (!(k in area._baseStyle)) area._baseStyle[k] = el.getAttribute('style') || '';
|
|
1363
|
+
if (!(k in area._baseText) && !el.children.length) area._baseText[k] = el.innerHTML.trim();
|
|
1335
1364
|
};
|
|
1336
1365
|
markEl(area);
|
|
1337
1366
|
// Stop at a rich editor: its internals are its own, and marking them would
|
|
@@ -1360,6 +1389,15 @@
|
|
|
1360
1389
|
[area, ...area.querySelectorAll('[data-edit-area]')].forEach(c => { if (capOf(c, 'sort') && !locked(c)) makeSortable(c); });
|
|
1361
1390
|
[area, ...area.querySelectorAll('[data-edit-area]')].forEach(c => { if (ownsCap(c, 'size')) armSize(c); });
|
|
1362
1391
|
if (kind === 'component') armComponent(area); else armText(area);
|
|
1392
|
+
if (!area._dndBound) {
|
|
1393
|
+
area._dndBound = true;
|
|
1394
|
+
// Native HTML5 drag (a text selection, an image, a link) must never
|
|
1395
|
+
// start inside a region: it drops SELECTED TEXT into neighbouring
|
|
1396
|
+
// editable leaves, silently merging elements. All region dragging is
|
|
1397
|
+
// pointer-based.
|
|
1398
|
+
area.addEventListener('dragstart', (e) => { if (isActive(area)) e.preventDefault(); });
|
|
1399
|
+
area.addEventListener('drop', (e) => { if (isActive(area)) e.preventDefault(); }); // nothing drops INTO a region either (an OS image drop would merge into a leaf)
|
|
1400
|
+
}
|
|
1363
1401
|
if (!area._ctxBound) {
|
|
1364
1402
|
area._ctxBound = true;
|
|
1365
1403
|
// One binding for the whole area: report the block, and fall back to the
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"manifest.appwrite.auth.js": "sha384-X30RFvbwT9qEIaMsMUumnW54ZzcnY5aY9QQeXPG8/0sI6VAbAw3+FZ6iuZb6b4cu",
|
|
3
|
-
"manifest.appwrite.data.js": "sha384-
|
|
3
|
+
"manifest.appwrite.data.js": "sha384-oPSItIjZCJghmbtcE+6wx6pU3RL925Z4itl/YWGxSpreVY8E5gjF9jI6NXNDCVm6",
|
|
4
4
|
"manifest.appwrite.presences.js": "sha384-JyVDWFT69b7Svq8J8p9tNWe2v9PZNQBY5UyhtOl1vnuHFAX0CRQ3MA9SiKq5wzNh",
|
|
5
5
|
"manifest.bindings.js": "sha384-lPtMf8Ykaic9gNVZKq8Scia1881ZeMAyxer3WCIkUNwwrgzGBWto1OXADUwjvORF",
|
|
6
6
|
"manifest.charts.js": "sha384-gEAv/98+29RbhvNkiNWTNyBPbLTFr2Ld15wLu29FLCL57j1cwGChRrL9Pfk7dBtT",
|
|
@@ -11,12 +11,12 @@
|
|
|
11
11
|
"manifest.combobox.js": "sha384-MILTkrgsTzsYUjGo4ZuADqV85Xs4GmfB4xexfe9vbJMIoQTZ42W4zs+DTaVSPEsx",
|
|
12
12
|
"manifest.components.js": "sha384-4bc3HgPvF9DHMiOG08w+nWXI7Hho01RuAPAUL9xyLxOiS2igeZpulCVcvr5aT9v6",
|
|
13
13
|
"manifest.computed.js": "sha384-GYML/e81gQfN+iDbqhPUgny8bINCyL+SrbTofoGk4hlCnLLjv51mCv42JMqq+ku5",
|
|
14
|
-
"manifest.data.js": "sha384-
|
|
14
|
+
"manifest.data.js": "sha384-4ulI/WUv1NxDgdf7WzzffU2N0HcB0lQpyOr7PNFrhdkvVmOssh6j8RjwO3emZt7U",
|
|
15
15
|
"manifest.datepicker.js": "sha384-XnbeURni2X91ozVzatQcSjYYIS4MoW12dBRJnYAGBr0nCQ624heH5s+xtQSpLZX6",
|
|
16
16
|
"manifest.defer.js": "sha384-WcCf8YQ1bKIXCLSZ24a0bJwepbYntYc8TzQM50hklo5VMIH8AtzDnwl6WU4prRv1",
|
|
17
17
|
"manifest.device.js": "sha384-g+txmrEszYtuOvRelTp/c1wSNR2AtllVI9rx5NPPD7DzhVUnMNughonZykv2xlBW",
|
|
18
18
|
"manifest.dropdowns.js": "sha384-0ilj9AvRph0LacIBJSqwpu1fvPRn5gcWbhIG1SOpaoM/Q8idMbKBLupwM+5O0UaX",
|
|
19
|
-
"manifest.edit.js": "sha384-
|
|
19
|
+
"manifest.edit.js": "sha384-63ym+1EWz+JKvwwH3v2XlR4q5alha/ShMiIJsbDbDS7eVymu5zgghsh8JRVeax+V",
|
|
20
20
|
"manifest.export.js": "sha384-Rx6XvqBZc4apzP/FwcDr6yIln4/zNa08Q4ZqBZcEqUz1aNZQfl4h0+Ae7mqCQyuG",
|
|
21
21
|
"manifest.icons.js": "sha384-kKOINpX70YzNO3FvkV6umrmO3f0KIu0DNbwB+ej55a7y/lhU7v/myQtXL0bwCTbt",
|
|
22
22
|
"manifest.localization.js": "sha384-EImsIw8Z3l13V5eH9ZJF/yfsOfIvR1oPRNSArANkPji7V8O3IRf6zMwsNiAGj3Hk",
|
|
@@ -28,10 +28,10 @@
|
|
|
28
28
|
"manifest.slides.js": "sha384-3uRTkyK9XPLmnxI2+igZlpi4EyPlU/7IHj5j3BZJJ2KN455vXyk99fiXV3feO/XY",
|
|
29
29
|
"manifest.status.js": "sha384-MBO345ab7H6Uq1X2dZLt4Ge1Gcra+Z2qYApvlAeIV8CJhDDJrz2FPXpp+Kcp8zcM",
|
|
30
30
|
"manifest.svg.js": "sha384-qr/8H9+2cIhSXNbRWRtIdrDlTOxKlNJqwEefHvCYoiBNW/kKhPTFa109Orv/EBnb",
|
|
31
|
-
"manifest.sw.js": "sha384-
|
|
31
|
+
"manifest.sw.js": "sha384-oQvga1NtT0DZ6GU0aoy/zA1OAZIZc03cCpiEMOSA3z5S2lwR+QxcmJjpQqgxDKX/",
|
|
32
32
|
"manifest.tabs.js": "sha384-OIIcyaQnGS0eNYk4zgAFEDboNYect7THvUd9D0V6nqCeDOxQcoiqLrBEmmWVTTet",
|
|
33
33
|
"manifest.tailwind.js": "sha384-r/Jvqb+Piz21jUts10BbVAJoKkShOdJRVFpywyi4HPIi/OfOJq5CtPyWAPbdkMgt",
|
|
34
|
-
"manifest.text.edit.js": "sha384-
|
|
34
|
+
"manifest.text.edit.js": "sha384-RzOvLQE8GbmI6gntAwiotDewYT/08OHcd7sJ5hLon8e8u1Xbq1D7VfEGuaRVd2CE",
|
|
35
35
|
"manifest.toasts.js": "sha384-/OV32LLMJl0Iw8NoNQJ+5lgntLxjIJq/YNj5JAKS/+nUhVx4MMB9h1FOqP7zuj2M",
|
|
36
36
|
"manifest.tooltips.js": "sha384-uXVVQADl6pY2Pj9i/UaSclZN7dzbKhLkmevv7Ad0+wXR8LgqodALNFrc5jZJjlwm",
|
|
37
37
|
"manifest.url.parameters.js": "sha384-J2aSVBOPsoqsbafHndrWLHEDpKb4AKJAoQnVmKA4gZh5P/8HHjFWHO/lkusmG84l",
|