mnfst 0.5.235 → 0.5.237

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.
@@ -2,7 +2,7 @@
2
2
 
3
3
  (function () {
4
4
 
5
- const MANIFEST_BUILD_VERSION = '0.5.235';
5
+ const MANIFEST_BUILD_VERSION = '0.5.237';
6
6
 
7
7
  /* Manifest Data Sources - Appwrite Integration */
8
8
 
@@ -0,0 +1,152 @@
1
+ /* Manifest Chat */
2
+
3
+ @layer base, components, utilities;
4
+
5
+ @layer utilities {
6
+
7
+ :where(.chat-wrapper) {
8
+ display: flex;
9
+ flex-direction: column;
10
+ width: 100%;
11
+ height: 100%;
12
+ overflow: hidden;
13
+
14
+ /* Chat log scroll container */
15
+ :where([role="log"]) {
16
+ display: flex;
17
+ flex-direction: column-reverse;
18
+ flex: 1;
19
+ font-size: 0.875rem;
20
+ overflow-y: auto;
21
+
22
+ /* Message column */
23
+ > :where(div) {
24
+ display: flex;
25
+ flex-direction: column;
26
+ justify-content: flex-end;
27
+ gap: 2rem;
28
+ min-height: 100%;
29
+ width: calc(var(--spacing-content-width, 74rem) - 4rem);
30
+ max-width: 100%;
31
+ margin-inline-start: auto;
32
+ margin-inline-end: auto;
33
+ padding-inline-start: 1rem;
34
+ padding-inline-end: 1rem;
35
+ padding-block-end: 3rem
36
+ }
37
+
38
+ /* Messages */
39
+ :where(div div) {
40
+
41
+ /* Markdown spacing */
42
+ :where(p) {
43
+ margin-block: 0.5rem
44
+ }
45
+
46
+ :where(p:first-child) {
47
+ margin-block-start: 0
48
+ }
49
+
50
+ :where(p:last-child) {
51
+ margin-block-end: 0
52
+ }
53
+ }
54
+
55
+ /* Own messages */
56
+ :where(.own) {
57
+ width: 95%;
58
+ max-width: calc(var(--spacing-content-width, 74rem) - 10rem);
59
+ margin-inline-start: auto;
60
+ padding-block: 0.5rem;
61
+ padding-inline: 0.75rem;
62
+ background: color-mix(in oklch, var(--color-surface-2) 50%, transparent);
63
+ border-radius: var(--radius)
64
+ }
65
+
66
+ /* Attachments */
67
+ :where(img, video) {
68
+ display: block;
69
+ max-width: 20rem;
70
+ max-height: 15rem;
71
+ margin-block: 0.5rem;
72
+ border-radius: var(--radius)
73
+ }
74
+
75
+ :where(audio) {
76
+ display: block;
77
+ width: 18rem;
78
+ max-width: 100%;
79
+ margin-block: 0.5rem
80
+ }
81
+
82
+ :where(a[download]) {
83
+ display: inline-flex;
84
+ align-items: center;
85
+ gap: 0.4rem;
86
+ margin-block: 0.5rem;
87
+ padding-block: 0.35rem;
88
+ padding-inline: 0.6rem;
89
+ background: var(--color-surface-2);
90
+ border-radius: var(--radius)
91
+ }
92
+ }
93
+
94
+ /* Chat input */
95
+ :where(form:not(.unstyle)) {
96
+ display: flex;
97
+ flex-flow: row wrap;
98
+ align-items: end;
99
+ gap: 0;
100
+ width: var(--spacing-content-width, 74rem);
101
+ max-width: 100%;
102
+ margin-inline-start: auto;
103
+ margin-inline-end: auto;
104
+
105
+ :where(textarea, .textarea):not(.unstyle) {
106
+ flex: 1;
107
+ width: auto;
108
+ min-height: var(--spacing-field-height, 2.5rem);
109
+ field-sizing: content;
110
+ resize: none
111
+ }
112
+ }
113
+
114
+ /* Pending attachments */
115
+ :where(output:not(.unstyle)) {
116
+ display: flex;
117
+ flex-flow: row wrap;
118
+ gap: 0.325rem;
119
+ width: 100%;
120
+ max-width: 100%;
121
+ height: fit-content;
122
+ padding: calc(var(--spacing) * 2);
123
+
124
+ > :where(:not(template)) {
125
+ position: relative;
126
+ flex: 0 0 auto
127
+ }
128
+
129
+ /* Images */
130
+ :where(img) {
131
+ display: block;
132
+ width: var(--spacing-field-height, 2.5rem);
133
+ height: var(--spacing-field-height, 2.5rem);
134
+ object-fit: cover;
135
+ border-radius: calc(var(--radius, 0.5rem) * 0.75)
136
+ }
137
+
138
+ /* File chips */
139
+ > :where(:not(template))> :where(span) {
140
+ display: flex;
141
+ align-items: center;
142
+ gap: 0.3rem;
143
+ height: fit-content;
144
+ padding-inline: 0.5rem;
145
+ font-size: 0.75rem;
146
+ white-space: nowrap;
147
+ background: var(--color-line);
148
+ border-radius: calc(var(--radius, 0.5rem) * 0.75)
149
+ }
150
+ }
151
+ }
152
+ }
package/lib/manifest.css CHANGED
@@ -2945,32 +2945,15 @@
2945
2945
  outline-offset: 3px;
2946
2946
  }
2947
2947
 
2948
- /* Grip: drags a text piece; sits at its inline-start edge */
2949
- :where([data-edit-grip]) {
2950
- position: fixed;
2951
- z-index: 1001;
2952
- width: var(--edit-grip-size, 0.75rem);
2953
- height: calc(var(--edit-grip-size, 0.75rem) * 1.5);
2954
- translate: calc(-100% - var(--edit-grip-offset, 0.25rem)) 0;
2955
- border-radius: 3px;
2956
- color: var(--edit-accent, var(--color-brand-content));
2957
- background: radial-gradient(circle, currentColor 1px, transparent 1.5px) center / 6px 6px;
2958
- opacity: 0.6;
2948
+ /* A piece with its own grab handle drags only from it */
2949
+ :where([data-edit-piece][data-edit-grabbable]) {
2950
+ cursor: auto;
2951
+ }
2952
+
2953
+ :where([x-edit\.grab]) {
2959
2954
  cursor: grab;
2960
2955
  touch-action: none;
2961
2956
  user-select: none;
2962
-
2963
- &:hover {
2964
- opacity: 1;
2965
- }
2966
- }
2967
-
2968
- :where([data-edit-grip]:dir(rtl)) {
2969
- translate: var(--edit-grip-offset, 0.25rem) 0;
2970
- }
2971
-
2972
- :where([data-edit-grip][hidden]) {
2973
- display: none;
2974
2957
  }
2975
2958
 
2976
2959
  /* Lifted, following the pointer */
@@ -3084,6 +3067,26 @@
3084
3067
  }
3085
3068
  }
