mnfst 0.5.231 → 0.5.233

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,142 +2,139 @@
2
2
 
3
3
  @layer base, components, utilities;
4
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. */
5
+ /* Rules are :where()-wrapped and keyed off data-edit-* attributes, so authors
6
+ override without specificity battles. Visuals are on for every area;
7
+ x-edit.bare (data-edit-area="bare") keeps the behaviour and draws none of them. */
9
8
 
10
9
  @layer utilities {
11
10
 
12
- /* ---- Editing UI scope ----
13
- ALL chrome — cursors, movable/text outlines, size-handle hairlines, drop
14
- targets, the floating toolbar — lives under ONE scope: [data-edit-ui], on
15
- the region itself or any ancestor (e.g. <body data-edit-ui> for a whole
16
- editor view). The plugin sets it on `.authoring` regions automatically;
17
- everything else opts in with the attribute. A region outside the scope is
18
- just as editable — it simply carries no visual cues. */
19
-
20
- /* ---- Editable area (armed) ---- */
21
- :where([data-edit-armed]) {
22
- position: relative;
23
- }
24
-
25
- /* No default region boundary or label: a page being edited should look
26
- like the page, not a diagram of its regions. The hooks remain for a real
27
- editor product to draw its own — [data-edit-authoring] marks the region
28
- and data-edit-label carries "key · regime". */
29
-
30
- /* ---- Reorder (pointer + keyboard; touch-action:none lets touch-drag beat scroll) ---- */
31
- :where([data-edit-sortable]) {
11
+ /* ---- Pieces ---- */
12
+ :where([data-edit-piece]) {
32
13
  cursor: grab;
33
14
  touch-action: none;
34
15
  transition: opacity 0.12s ease, outline-color 0.12s ease;
35
16
  }
36
17
 
37
- /* No injected grip: the row itself is the handle (grab cursor), while text
38
- inside stays a text surface (text cursor, below). Rows that are pure text
39
- still reorder by keyboard (Alt/Option + arrows). */
40
-
41
- /* Hover hint in the UI scope; quiet while a drag is in progress. */
42
- :where([data-edit-ui] [data-edit-sortable], [data-edit-ui][data-edit-sortable]):where(:hover):where(:not([data-edit-dragging], [data-edit-ghost], [data-edit-dragging-in] [data-edit-sortable])) {
18
+ :where([data-edit-piece]):where(:hover):where(:not([data-edit-area="bare"], [data-edit-area="bare"] *, [data-edit-dragging], [data-edit-ghost], :has([data-edit-dragging]) *)) {
43
19
  outline: 1px solid color-mix(in oklch, var(--edit-accent, var(--color-brand-content)) 45%, transparent);
44
20
  outline-offset: 3px;
45
21
  }
46
22
 
47
- :where([data-edit-sortable], [data-edit-movable]):focus-visible {
23
+ :where([data-edit-piece]):focus-visible:where(:not([data-edit-area="bare"], [data-edit-area="bare"] *)) {
48
24
  outline: 2px solid var(--edit-accent, var(--color-brand-content));
49
25
  outline-offset: 3px;
50
26
  }
51
27
 
52
- /* Lifted out of the flow and following the pointer. */
53
- :where([data-edit-sortable][data-edit-dragging]) {
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;
39
+ cursor: grab;
40
+ touch-action: none;
41
+ 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
+ }
55
+
56
+ /* Lifted, following the pointer */
57
+ :where([data-edit-dragging=""]) {
54
58
  z-index: 1000;
55
59
  cursor: grabbing;
60
+ }
61
+
62
+ :where([data-edit-dragging=""]):where(:not([data-edit-area="bare"], [data-edit-area="bare"] *)) {
56
63
  box-shadow: 0 8px 24px rgb(0 0 0 / 0.18);
57
64
  }
58
65
 
59
- /* The stand-in holding the slot the drop would land in — a translucent copy of
60
- the element by default, because the clearest preview of where it lands is the
61
- thing itself. Restyle it freely: hide its contents for an empty outlined slot,
62
- give it a background, animate it. */
66
+ /* Grabbed from the keyboard */
67
+ :where([data-edit-dragging="keyboard"]):where(:not([data-edit-area="bare"], [data-edit-area="bare"] *)) {
68
+ outline: 2px dashed var(--edit-accent, var(--color-brand-content));
69
+ outline-offset: 3px;
70
+ }
71
+
72
+ /* The stand-in holding the slot the drop would land in */
63
73
  :where([data-edit-ghost]) {
64
74
  opacity: var(--edit-ghost-opacity, 0.4);
65
75
  pointer-events: none;
66
76
  }
67
77
 
68
- /* Valid cross-region destinations during a drag: a dashed invitation, and
69
- enough height that an EMPTY region can be dropped onto at all. */
70
- :where([data-edit-drop-target]) {
78
+ :where([data-edit-area="bare"] [data-edit-ghost]) {
79
+ opacity: 0;
80
+ }
81
+
82
+ /* The container the drop would land in */
83
+ :where(:has(> [data-edit-ghost])):where(:not([data-edit-area="bare"], [data-edit-area="bare"] *)) {
84
+ outline: 1px dashed color-mix(in oklch, var(--edit-accent, var(--color-brand-content)) 55%, transparent);
85
+ outline-offset: 4px;
86
+ }
87
+
88
+ /* Other areas a drag can land in; an empty one still has a surface */
89
+ :where([data-edit-drop-target]):where(:not([data-edit-area="bare"])) {
90
+ min-height: 2.5rem;
71
91
  outline: 1px dashed color-mix(in oklch, var(--edit-accent, var(--color-brand-content)) 55%, transparent);
72
92
  outline-offset: 4px;
93
+ }
94
+
95
+ /* While dragging: empty containers can be dropped into, nothing else reacts */
96
+ :where([data-edit-area]:not([data-edit-area="bare"]):has([data-edit-dragging=""])) :where(:empty:not(img, input, br, hr, svg, video, canvas, iframe, textarea, [data-edit-ghost], [data-edit-handle], [data-edit-area="bare"], [data-edit-area="bare"] *)) {
73
97
  min-height: 2.5rem;
74
98
  }
75
99
 
76
- /* Nothing else should react while a drag is in progress */
77
- :where([data-edit-dragging-in]) {
100
+ :where([data-edit-area]:has([data-edit-dragging=""])) {
78
101
  cursor: grabbing;
79
102
  user-select: none;
80
103
 
81
- & :where([data-edit-sortable]:not([data-edit-dragging])) {
104
+ & :where([data-edit-piece]:not([data-edit-dragging])) {
82
105
  cursor: grabbing;
83
106
  }
84
107
  }
85
108
 
86
- :where([data-edit-grabbed]) {
87
- outline: 2px dashed var(--edit-accent, var(--color-brand-content));
88
- outline-offset: 3px;
89
- }
90
-
91
- /* ---- Move (positioned children) ---- */
92
- :where([data-edit-movable]) {
93
- cursor: move;
94
- touch-action: none;
95
- }
96
-
97
- :where([data-edit-ui] [data-edit-movable], [data-edit-ui][data-edit-movable]):hover {
98
- outline: 1px solid var(--edit-accent, var(--color-brand-content));
99
- outline-offset: 2px;
100
- }
101
-
102
- /* ---- Inline text ----
103
- One weight everywhere: hover hints and the focused caret both use the same
104
- 1px line, so focusing doesn't make the page jump a weight class. */
105
- :where([data-edit-ui]) :where([data-edit-armed]) [contenteditable="true"],
106
- :where([data-edit-ui])[data-edit-armed] [contenteditable="true"] {
109
+ /* ---- Inline text ---- */
110
+ :where([data-edit-area]:not([data-edit-area="bare"])) :where([contenteditable="true"], [data-edit-text]):where(:not([data-edit-area="bare"] *)),
111
+ :where([data-edit-area]:is([contenteditable="true"], [data-edit-text]):not([data-edit-area="bare"])) {
107
112
  cursor: text;
108
113
  outline: 1px solid transparent;
109
114
  outline-offset: 2px;
110
115
  border-radius: 3px;
111
116
  transition: outline-color 0.12s ease;
112
- }
113
117
 
114
- :where([data-edit-ui]) :where([data-edit-armed]) [contenteditable="true"]:hover,
115
- :where([data-edit-ui])[data-edit-armed] [contenteditable="true"]:hover {
116
- outline-color: color-mix(in oklch, var(--edit-accent, var(--color-brand-content)) 45%, transparent);
117
- }
118
+ &:hover {
119
+ outline-color: color-mix(in oklch, var(--edit-accent, var(--color-brand-content)) 45%, transparent);
120
+ }
118
121
 
119
- :where([data-edit-ui]) :where([data-edit-armed]) [contenteditable="true"]:focus,
120
- :where([data-edit-ui])[data-edit-armed] [contenteditable="true"]:focus {
121
- outline-color: var(--edit-accent, var(--color-brand-content));
122
+ &:focus {
123
+ outline-color: var(--edit-accent, var(--color-brand-content));
124
+ }
122
125
  }
123
126
 
124
- /* ---- Size handles ----
125
- Injected as siblings of the element's own markup, so its native :hover and
126
- :focus still fire and its body stays interactive. */
127
- :where([data-edit-sizable]) {
127
+ /* ---- Size handles ---- */
128
+ :where(:has(> [data-edit-handle])) {
128
129
  position: relative;
129
130
  }
130
131
 
131
- /* Handles reveal on approach rather than sitting on the element permanently.
132
- The hairline is chrome, so it follows the [data-edit-ui] scope like every
133
- other affordance; outside the scope the handles still work (hit areas and
134
- resize cursors stay), they just draw nothing. */
135
- :where([data-edit-sizable]) :where([data-edit-handle])::before {
132
+ :where([data-edit-handle])::before {
136
133
  opacity: 0;
137
134
  transition: opacity 0.12s ease, background-color 0.15s ease;
138
135
  }
139
136
 
140
- :where([data-edit-ui] [data-edit-sizable], [data-edit-ui][data-edit-sizable]):is(:hover, :focus-within) :where([data-edit-handle])::before {
137
+ :where(:has(> [data-edit-handle])):is(:hover, :focus-within):where(:not([data-edit-area="bare"], [data-edit-area="bare"] *)) > :where([data-edit-handle])::before {
141
138
  opacity: 1;
142
139
  }
143
140
 
@@ -145,9 +142,9 @@
145
142
  position: absolute;
146
143
  z-index: 100;
147
144
  background: transparent;
145
+ touch-action: none;
148
146
  --h: var(--edit-size-handle, 1rem);
149
147
 
150
- /* Hairline, revealed on hover */
151
148
  &::before {
152
149
  content: '';
153
150
  position: absolute;
@@ -167,11 +164,10 @@
167
164
  }
168
165
  }
169
166
 
170
- :where([data-edit-handle]):focus-visible {
167
+ :where([data-edit-handle]):focus-visible:where(:not([data-edit-area="bare"], [data-edit-area="bare"] *)) {
171
168
  outline: 2px solid var(--edit-accent, var(--color-brand-content));
172
169
  }
173
170
 
174
- /* Edges span their axis; corners are square. A corner value contains a hyphen. */
175
171
  :where([data-edit-handle="top"], [data-edit-handle="bottom"]) {
176
172
  left: 0;
177
173
  width: 100%;
@@ -199,8 +195,6 @@
199
195
  height: var(--h);
200
196
  }
201
197
 
202
- /* Inset — each handle straddles its edge; a corner matches one rule per axis.
203
- inset-inline-* carries the logical start/end values, so they flip for RTL. */
204
198
  :where([data-edit-handle^="top"]) {
205
199
  top: calc(var(--h) * -0.5);
206
200
  }
@@ -225,7 +219,6 @@
225
219
  inset-inline-end: calc(var(--h) * -0.5);
226
220
  }
227
221
 
228
- /* Corner cursors; the logical pair flips with direction. */
229
222
  :where([data-edit-handle="top-left"], [data-edit-handle="bottom-right"], [data-edit-handle="top-start"], [data-edit-handle="bottom-end"]) {
230
223
  cursor: nwse-resize;
231
224
  }
@@ -242,12 +235,12 @@
242
235
  cursor: nwse-resize;
243
236
  }
244
237
 
245
- /* Collapsed past --edit-size-collapse-* — authors restyle freely */
246
- :where([data-edit-collapsed]) {
247
- opacity: 0.45;
238
+ /* Bare: handles keep their hit areas and cursors, draw nothing */
239
+ :where([data-edit-area="bare"] [data-edit-handle])::before {
240
+ content: none;
248
241
  }
249
242
 
250
- /* Full-viewport capture layer during a size or move drag */
243
+ /* Pointer capture layer during a resize */
251
244
  :where([data-edit-overlay]) {
252
245
  position: fixed;
253
246
  inset: 0;
@@ -255,7 +248,7 @@
255
248
  background: transparent;
256
249
  }
257
250
 
258
- /* Screen-reader announcements (keyboard grab / move / drop) */
251
+ /* Screen-reader announcements */
259
252
  :where([data-edit-live]) {
260
253
  position: absolute;
261
254
  width: 1px;
@@ -270,65 +263,9 @@
270
263
 
271
264
  @media (prefers-reduced-motion: reduce) {
272
265
 
273
- :where([data-edit-armed]) > [draggable="true"],
266
+ :where([data-edit-piece]),
274
267
  :where([data-edit-handle])::before {
275
268
  transition: none;
276
269
  }
277
270
  }
278
271
  }
279
-
280
- /* ---- Authoring chrome (toolbar + class/scope menu) ---- */
281
- @layer components {
282
-
283
- /* Presence is script's business — it knows which route is on screen. Whether it
284
- is seen is not, so the toolbar is styled and revealed here. */
285
- :where([data-edit-toolbar]) {
286
- display: none;
287
- position: fixed;
288
- inset-block-end: 1rem;
289
- inset-inline-end: 1rem;
290
- z-index: 9999;
291
- gap: 0.5rem;
292
- align-items: center;
293
-
294
- & button[disabled] {
295
- opacity: 0.35;
296
- pointer-events: none;
297
- }
298
- }
299
-
300
- /* --edit-toolbar: none (any scope up to :root) suppresses it — e.g. a site
301
- that demos .authoring regions but has its own publish flow. */
302
- :root[data-edit-active] :where([data-edit-toolbar]) {
303
- display: var(--edit-toolbar, flex);
304
- }
305
-
306
- /* A popover: the top layer sits above every stacking context, so no handle,
307
- overlay or z-index anywhere on the page can paint over it. */
308
- :where([data-edit-menu]) {
309
- position: fixed;
310
- margin: 0;
311
- width: 16rem;
312
- padding: 0.6rem;
313
- display: flex;
314
- flex-direction: column;
315
- gap: 0.4rem;
316
- background: var(--color-surface-1);
317
- border: 1px solid var(--color-line);
318
- border-radius: var(--radius);
319
- box-shadow: 0 8px 30px rgb(0 0 0 / 0.2);
320
-
321
- & input {
322
- width: 100%;
323
- font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
324
- font-size: 0.75rem;
325
- }
326
-
327
- & small {
328
- font-size: 0.7rem;
329
- text-transform: uppercase;
330
- letter-spacing: 0.05em;
331
- color: var(--color-content-subtle);
332
- }
333
- }
334
- }