3086
3069
 
3070
+ /* While resizing, the edge stays drawn; at a snap point it thickens */
3071
+ :where([data-edit-handle][data-edit-resizing]):where(:not([data-edit-area="bare"] *))::before {
3072
+ opacity: 1;
3073
+ background-color: var(--edit-accent, var(--color-brand-content));
3074
+ }
3075
+
3076
+ :where([data-edit-handle="top"][data-edit-snapped], [data-edit-handle="bottom"][data-edit-snapped]):where(:not([data-edit-area="bare"] *))::before {
3077
+ height: 3px;
3078
+ }
3079
+
3080
+ :where([data-edit-handle="left"][data-edit-snapped], [data-edit-handle="right"][data-edit-snapped], [data-edit-handle="start"][data-edit-snapped], [data-edit-handle="end"][data-edit-snapped]):where(:not([data-edit-area="bare"] *))::before {
3081
+ width: 3px;
3082
+ }
3083
+
3084
+ :where([data-edit-handle*="-"][data-edit-snapped]):where(:not([data-edit-area="bare"] *))::before {
3085
+ width: 0.5rem;
3086
+ height: 0.5rem;
3087
+ border-radius: 50%;
3088
+ }
3089
+
3087
3090
  :where([data-edit-handle]):focus-visible:where(:not([data-edit-area="bare"], [data-edit-area="bare"] *)) {
3088
3091
  outline: 2px solid var(--edit-accent, var(--color-brand-content));
3089
3092
  }
@@ -4033,13 +4036,27 @@
4033
4036
  & table {
4034
4037
  width: 100%;
4035
4038
  table-layout: fixed;
4039
+ border-collapse: separate;
4040
+ border-spacing: 0;
4041
+ border: 1px solid var(--color-line);
4036
4042
 
4043
+ /* Inner dividers only: the table's own border draws the rounded edge */
4037
4044
  & :where(td, th) {
4038
4045
  padding: 0.35em 0.5em;
4039
- border: 1px solid var(--color-line);
4046
+ border: 0 none;
4047
+ border-inline-end: 1px solid var(--color-line);
4048
+ border-block-end: 1px solid var(--color-line);
4040
4049
  vertical-align: top;
4041
4050
  }
4042
4051
 
4052
+ & :where(tr > :last-child) {
4053
+ border-inline-end: 0 none;
4054
+ }
4055
+
4056
+ & :where(tbody:last-child > tr:last-child > *, tfoot > tr:last-child > *) {
4057
+ border-block-end: 0 none;
4058
+ }
4059
+
4043
4060
  & th {
4044
4061
  font-weight: 600;
4045
4062
  text-align: start;
@@ -4365,8 +4382,8 @@
4365
4382
  text-align: left;
4366
4383
  text-align: start;
4367
4384
 
4368
- /* Make elements within cell inline */
4369
- &>*:not(template) {
4385
+ /* Make elements within cell inline; text-level elements stay in the text flow */
4386
+ &>*:not(template, br, :is(a, abbr, b, cite, code, data, del, dfn, em, i, ins, kbd, mark, q, s, samp, small, strong, sub, sup, time, u, var):not([role=button], [x-icon], .badge)) {
4370
4387
  display: inline-flex;
4371
4388
  vertical-align: middle;
4372
4389
 
package/lib/manifest.d.ts CHANGED
@@ -531,6 +531,8 @@ export interface ManifestRichTextAttachOptions {
531
531
  mode?: 'block' | 'inline';
532
532
  /** Inline html only: `<br>` line breaks are kept, Shift+Enter inserts one, pasted blocks become lines. */
533
533
  breaks?: boolean;
534
+ /** 'break': Enter inserts a line break too, instead of firing `rich-text:enter`. Needs `breaks` and the 'html' format. */
535
+ enter?: 'break';
534
536
  /** false: `.all` controls and `$rich.all` are unavailable (no area-wide --rich-text-* styles). Default true. */
535
537
  all?: boolean;
536
538
  /** Storage format: 'markdown' (default), 'html' or 'plain'. */
@@ -2,7 +2,7 @@
2
2
 
3
3
  (function () {
4
4
 
5
- const MANIFEST_BUILD_VERSION = '0.5.235';
5
+ const MANIFEST_BUILD_VERSION = '0.5.237';
6
6
 
7
7
  /* Manifest Data Sources - Configuration */
8
8
 
@@ -25,32 +25,15 @@
25
25
  outline-offset: 3px;
26
26
  }
27
27
 
28
- /* Grip: drags a text piece; sits at its inline-start edge */
29
- :where([data-edit-grip]) {
30
- position: fixed;
31
- z-index: 1001;
32
- width: var(--edit-grip-size, 0.75rem);
33
- height: calc(var(--edit-grip-size, 0.75rem) * 1.5);
34
- translate: calc(-100% - var(--edit-grip-offset, 0.25rem)) 0;
35
- border-radius: 3px;
36
- color: var(--edit-accent, var(--color-brand-content));
37
- background: radial-gradient(circle, currentColor 1px, transparent 1.5px) center / 6px 6px;
38
- opacity: 0.6;
28
+ /* A piece with its own grab handle drags only from it */
29
+ :where([data-edit-piece][data-edit-grabbable]) {
30
+ cursor: auto;
31
+ }
32
+
33
+ :where([x-edit\.grab]) {
39
34
  cursor: grab;
40
35
  touch-action: none;
41
36
  user-select: none;
42
-
43
- &:hover {
44
- opacity: 1;
45
- }
46
- }
47
-
48
- :where([data-edit-grip]:dir(rtl)) {
49
- translate: var(--edit-grip-offset, 0.25rem) 0;
50
- }
51
-
52
- :where([data-edit-grip][hidden]) {
53
- display: none;
54
37
  }
55
38
 
56
39
  /* Lifted, following the pointer */
@@ -164,6 +147,26 @@
164
147
  }
165
148
  }
166
149
 
150
+ /* While resizing, the edge stays drawn; at a snap point it thickens */
151
+ :where([data-edit-handle][data-edit-resizing]):where(:not([data-edit-area="bare"] *))::before {
152
+ opacity: 1;
153
+ background-color: var(--edit-accent, var(--color-brand-content));
154
+ }
155
+
156
+ :where([data-edit-handle="top"][data-edit-snapped], [data-edit-handle="bottom"][data-edit-snapped]):where(:not([data-edit-area="bare"] *))::before {
157
+ height: 3px;
158
+ }
159
+
160
+ :where([data-edit-handle="left"][data-edit-snapped], [data-edit-handle="right"][data-edit-snapped], [data-edit-handle="start"][data-edit-snapped], [data-edit-handle="end"][data-edit-snapped]):where(:not([data-edit-area="bare"] *))::before {
161
+ width: 3px;
162
+ }
163
+
164
+ :where([data-edit-handle*="-"][data-edit-snapped]):where(:not([data-edit-area="bare"] *))::before {
165
+ width: 0.5rem;
166
+ height: 0.5rem;
167
+ border-radius: 50%;
168
+ }
169
+
167
170
  :where([data-edit-handle]):focus-visible:where(:not([data-edit-area="bare"], [data-edit-area="bare"] *)) {
168
171
  outline: 2px solid var(--edit-accent, var(--color-brand-content));
169
172
  }
@@ -263,6 +263,7 @@
263
263
  function registerEdit(el, modifiers, expression) {
264
264
  if (modifiers.includes('cssvar')) return registerCssVar(el, expression, modifiers);
265
265
  modifiers.forEach(m => { if (RETIRED[m] && !warnedRetired.has(m)) { warnedRetired.add(m); console.warn(`[edit] x-edit.${m} was removed — use ${RETIRED[m]}`); } });
266
+ if (modifiers.includes('grab')) return; // a piece's own drag handle, not a region
266
267
  if (modifiers.includes('lock')) {
267
268
  const actions = modifiers.filter(m => LOCK_ACTIONS.includes(m));
268
269
  el._editLock = new Set(actions.length ? actions : ['*']);
@@ -330,7 +331,7 @@
330
331
  };
331
332
  function dataSourceExpr(area) { const t = listTpl(area); const m = t && t.getAttribute('x-for').match(/\bin\s+(.+)$/); return m ? m[1].trim() : null; }
332
333
  const dataSourceName = (area) => { const e = dataSourceExpr(area); const m = e && e.match(/\$x\.(\w+)/); return m ? m[1] : (e || 'source'); };
333
- const pluginNode = (x) => x.hasAttribute('data-edit-handle') || x.hasAttribute('data-edit-ghost') || x.hasAttribute('data-edit-grip');
334
+ const pluginNode = (x) => x.hasAttribute('data-edit-handle') || x.hasAttribute('data-edit-ghost');
334
335
  // A component's roots past its first follow that first one (its <x-name> host) everywhere.
335
336
  const groupOf = (el) => el && el._manifestHost && el._manifestHost.roots && el._manifestHost.roots[0] === el ? el._manifestHost.roots.slice() : [el];
336
337
  const extraRoot = (x) => !!x._manifestHost && !!x._manifestHost.roots && x._manifestHost.roots[0] !== x;
@@ -359,7 +360,7 @@
359
360
  function plainFormatted(el) {
360
361
  if (!window.ManifestRichText) return false;
361
362
  const c = el.cloneNode(true);
362
- c.querySelectorAll('[data-edit-handle], [data-edit-ghost], [data-edit-grip]').forEach(n => n.remove());
363
+ c.querySelectorAll('[data-edit-handle], [data-edit-ghost]').forEach(n => n.remove());
363
364
  commentsIn(c).forEach(n => n.remove());
364
365
  if (!c.children.length) return true;
365
366
  c.querySelectorAll('*').forEach(n => [...n.attributes].forEach(a => { if (a.name.startsWith('data-edit-')) n.removeAttribute(a.name); }));
@@ -1103,7 +1104,7 @@
1103
1104
  const src = [el, ...el.querySelectorAll('*')], dst = [c, ...c.querySelectorAll('*')];
1104
1105
  dst.forEach((n, i) => { if (addedTab.has(src[i])) n.removeAttribute('tabindex'); });
1105
1106
  unrich(el, c);
1106
- c.querySelectorAll('[data-edit-handle], [data-edit-live], [data-edit-ghost], [data-edit-grip]').forEach(n => n.remove());
1107
+ c.querySelectorAll('[data-edit-handle], [data-edit-live], [data-edit-ghost]').forEach(n => n.remove());
1107
1108
  [c, ...c.querySelectorAll('*')].forEach(n => {
1108
1109
  [...n.attributes].forEach(a => { if (a.name.startsWith('data-edit-') && !(keepKeys && a.name === 'data-edit-key')) n.removeAttribute(a.name); });
1109
1110
  n.removeAttribute('contenteditable');
@@ -1357,16 +1358,17 @@
1357
1358
  if (kind === 'component') return [];
1358
1359
  if (kind === 'data') { const tpl = listTpl(area); return tpl ? sortableChildren(tpl.parentElement) : []; }
1359
1360
  const out = [];
1360
- const walk = (c) => sortableChildren(c).forEach(ch => { if (ch.closest('[data-edit-ghost]')) return; if (ch._manifestComponent) { if (isHostEl(ch)) out.push(ch); return; } out.push(ch); if (isContainer(ch) && !isRendered(ch)) walk(ch); });
1361
+ const walk = (c) => sortableChildren(c).forEach(ch => { if (ch.closest('[data-edit-ghost], [x-edit\\.grab]')) return; if (ch._manifestComponent) { if (isHostEl(ch)) out.push(ch); return; } out.push(ch); if (isContainer(ch) && !isRendered(ch)) walk(ch); });
1361
1362
  if (isContainer(area)) walk(area);
1362
1363
  return out;
1363
1364
  }
1364
- function unmarkPiece(el) { el.removeAttribute('data-edit-piece'); if (addedTab.has(el)) { el.removeAttribute('tabindex'); addedTab.delete(el); } }
1365
+ function unmarkPiece(el) { el.removeAttribute('data-edit-piece'); el.removeAttribute('data-edit-grabbable'); if (addedTab.has(el)) { el.removeAttribute('tabindex'); addedTab.delete(el); } }
1365
1366
  function markPieces(area) {
1366
1367
  const want = new Set(collectPieces(area).filter(movable));
1367
1368
  area.querySelectorAll('[data-edit-piece]').forEach(el => { if (!want.has(el) && regionOf(el.parentElement) === area) unmarkPiece(el); });
1368
1369
  want.forEach(el => {
1369
1370
  el.setAttribute('data-edit-piece', '');
1371
+ el.toggleAttribute('data-edit-grabbable', !!ownGrab(el));
1370
1372
  if (!el.hasAttribute('tabindex')) { el.tabIndex = 0; addedTab.add(el); }
1371
1373
  });
1372
1374
  }
@@ -1473,86 +1475,20 @@
1473
1475
  const area = this;
1474
1476
  if (!isActive(area) || (e.pointerType === 'mouse' && e.button !== 0) || e._editDrag) return;
1475
1477
  const t = e.target;
1476
- if (!t || t.nodeType !== 1 || t.isContentEditable || t.hasAttribute('data-edit-handle')) return;
1478
+ if (!t || t.nodeType !== 1 || t.hasAttribute('data-edit-handle')) return;
1477
1479
  if (t._pieceMode) { exitPieceMode(t); return; }
1478
1480
  let item = null;
1479
1481
  for (let n = t; n && n !== area; n = n.parentElement) if (n.hasAttribute('data-edit-piece') && movable(n)) { item = n; break; }
1480
1482
  if (!item) return;
1483
+ const grab = ownGrab(item);
1484
+ if (grab) { if (!grab.contains(t)) return; e.preventDefault(); }
1485
+ else if (typing(t)) return; // in the text being typed: the drag selects
1481
1486
  e._editDrag = true;
1482
1487
  startDrag(e, item, area);
1483
1488
  }
1484
-
1485
- /* ---- Grip: text pieces, whose surface is for typing, drag from a handle at their inline-start edge ---- */
1486
- let grip = null, gripFor = null, gripHovered = null, gripFocused = null, gripListening = false;
1487
- // Text pieces, and rows that are themselves a data field (their surface is for typing too)
1488
- const needsGrip = (p) => !!p && movable(p) && ((isTextEl(p) && capOf(p, 'text')) || p._editField != null);
1489
- function gripPiece(node, area) {
1490
- for (let n = node; n && n.nodeType === 1 && n !== area; n = n.parentElement) if (n.hasAttribute('data-edit-piece')) return needsGrip(n) ? n : null;
1491
- return null;
1492
- }
1493
- function ensureGrip() {
1494
- if (grip && grip.isConnected) return grip;
1495
- grip = document.createElement('span');
1496
- grip.setAttribute('data-edit-grip', '');
1497
- grip.setAttribute('aria-hidden', 'true');
1498
- grip.hidden = true;
1499
- grip.addEventListener('pointerdown', onGripDown);
1500
- grip.addEventListener('pointerleave', (e) => gripLeave(e.relatedTarget));
1501
- document.body.appendChild(grip);
1502
- if (!gripListening) {
1503
- gripListening = true;
1504
- window.addEventListener('scroll', placeGrip, { capture: true, passive: true });
1505
- window.addEventListener('resize', placeGrip, { passive: true });
1506
- }
1507
- return grip;
1508
- }
1509
- function placeGrip() {
1510
- if (!gripFor || !grip) return;
1511
- if (!gripFor.isConnected) return hideGrip();
1512
- const r = gripFor.getBoundingClientRect(), rtl = getComputedStyle(gripFor).direction === 'rtl';
1513
- grip.dir = rtl ? 'rtl' : 'ltr';
1514
- grip.style.top = r.top + 'px';
1515
- grip.style.left = (rtl ? r.right : r.left) + 'px';
1516
- }
1517
- function showGrip(piece) {
1518
- const area = regionOf(piece);
1519
- if (dragged || !area || area._edit.bare || piece.closest('[data-edit-area="bare"]')) return hideGrip();
1520
- const g = ensureGrip();
1521
- // The grip lives on <body>: it takes the piece's own accent and sizes
1522
- ['--edit-accent', '--edit-grip-size', '--edit-grip-offset'].forEach(v => { const x = rawVar(piece, v); if (x) g.style.setProperty(v, x); else g.style.removeProperty(v); });
1523
- gripFor = piece; placeGrip(); g.hidden = false;
1524
- }
1525
- function hideGrip() { gripFor = null; if (grip) grip.hidden = true; }
1526
- function gripOver(e, area) {
1527
- if (dragged) return;
1528
- const p = gripPiece(e.target, area);
1529
- gripHovered = p;
1530
- if (p) showGrip(p); else if (gripFocused) showGrip(gripFocused); else hideGrip();
1531
- }
1532
- function gripLeave(to) {
1533
- if (to && grip && (to === grip || (gripFor && gripFor.contains(to)))) return;
1534
- gripHovered = null;
1535
- if (gripFocused && gripFocused.isConnected) showGrip(gripFocused); else hideGrip();
1536
- }
1537
- function gripFocus(e, area) {
1538
- const p = gripPiece(e.target, area);
1539
- gripFocused = p;
1540
- if (p && !(gripHovered && gripHovered.isConnected)) showGrip(p);
1541
- }
1542
- function gripBlur(to) {
1543
- if (to && gripFocused && gripFocused.contains(to)) return;
1544
- gripFocused = null;
1545
- if (!(gripHovered && gripHovered.isConnected)) hideGrip();
1546
- }
1547
- function onGripDown(e) {
1548
- const piece = gripFor;
1549
- if (!piece || (e.pointerType === 'mouse' && e.button !== 0)) return;
1550
- const area = regionOf(piece);
1551
- if (!isActive(area) || !movable(piece)) return;
1552
- e.preventDefault(); e.stopPropagation();
1553
- hideGrip();
1554
- startDrag(e, piece, area);
1555
- }
1489
+ // An x-edit.grab inside a piece, not a nested piece's: the only place that piece drags from.
1490
+ const ownGrab = (item) => [...item.querySelectorAll('[x-edit\\.grab]')].find(g => g.closest('[data-edit-piece]') === item) || null;
1491
+ const typing = (t) => { const a = document.activeElement; return !!a && a !== document.body && a.isContentEditable && (a === t || a.contains(t)); };
1556
1492
 
1557
1493
  function startDrag(e, item, area) {
1558
1494
  const home = item.parentElement, homeNext = groupOf(item).pop().nextElementSibling, rest = groupOf(item).slice(1);
@@ -1569,6 +1505,8 @@
1569
1505
  let active = false, frame = 0, px = sx, py = sy, last = null;
1570
1506
 
1571
1507
  const lift = (ev) => {
1508
+ const a = document.activeElement; if (a && item.contains(a) && a.isContentEditable) a.blur(); // a text piece moves, it isn't typed in
1509
+ try { window.getSelection().removeAllRanges(); } catch { }
1572
1510
  if (s.crossOK) areas().forEach(a => { if (a !== area && dropValid(a)) a.setAttribute('data-edit-drop-target', ''); });
1573
1511
  const ghost = s.ghost = item.cloneNode(true);
1574
1512
  unrich(item, ghost);
@@ -1601,7 +1539,7 @@
1601
1539
  px = ev.clientX; py = ev.clientY;
1602
1540
  if (!active) {
1603
1541
  if (Math.hypot(px - sx, py - sy) < 5) return;
1604
- active = true; hideGrip(); lift(ev); paint();
1542
+ active = true; lift(ev); paint();
1605
1543
  }
1606
1544
  ev.preventDefault();
1607
1545
  const drop = dropTarget(px, py, s);
@@ -1884,9 +1822,9 @@
1884
1822
  const showable = (v) => { try { return JSON.stringify(v) !== undefined ? JSON.parse(JSON.stringify(v)) : true; } catch { return true; } };
1885
1823
  function setShow(el, expr, value) { window.Alpine.evaluate(el, `${expr} = ${JSON.stringify(value)}`); }
1886
1824
  // Closing is logged with the size change, so one undo reopens it.
1887
- function closeVia(area, el, expr, join) {
1888
- let before;
1889
- try { before = showable(window.Alpine.evaluate(el, expr)); setShow(el, expr, false); } catch (err) { console.warn('[edit] could not close via x-show:', err); return false; }
1825
+ function closeVia(area, el, expr, join, prior) {
1826
+ let before = prior;
1827
+ try { if (before === undefined) before = showable(window.Alpine.evaluate(el, expr)); setShow(el, expr, false); } catch (err) { console.warn('[edit] could not close via x-show:', err); return false; }
1890
1828
  const id = identity(el, area);
1891
1829
  if (id != null) pushDelta(Object.assign({ kind: 'show', region: key(area), key: id, expr, before, value: false }, join ? { join: true } : {}));
1892
1830
  return true;
@@ -1905,34 +1843,51 @@
1905
1843
  const snapW = snapStops(el, 'x', 'w'), snapH = snapStops(el, 'y', 'h');
1906
1844
  const closeX = lengthVar(el, '--edit-size-close', 'x', 'w'), closeY = lengthVar(el, '--edit-size-close', 'y', 'h');
1907
1845
  const showExpr = closeX != null || closeY != null ? closable(el) : null;
1908
- try { e.target.setPointerCapture && e.target.setPointerCapture(e.pointerId); } catch { }
1846
+ let showBefore;
1847
+ if (showExpr) { try { showBefore = showable(window.Alpine.evaluate(el, showExpr)); } catch { } }
1848
+ const handle = e.target;
1849
+ try { handle.setPointerCapture && handle.setPointerCapture(e.pointerId); } catch { }
1850
+ handle.setAttribute('data-edit-resizing', '');
1909
1851
  showOverlay();
1910
- let rawW = baseW, rawH = baseH;
1852
+ let rawW = baseW, rawH = baseH, closed = false;
1853
+ const shut = () => ((right || left) && closeX != null && rawW < closeX) || ((top || bottom) && closeY != null && rawH < closeY);
1854
+ // Closes live once past the close size, and reopens if dragged back out.
1855
+ const setClosed = (v) => { if (v === closed || !showExpr) return; closed = v; try { setShow(el, showExpr, v ? false : showBefore); } catch { } };
1911
1856
  const move = (ev) => {
1912
1857
  const dx = ev.clientX - sx, dy = ev.clientY - sy;
1913
- if (right || left) { rawW = baseW + (right ? dx : -dx); el.style.width = toUnit(snapTo(clamp(rawW, minW, maxW), snapW, reachX), wu, el, el.parentElement, 'w') + wu; }
1914
- if (top || bottom) { rawH = baseH + (bottom ? dy : -dy); el.style.height = toUnit(snapTo(clamp(rawH, minH, maxH), snapH, reachY), hu, el, el.parentElement, 'h') + hu; }
1858
+ if (right || left) rawW = baseW + (right ? dx : -dx);
1859
+ if (top || bottom) rawH = baseH + (bottom ? dy : -dy);
1860
+ setClosed(shut());
1861
+ let snapped = false;
1862
+ if (right || left) { const c = clamp(rawW, minW, maxW), v = snapTo(c, snapW, reachX); snapped = snapped || (snapW.length > 0 && v !== c); el.style.width = toUnit(v, wu, el, el.parentElement, 'w') + wu; }
1863
+ if (top || bottom) { const c = clamp(rawH, minH, maxH), v = snapTo(c, snapH, reachY); snapped = snapped || (snapH.length > 0 && v !== c); el.style.height = toUnit(v, hu, el, el.parentElement, 'h') + hu; }
1864
+ el.toggleAttribute('data-edit-snapped', snapped && !closed);
1865
+ handle.toggleAttribute('data-edit-snapped', snapped && !closed);
1915
1866
  sizeEvent(el, false);
1916
1867
  };
1917
1868
  const end = () => {
1918
1869
  document.removeEventListener('pointermove', move); document.removeEventListener('pointerup', up); document.removeEventListener('pointercancel', cancel);
1919
1870
  hideOverlay();
1871
+ ['data-edit-snapped', 'data-edit-resizing'].forEach(a => handle.removeAttribute(a));
1872
+ el.removeAttribute('data-edit-snapped');
1920
1873
  };
1921
1874
  const restore = () => { if (preStyle == null) el.removeAttribute('style'); else el.setAttribute('style', preStyle); };
1922
- const cancel = () => { end(); restore(); sizeEvent(el, true); };
1875
+ const cancel = () => { end(); setClosed(false); restore(); sizeEvent(el, true); };
1923
1876
  const up = () => {
1924
1877
  end();
1925
1878
  const shutX = (right || left) && closeX != null && rawW < closeX, shutY = (top || bottom) && closeY != null && rawH < closeY;
1926
1879
  if ((shutX || shutY) && showExpr) {
1927
1880
  // Closed at the minimum it can have; with none, at the size it had, so it reopens sensibly.
1928
1881
  const atMin = (shutX ? minW : 0) > 0 || (shutY ? minH : 0) > 0;
1882
+ const hidden = el.style.display; // x-show's own hide, applied while held
1929
1883
  restore();
1930
1884
  if (atMin) {
1931
1885
  if (shutX && minW > 0) el.style.width = toUnit(minW, wu, el, el.parentElement, 'w') + wu;
1932
1886
  if (shutY && minH > 0) el.style.height = toUnit(minH, hu, el, el.parentElement, 'h') + hu;
1933
1887
  }
1934
- const sized = commitStyle(area, el); sizeEvent(el, true);
1935
- closeVia(area, el, showExpr, sized);
1888
+ const sized = commitStyle(area, el); sizeEvent(el, true); // the hide is page state: never logged
1889
+ if (hidden === 'none') el.style.display = 'none';
1890
+ closeVia(area, el, showExpr, sized, showBefore);
1936
1891
  return;
1937
1892
  }
1938
1893
  if (shutX || shutY) { restore(); sizeEvent(el, true); return; } // close configured but not possible here: leave it as it was
@@ -1981,7 +1936,7 @@
1981
1936
  if (el._editRt || el._pieceMode) return; // a focused piece re-attaches when it leaves piece mode
1982
1937
  const RT = rtEngine();
1983
1938
  el._editRtPrior = ['role', 'aria-multiline'].map(a => [a, el.getAttribute(a)]);
1984
- const h = RT && RT.attach(el, { mode: 'inline', breaks: true, all: false, format, onChange: () => { } });
1939
+ const h = RT && RT.attach(el, { mode: 'inline', breaks: true, enter: 'break', all: false, format, onChange: () => { } });
1985
1940
  if (!h) { el.setAttribute('contenteditable', 'true'); return; }
1986
1941
  el._editRt = h;
1987
1942
  el.setAttribute('data-edit-text', '');
@@ -2042,7 +1997,11 @@
2042
1997
  if (el._textBound) return; el._textBound = true;
2043
1998
  el._commitText = commit;
2044
1999
  el.addEventListener('focus', () => { restartRich(el); el._preEdit = leafText(el); selectAllIn(el); });
2045
- el.addEventListener('keydown', (e) => { if (e.key === 'Escape' && el.isContentEditable) { e.preventDefault(); e.stopImmediatePropagation(); leaveText(el); } });
2000
+ el.addEventListener('keydown', (e) => {
2001
+ if (!el.isContentEditable) return;
2002
+ if (e.key === 'Escape') { e.preventDefault(); e.stopImmediatePropagation(); el.blur(); }
2003
+ else if (e.key === 'Enter' && e.altKey && !e.shiftKey && !e.metaKey && !e.ctrlKey) { e.preventDefault(); e.stopImmediatePropagation(); leaveText(el); }
2004
+ });
2046
2005
  el.addEventListener('keydown', richHistoryKey);
2047
2006
  noDrop(el);
2048
2007
  if (input) el.addEventListener('input', input);
@@ -2075,7 +2034,7 @@
2075
2034
  const nodes = [...area.querySelectorAll('*')];
2076
2035
  if (!realChildren(area).length || wholeLeaf(area)) nodes.unshift(area); // size handles are children too
2077
2036
  nodes.forEach(el => {
2078
- if ((realChildren(el).length && !wholeLeaf(el)) || !el.textContent.trim() || el.closest('template') || el.hasAttribute('data-edit-handle')) return;
2037
+ if ((realChildren(el).length && !wholeLeaf(el)) || !el.textContent.trim() || el.closest('template') || el.closest('[data-edit-handle], [x-edit\\.grab]')) return;
2079
2038
  if (el._manifestComponent && el !== area) return; // a component host: its markup is another file's
2080
2039
  if (richOwned(el)) return; // the rich editor owns this subtree
2081
2040
  if (el.closest('[contenteditable="false"]:not([data-edit-piece-mode])')) return; // an authored non-editable island
@@ -2154,7 +2113,7 @@
2154
2113
  editable(el, 'html');
2155
2114
  });
2156
2115
  }
2157
- // Escape leaves text with the piece focused, so Alt+arrows move it.
2116
+ // Alt+Enter leaves text with the piece focused, so Space picks it up and Alt+arrows move it.
2158
2117
  function leaveText(el) {
2159
2118
  el.blur();
2160
2119
  const piece = [el, ...ancestorsIn(el)].find(n => n.hasAttribute && n.hasAttribute('data-edit-piece'));
@@ -2525,10 +2484,6 @@
2525
2484
  area._editBound = true;
2526
2485
  area.addEventListener('pointerdown', onAreaPointerDown);
2527
2486
  area.addEventListener('keydown', (e) => { if (isActive(area)) onPieceKeydown(e, area); });
2528
- area.addEventListener('pointerover', (e) => { if (isActive(area)) gripOver(e, area); });
2529
- area.addEventListener('pointerleave', (e) => gripLeave(e.relatedTarget));
2530
- area.addEventListener('focusin', (e) => { if (isActive(area)) gripFocus(e, area); });
2531
- area.addEventListener('focusout', (e) => gripBlur(e.relatedTarget));
2532
2487
  // Native HTML5 drag would drop selected text into neighbouring leaves.
2533
2488
  area.addEventListener('dragstart', (e) => { if (isActive(area)) e.preventDefault(); });
2534
2489
  area.addEventListener('drop', (e) => { if (isActive(area)) e.preventDefault(); });
@@ -2543,7 +2498,6 @@
2543
2498
  }
2544
2499
  }
2545
2500
  function disarmArea(area) {
2546
- if (gripFor && area.contains(gripFor)) hideGrip();
2547
2501
  [area, ...area.querySelectorAll('[data-edit-area]')].forEach(e => { if (e._editPos) { e.style.removeProperty('position'); e._editPos = false; } });
2548
2502
  area.querySelectorAll('[data-edit-handle]').forEach(h => h.remove());
2549
2503
  uneditIn(area);