@daydreamui/has-lab 0.0.0-stage → 0.3.0

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/dist/index.js ADDED
@@ -0,0 +1,1568 @@
1
+ import { addEvent, className, createComponent, delegateEvents, effect, insert, setAttribute, setStyleProperty, template } from "@solidjs/web";
2
+ import { For, Show, createEffect, createMemo, createSignal, onCleanup, untrack } from "solid-js";
3
+ var base = String.raw`(?:[a-zA-Z][\w-]*|\*)?(?:[.#][\w-]+)*`;
4
+ var attribute = String.raw`\[(?:[\w-]+)(?:=(?:"[\w -]+"|'[\w -]+'|[\w-]+))?\]`;
5
+ var simple = String.raw`(?:[a-zA-Z][\w-]*|\*)?(?:[.#][\w-]+|${attribute})*`;
6
+ var pseudoStates = [
7
+ {
8
+ token: ":checked",
9
+ message: "No candidate input is checked."
10
+ },
11
+ {
12
+ token: ":hover",
13
+ message: "The element is present. Hover it while browsing the page."
14
+ },
15
+ ...[
16
+ ":focus-visible",
17
+ ":focus-within",
18
+ ":focus"
19
+ ].map((token) => ({
20
+ token,
21
+ message: "The control is present. Focus it while browsing the page."
22
+ })),
23
+ {
24
+ token: ":not(:placeholder-shown)",
25
+ message: "The fields are empty. This condition waits until text is entered."
26
+ },
27
+ {
28
+ token: ":invalid",
29
+ message: "No candidate field is invalid."
30
+ },
31
+ {
32
+ token: ":valid",
33
+ message: "No candidate field is valid."
34
+ }
35
+ ];
36
+ var statePattern = pseudoStates.map(({ token }) => token.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join("|");
37
+ var states = `(?:${statePattern}|:nth-child\\(\\d+\\))*`;
38
+ var whitespace = String.raw`[\t\n\f\r ]`;
39
+ var trim = (text) => text.replace(/^[\t\n\f\r ]+|[\t\n\f\r ]+$/g, "");
40
+ var condition = `(?:[>+~]${whitespace}*)?${simple}${states}`;
41
+ var shape = new RegExp(`^(${base}):has\\(${whitespace}*(${condition})${whitespace}*\\)([\\s\\S]*)$`);
42
+ var recipientShape = new RegExp(`^(?:>${whitespace}*)?${simple}$`);
43
+ function relationshipOf(selector) {
44
+ const match = shape.exec(trim(selector));
45
+ if (!match) return null;
46
+ const carrier = match[1], condition = trim(match[2]), suffix = match[3];
47
+ const recipient = trim(suffix);
48
+ if (!carrier || !condition || /^[>+~]$/.test(condition)) return null;
49
+ if (suffix && !/^[\t\n\f\r >]/.test(suffix)) return null;
50
+ if (recipient && (recipient === ">" || !recipientShape.test(recipient))) return null;
51
+ return {
52
+ carrier,
53
+ condition,
54
+ recipient,
55
+ candidate: candidateOf(condition)
56
+ };
57
+ }
58
+ function candidateOf(condition) {
59
+ let candidate = trim(condition.replace(new RegExp(statePattern, "g"), ""));
60
+ if (!candidate || /^[>+~]$/.test(candidate)) candidate += " *";
61
+ return trim(candidate);
62
+ }
63
+ var sourceKey = (source) => "file" in source ? `file:${source.file}` : "style" in source ? `style:${source.style}` : `url:${source.url}`;
64
+ var sourceName = (source) => "file" in source ? source.file : "style" in source ? `Style block ${source.style + 1}` : source.url;
65
+ function rulesIn(blocks, text, source) {
66
+ const result = [];
67
+ const visit = (blocks, nested) => {
68
+ for (const block of blocks) {
69
+ if (!block.statement && !block.prelude.startsWith("@") && block.prelude.includes(":has(")) result.push({
70
+ id: `${sourceKey(source)}:${result.length}:${block.prelude}`,
71
+ selector: block.prelude,
72
+ declarations: sourceBody(text.slice(...block.range)),
73
+ source: sourceName(source),
74
+ relationship: nested || block.children.length ? null : relationshipOf(block.prelude),
75
+ limitation: nested || block.children.length ? "This rule has nesting or conditions. Use the CSS editor to inspect its context." : "The lens explains one :has() with a simple parent, child or sibling condition, and an optional descendant to style."
76
+ });
77
+ visit(block.children, true);
78
+ }
79
+ };
80
+ visit(blocks, false);
81
+ return result;
82
+ }
83
+ function sourceBody(rule) {
84
+ let quote = "", comment = false;
85
+ for (let i = 0; i < rule.length; i++) {
86
+ const ch = rule[i];
87
+ if (comment) {
88
+ if (ch === "*" && rule[i + 1] === "/") {
89
+ comment = false;
90
+ i++;
91
+ }
92
+ continue;
93
+ }
94
+ if (ch === "\\") {
95
+ i++;
96
+ continue;
97
+ }
98
+ if (quote) {
99
+ if (ch === quote) quote = "";
100
+ continue;
101
+ }
102
+ if (ch === "/" && rule[i + 1] === "*") {
103
+ comment = true;
104
+ i++;
105
+ continue;
106
+ }
107
+ if (ch === "\"" || ch === "'") {
108
+ quote = ch;
109
+ continue;
110
+ }
111
+ if (ch === "{") return rule.slice(i + 1, rule.endsWith("}") ? -1 : void 0);
112
+ }
113
+ return "";
114
+ }
115
+ function relative(element, selector) {
116
+ if (selector.startsWith("+") || selector.startsWith("~")) {
117
+ const matches = [];
118
+ for (let next = element.nextElementSibling; next; next = next.nextElementSibling) {
119
+ if (next.matches(selector.slice(1).trim())) matches.push(next);
120
+ if (selector.startsWith("+")) break;
121
+ }
122
+ return matches;
123
+ }
124
+ return [...element.querySelectorAll(`:scope ${selector}`)];
125
+ }
126
+ function describe(element) {
127
+ const heading = element.querySelector(":scope > h1, :scope > h2, :scope > h3");
128
+ const label = element.getAttribute("aria-label") ?? heading?.textContent?.trim();
129
+ return label ? label : element.id ? `${element.localName}#${element.id}` : `${element.localName}${element.classList[0] ? `.${element.classList[0]}` : ""}`;
130
+ }
131
+ function explain(relationship, carrier, matches, candidates, witnesses) {
132
+ if (matches) return `Matches: ${witnesses.slice(0, 2).map(describe).join(" and ")} ${witnesses.length === 1 ? "satisfies" : "satisfy"} the condition.`;
133
+ const { condition } = relationship;
134
+ if (candidates[0]) {
135
+ const missing = pseudoStates.find(({ token }) => condition.includes(token) && candidates.every((el) => !el.matches(token)));
136
+ if (missing) return missing.message;
137
+ }
138
+ const count = condition.match(/^>\s*:nth-child\((\d+)\)$/);
139
+ if (count) return `${carrier.children.length} direct children; this condition needs at least ${count[1]}.`;
140
+ if (condition.startsWith("+") && !carrier.nextElementSibling) return "There is no next sibling.";
141
+ return `No ${condition.startsWith("+") || condition.startsWith("~") ? "following sibling" : "element inside"} currently matches ${condition}.`;
142
+ }
143
+ function readRelationship(dd, target, relationship, chosen) {
144
+ const node = dd.geometry.node(target.elementId);
145
+ if (!node) return {
146
+ entries: [],
147
+ selected: null,
148
+ matched: 0,
149
+ total: 0
150
+ };
151
+ const all = node.getRootNode().querySelectorAll(relationship.carrier);
152
+ const carriers = Array.from({ length: Math.min(all.length, 200) }, (_, i) => all[i]);
153
+ const chosenId = chosen?.markup === target.markup ? dd.pageFind(target.viewportId, chosen.selector) : null;
154
+ const chosenNode = chosenId ? dd.geometry.node(chosenId) : void 0;
155
+ const selectedCarrier = carriers.find((element) => element === chosenNode) ?? carriers.find((element) => element === node || element.contains(node)) ?? carriers[0];
156
+ const entries = carriers.map((carrier, index) => {
157
+ const matches = carrier.matches(`${relationship.carrier}:has(${relationship.condition})`);
158
+ const entry = {
159
+ key: String(index),
160
+ name: describe(carrier),
161
+ matches,
162
+ explanation: "",
163
+ recipientNames: [],
164
+ recipientId: null,
165
+ marks: []
166
+ };
167
+ if (carrier !== selectedCarrier) return entry;
168
+ const witnesses = relative(carrier, relationship.condition);
169
+ const candidates = relative(carrier, relationship.candidate);
170
+ const recipients = relationship.recipient ? relative(carrier, relationship.recipient) : [carrier];
171
+ const marks = [];
172
+ const mark = (element, role, label) => {
173
+ const id = target.idOf(element);
174
+ if (id) marks.push({
175
+ id,
176
+ role,
177
+ label
178
+ });
179
+ };
180
+ mark(carrier, "carrier", recipients[0] === carrier ? "Condition + style" : "Condition lives here");
181
+ for (const source of (witnesses.length ? witnesses : candidates).slice(0, 3)) mark(source, "condition", matches ? "Condition met" : "Looks for this");
182
+ for (const recipient of recipients.slice(0, 4)) if (recipient !== carrier) mark(recipient, "recipient", matches ? "Selector matches here" : "Would style this");
183
+ return {
184
+ ...entry,
185
+ explanation: explain(relationship, carrier, matches, candidates, witnesses),
186
+ recipientNames: recipients.slice(0, 4).map(describe),
187
+ recipientId: recipients[0] ? target.idOf(recipients[0]) : null,
188
+ marks
189
+ };
190
+ });
191
+ return {
192
+ entries,
193
+ selected: entries[carriers.indexOf(selectedCarrier)] ?? null,
194
+ matched: entries.filter((entry) => entry.matches).length,
195
+ total: all.length
196
+ };
197
+ }
198
+ var _tmpl$$4 = template(`<svg aria-hidden=true><path>`);
199
+ var _tmpl$2$4 = template(`<div><span>`);
200
+ function createMarkEffect(dd, nodes, publish) {
201
+ createEffect(() => ({
202
+ nodes: nodes(),
203
+ geometry: dd.geometry.version()
204
+ }), ({ nodes }) => untrack(() => {
205
+ publish(nodes.flatMap((mark) => {
206
+ const rect = dd.geometry.rect(mark.id);
207
+ return rect ? [{
208
+ ...mark,
209
+ rect
210
+ }] : [];
211
+ }));
212
+ }));
213
+ onCleanup(() => publish([]));
214
+ }
215
+ function RelationshipOverlay(props) {
216
+ const connection = () => {
217
+ const a = props.marks().find((mark) => mark.role === "condition")?.rect;
218
+ const b = props.marks().find((mark) => mark.role === "carrier")?.rect;
219
+ return a && b ? `M ${a.x + a.width / 2} ${a.y} Q ${a.x + a.width / 2} ${Math.min(a.y, b.y) - 18} ${b.x + b.width / 2} ${b.y}` : "";
220
+ };
221
+ return [(() => {
222
+ var _el$ = _tmpl$$4();
223
+ var _el$2 = _el$.firstChild;
224
+ effect(() => {
225
+ return {
226
+ e: `${props.p}-wires`,
227
+ t: connection()
228
+ };
229
+ }, ({ e, t }, _p$) => {
230
+ className(_el$, e, _p$?.e);
231
+ t !== _p$?.t && setAttribute(_el$2, "d", t);
232
+ });
233
+ return _el$;
234
+ })(), createComponent(For, {
235
+ get each() {
236
+ return props.marks();
237
+ },
238
+ children: (mark) => (() => {
239
+ var _el$3 = _tmpl$2$4();
240
+ var _el$4 = _el$3.firstChild;
241
+ insert(_el$4, () => {
242
+ return mark.label;
243
+ });
244
+ effect(() => {
245
+ return {
246
+ e: `${props.p}-box`,
247
+ t: mark.role,
248
+ a: `${mark.rect.x}px`,
249
+ o: `${mark.rect.y}px`,
250
+ i: `${mark.rect.width}px`,
251
+ n: `${mark.rect.height}px`
252
+ };
253
+ }, ({ e, t, a, o, i, n }, _p$) => {
254
+ className(_el$3, e, _p$?.e);
255
+ t !== _p$?.t && setAttribute(_el$3, "data-role", t);
256
+ a !== _p$?.a && setStyleProperty(_el$3, "left", a);
257
+ o !== _p$?.o && setStyleProperty(_el$3, "top", o);
258
+ i !== _p$?.i && setStyleProperty(_el$3, "width", i);
259
+ n !== _p$?.n && setStyleProperty(_el$3, "height", n);
260
+ });
261
+ return _el$3;
262
+ })()
263
+ })];
264
+ }
265
+ function createReading(dd, source, relationship, publish, chosen = () => null) {
266
+ const [reading, setReading] = createSignal(null);
267
+ const [drawing, setDrawing] = createSignal(true);
268
+ createEffect(() => ({
269
+ target: source.target(),
270
+ relationship: relationship(),
271
+ chosen: chosen(),
272
+ input: source.input(),
273
+ interaction: dd.interactionVersion(),
274
+ resize: dd.geometry.resizeVersion()
275
+ }), (state) => untrack(() => {
276
+ setReading(state.target && state.relationship ? readRelationship(dd, state.target, state.relationship, state.chosen) : null);
277
+ }));
278
+ createMarkEffect(dd, () => drawing() ? reading()?.selected?.marks ?? [] : [], publish);
279
+ const carrierChoice = (key) => {
280
+ const target = source.target(), relation = relationship(), index = Number(key);
281
+ if (!target || !relation || !Number.isInteger(index) || index < 0 || index >= 200) return null;
282
+ const node = (dd.geometry.node(target.elementId)?.getRootNode())?.querySelectorAll(relation.carrier)[index];
283
+ const id = node ? target.idOf(node) : null;
284
+ const selector = id ? dd.pageElement(id)?.selector : null;
285
+ return selector ? {
286
+ selector,
287
+ markup: target.markup
288
+ } : null;
289
+ };
290
+ return {
291
+ reading,
292
+ drawing,
293
+ setDrawing,
294
+ carrierChoice
295
+ };
296
+ }
297
+ var _tmpl$$3 = template(`<div><label><input type=checkbox>Canvas lens</label><div><span><i data-role=carrier></i>Condition carrier</span><span><i data-role=condition></i>Condition witness</span><span><i data-role=recipient></i>Style recipient`);
298
+ var _tmpl$2$3 = template(`<p>Showing the first <!> of <!> elements. Counts cover only these elements; choose a narrower rule to inspect the rest.`);
299
+ function LensControls(props) {
300
+ var _el$ = _tmpl$$3();
301
+ var _el$2 = _el$.firstChild;
302
+ var _el$3 = _el$2.firstChild;
303
+ var _el$4 = _el$2.nextSibling;
304
+ _el$3.addEventListener("change", (event) => props.setDrawing(event.currentTarget.checked));
305
+ insert(_el$, createComponent(Show, {
306
+ get when() {
307
+ return props.reading();
308
+ },
309
+ children: (reading) => createComponent(Show, {
310
+ get when() {
311
+ return reading().total > reading().entries.length;
312
+ },
313
+ get children() {
314
+ var _el$5 = _tmpl$2$3();
315
+ var _el$7 = _el$5.firstChild.nextSibling;
316
+ var _el$9 = _el$7.nextSibling.nextSibling;
317
+ _el$9.nextSibling;
318
+ insert(_el$5, () => {
319
+ return reading().entries.length;
320
+ }, _el$7);
321
+ insert(_el$5, () => {
322
+ return reading().total;
323
+ }, _el$9);
324
+ effect(() => `${props.p}-muted`, (_v$, _$p) => {
325
+ className(_el$5, _v$, _$p);
326
+ });
327
+ return _el$5;
328
+ }
329
+ })
330
+ }), null);
331
+ effect(() => {
332
+ return {
333
+ e: `${props.p}-lens-controls`,
334
+ t: `${props.p}-check`,
335
+ a: props.drawing(),
336
+ o: `${props.p}-legend`
337
+ };
338
+ }, ({ e, t, a, o }, _p$) => {
339
+ className(_el$, e, _p$?.e);
340
+ className(_el$2, t, _p$?.t);
341
+ _el$3.checked = a;
342
+ className(_el$4, o, _p$?.o);
343
+ });
344
+ return _el$;
345
+ }
346
+ var _tmpl$$2 = template(`<div><p>Select an element on the canvas to explore what can affect it.`);
347
+ var _tmpl$2$2 = template(`<p>`);
348
+ var _tmpl$3$1 = template(`<div><code></code><button type=button>Use parent`);
349
+ var _tmpl$4$1 = template(`<label><input type=checkbox>All elements matching <code>`);
350
+ var _tmpl$5$1 = template(`<label>When…<select aria-label="Relationship condition">`);
351
+ var _tmpl$6$1 = template(`<label>Then apply these declarations<textarea aria-label="CSS declarations">`);
352
+ var _tmpl$7$1 = template(`<pre>`);
353
+ var _tmpl$8$1 = template(`<div><span role=status> of <!> match now`);
354
+ var _tmpl$9$1 = template(`<p>Move over the page, then hold ⌥ to interact. Release it to see the lens. Counts follow the real selector condition.`);
355
+ var _tmpl$10$1 = template(`<div><button type=button>Keep rule</button><button type=button>Revert trial`);
356
+ var _tmpl$11$1 = template(`<p>Edits <!>. A trial writes to the file; Revert restores it.`);
357
+ var _tmpl$12$1 = template(`<p>This sheet is also linked by <!>.`);
358
+ var _tmpl$13$1 = template(`<p>This element has no children or next sibling. Select its parent to explore a relationship.`);
359
+ var _tmpl$14$1 = template(`<option>`);
360
+ var _tmpl$15$1 = template(`<button type=button>Try on canvas`);
361
+ function CreatePanel(props) {
362
+ const { dd, p, ops, source, publish } = untrack(() => props);
363
+ const target = source.target;
364
+ const [recipeId, setRecipeId] = createSignal("checked");
365
+ const [similar, setSimilar] = createSignal(false);
366
+ const [declarations, setDeclarations] = createSignal("background: #e4f1ce;\nborder-color: #679b41;");
367
+ const recipe = () => target()?.recipes.find((item) => item.id === recipeId()) ?? target()?.recipes[0];
368
+ const selector = () => `${similar() && target()?.group ? target().group : target()?.selector}:has(${recipe()?.relative})`;
369
+ const rule = () => `${selector()} {\n${declarations().split("\n").map((line) => ` ${line}`).join("\n")}\n}`;
370
+ const sheetName = () => target()?.sheet ? sourceName(target().sheet) : "No unconditional writable stylesheet";
371
+ const sharedPages = () => {
372
+ const t = target();
373
+ if (!t?.sheet || !("file" in t.sheet)) return [];
374
+ const file = t.sheet.file;
375
+ return dd.document().pages.filter((entry) => entry.path !== t.page && dd.page(entry.path)?.sheets.some((sheet) => "file" in sheet.source && sheet.source.file === file)).map((entry) => entry.path);
376
+ };
377
+ const relationship = createMemo(() => {
378
+ const t = target(), choice = recipe();
379
+ return t && choice ? {
380
+ carrier: similar() && t.group ? t.group : t.selector,
381
+ condition: choice.relative,
382
+ recipient: "",
383
+ candidate: candidateOf(choice.relative)
384
+ } : null;
385
+ });
386
+ const { reading: inspection, drawing, setDrawing } = createReading(dd, source, () => relationship(), publish);
387
+ onCleanup(() => {
388
+ if (ops.trial()) ops.revert();
389
+ });
390
+ const tryRule = () => {
391
+ const t = target();
392
+ if (t && recipe()) ops.tryRule(t, selector(), declarations());
393
+ };
394
+ var _el$ = _tmpl$$2();
395
+ var _el$2 = _el$.firstChild;
396
+ insert(_el$, createComponent(Show, {
397
+ get when() {
398
+ return target();
399
+ },
400
+ get fallback() {
401
+ var _el$3 = _tmpl$2$2();
402
+ insert(_el$3, () => {
403
+ return source.reason();
404
+ });
405
+ return _el$3;
406
+ },
407
+ children: (target) => [
408
+ (() => {
409
+ var _el$4 = _tmpl$3$1();
410
+ var _el$5 = _el$4.firstChild;
411
+ var _el$6 = _el$5.nextSibling;
412
+ insert(_el$5, () => {
413
+ return target().selector;
414
+ });
415
+ _el$6._$$click = () => {
416
+ const t = target();
417
+ const parent = t ? dd.pageElement(t.elementId)?.parentId : null;
418
+ if (parent) dd.select(parent);
419
+ };
420
+ effect(() => {
421
+ return {
422
+ e: `${p}-row`,
423
+ t: !!ops.trial()
424
+ };
425
+ }, ({ e, t }, _p$) => {
426
+ className(_el$4, e, _p$?.e);
427
+ t !== _p$?.t && setAttribute(_el$6, "disabled", t);
428
+ });
429
+ return _el$4;
430
+ })(),
431
+ (() => {
432
+ var _el$7 = _tmpl$2$2();
433
+ insert(_el$7, () => {
434
+ return target().page;
435
+ });
436
+ effect(() => `${p}-muted`, (_v$, _$p) => {
437
+ className(_el$7, _v$, _$p);
438
+ });
439
+ return _el$7;
440
+ })(),
441
+ createComponent(Show, {
442
+ get when() {
443
+ return target().group;
444
+ },
445
+ get children() {
446
+ var _el$8 = _tmpl$4$1();
447
+ var _el$9 = _el$8.firstChild;
448
+ var _el$11 = _el$9.nextSibling.nextSibling;
449
+ _el$9.addEventListener("change", (event) => setSimilar(event.currentTarget.checked));
450
+ insert(_el$11, () => {
451
+ return target().group;
452
+ });
453
+ effect(() => {
454
+ return {
455
+ e: `${p}-check`,
456
+ t: similar(),
457
+ a: !!ops.trial()
458
+ };
459
+ }, ({ e, t, a }, _p$) => {
460
+ className(_el$8, e, _p$?.e);
461
+ _el$9.checked = t;
462
+ a !== _p$?.a && setAttribute(_el$9, "disabled", a);
463
+ });
464
+ return _el$8;
465
+ }
466
+ }),
467
+ createComponent(Show, {
468
+ get when() {
469
+ return recipe();
470
+ },
471
+ get fallback() {
472
+ return _tmpl$13$1();
473
+ },
474
+ get children() {
475
+ return [
476
+ (() => {
477
+ var _el$12 = _tmpl$5$1();
478
+ var _el$14 = _el$12.firstChild.nextSibling;
479
+ _el$14.addEventListener("change", (event) => {
480
+ setRecipeId(event.currentTarget.value);
481
+ ops.clearStatus();
482
+ });
483
+ insert(_el$14, createComponent(For, {
484
+ get each() {
485
+ return target().recipes;
486
+ },
487
+ children: (option) => (() => {
488
+ var _el$37 = _tmpl$14$1();
489
+ insert(_el$37, () => {
490
+ return option.title;
491
+ });
492
+ effect(() => option.id, (_v$) => {
493
+ _el$37.value = _v$;
494
+ });
495
+ return _el$37;
496
+ })()
497
+ }));
498
+ effect(() => {
499
+ return {
500
+ e: !!ops.trial(),
501
+ t: recipe()?.id
502
+ };
503
+ }, ({ e, t }, _p$) => {
504
+ e !== _p$?.e && setAttribute(_el$14, "disabled", e);
505
+ queueMicrotask(() => {
506
+ return _el$14.value = t;
507
+ }) || (_el$14.value = t);
508
+ });
509
+ return _el$12;
510
+ })(),
511
+ (() => {
512
+ var _el$15 = _tmpl$6$1();
513
+ var _el$17 = _el$15.firstChild.nextSibling;
514
+ _el$17._$$input = (event) => {
515
+ setDeclarations(event.currentTarget.value);
516
+ ops.clearStatus();
517
+ };
518
+ effect(() => {
519
+ return {
520
+ e: declarations(),
521
+ t: !!ops.trial()
522
+ };
523
+ }, ({ e, t }, _p$) => {
524
+ _el$17.value = e ?? "";
525
+ t !== _p$?.t && setAttribute(_el$17, "disabled", t);
526
+ });
527
+ return _el$15;
528
+ })(),
529
+ (() => {
530
+ var _el$18 = _tmpl$7$1();
531
+ insert(_el$18, rule);
532
+ effect(() => `${p}-rule`, (_v$, _$p) => {
533
+ className(_el$18, _v$, _$p);
534
+ });
535
+ return _el$18;
536
+ })(),
537
+ (() => {
538
+ var _el$19 = _tmpl$8$1();
539
+ var _el$20 = _el$19.firstChild;
540
+ var _el$21 = _el$20.firstChild;
541
+ var _el$22 = _el$21.nextSibling;
542
+ _el$22.nextSibling;
543
+ insert(_el$20, () => {
544
+ return inspection()?.matched ?? 0;
545
+ }, _el$21);
546
+ insert(_el$20, () => {
547
+ return inspection()?.entries.length ?? 0;
548
+ }, _el$22);
549
+ effect(() => `${p}-row`, (_v$, _$p) => {
550
+ className(_el$19, _v$, _$p);
551
+ });
552
+ return _el$19;
553
+ })(),
554
+ createComponent(LensControls, {
555
+ p,
556
+ drawing,
557
+ setDrawing,
558
+ reading: inspection
559
+ }),
560
+ (() => {
561
+ var _el$24 = _tmpl$9$1();
562
+ effect(() => `${p}-muted`, (_v$, _$p) => {
563
+ className(_el$24, _v$, _$p);
564
+ });
565
+ return _el$24;
566
+ })(),
567
+ createComponent(Show, {
568
+ get when() {
569
+ return ops.trial();
570
+ },
571
+ get fallback() {
572
+ var _el$38 = _tmpl$15$1();
573
+ _el$38._$$click = tryRule;
574
+ effect(() => {
575
+ return {
576
+ e: `${p}-primary`,
577
+ t: ops.creating() || !target().sheet || !declarations().trim()
578
+ };
579
+ }, ({ e, t }, _p$) => {
580
+ className(_el$38, e, _p$?.e);
581
+ t !== _p$?.t && setAttribute(_el$38, "disabled", t);
582
+ });
583
+ return _el$38;
584
+ },
585
+ get children() {
586
+ var _el$25 = _tmpl$10$1();
587
+ var _el$26 = _el$25.firstChild;
588
+ var _el$27 = _el$26.nextSibling;
589
+ addEvent(_el$26, "click", ops.keep, true);
590
+ addEvent(_el$27, "click", ops.revert, true);
591
+ effect(() => {
592
+ return {
593
+ e: `${p}-row`,
594
+ t: `${p}-primary`
595
+ };
596
+ }, ({ e, t }, _p$) => {
597
+ className(_el$25, e, _p$?.e);
598
+ className(_el$26, t, _p$?.t);
599
+ });
600
+ return _el$25;
601
+ }
602
+ }),
603
+ (() => {
604
+ var _el$28 = _tmpl$11$1();
605
+ var _el$30 = _el$28.firstChild.nextSibling;
606
+ _el$30.nextSibling;
607
+ insert(_el$28, sheetName, _el$30);
608
+ effect(() => `${p}-muted`, (_v$, _$p) => {
609
+ className(_el$28, _v$, _$p);
610
+ });
611
+ return _el$28;
612
+ })(),
613
+ createComponent(Show, {
614
+ get when() {
615
+ return sharedPages().length;
616
+ },
617
+ get children() {
618
+ var _el$32 = _tmpl$12$1();
619
+ var _el$34 = _el$32.firstChild.nextSibling;
620
+ _el$34.nextSibling;
621
+ insert(_el$32, () => {
622
+ return sharedPages().join(", ");
623
+ }, _el$34);
624
+ effect(() => `${p}-muted`, (_v$, _$p) => {
625
+ className(_el$32, _v$, _$p);
626
+ });
627
+ return _el$32;
628
+ }
629
+ })
630
+ ];
631
+ }
632
+ })
633
+ ]
634
+ }), null);
635
+ effect(() => {
636
+ return {
637
+ e: `${p}-panel`,
638
+ t: `${p}-muted`
639
+ };
640
+ }, ({ e, t }, _p$) => {
641
+ className(_el$, e, _p$?.e);
642
+ className(_el$2, t, _p$?.t);
643
+ });
644
+ return _el$;
645
+ }
646
+ delegateEvents(["click", "input"]);
647
+ function observeRelationships(dd, source, publish) {
648
+ const [choices, setChoices] = createSignal({});
649
+ const [view, setView] = createSignal(null);
650
+ let cache = null;
651
+ createEffect(() => ({
652
+ target: source.target(),
653
+ choices: choices(),
654
+ load: dd.loadVersion()
655
+ }), (state) => untrack(() => {
656
+ const target = state.target;
657
+ if (!target) {
658
+ setView(null);
659
+ cache = null;
660
+ return;
661
+ }
662
+ if (cache?.target !== target) {
663
+ const live = new Set(target.sources.map(sourceKey));
664
+ const rules = (dd.page(target.page)?.sheets ?? []).filter((sheet) => live.has(sourceKey(sheet.source))).flatMap((sheet) => rulesIn(dd.core.cssBlocks(sheet.text), sheet.text, sheet.source));
665
+ for (const rule of rules) if (rule.relationship && !CSS.supports(`selector(${rule.selector})`)) rule.relationship = null;
666
+ cache = {
667
+ target,
668
+ rules
669
+ };
670
+ }
671
+ const rules = cache.rules, choice = state.choices[`${state.load}:${target.viewportId}`];
672
+ const rule = rules.find((entry) => entry.id === choice?.rule) ?? rules.find((entry) => entry.relationship) ?? rules[0] ?? null;
673
+ setView({
674
+ target,
675
+ rules,
676
+ rule,
677
+ carrier: choice?.carrier ?? null
678
+ });
679
+ }));
680
+ const reader = createReading(dd, source, () => view()?.rule?.relationship ?? null, publish, () => view()?.carrier ?? null);
681
+ const choose = (change) => {
682
+ const id = source.target()?.viewportId;
683
+ if (id) {
684
+ const key = `${dd.loadVersion()}:${id}`;
685
+ setChoices((all) => ({
686
+ ...all,
687
+ [key]: {
688
+ rule: null,
689
+ carrier: null,
690
+ ...all[key],
691
+ ...change
692
+ }
693
+ }));
694
+ }
695
+ };
696
+ return {
697
+ view,
698
+ ...reader,
699
+ selected: () => reader.reading()?.selected,
700
+ setRuleId: (rule) => choose({
701
+ rule,
702
+ carrier: null
703
+ }),
704
+ setCarrier: (key) => choose({ carrier: reader.carrierChoice(key) })
705
+ };
706
+ }
707
+ var _tmpl$$1 = template(`<div><p>`);
708
+ var _tmpl$2$1 = template(`<div>`);
709
+ var _tmpl$3 = template(`<div><div><span>Rules in this page</span><span></span></div><p>`);
710
+ var _tmpl$4 = template(`<p>No :has() rules yet. Create a rule for the selected element.`);
711
+ var _tmpl$5 = template(`<button type=button><span aria-hidden=true></span><code>`);
712
+ var _tmpl$6 = template(`<details><summary>Declarations <span></span></summary><pre></pre><p>Counts describe the selector condition. Stylesheet conditions and other rules can affect which styles apply. Select the target to inspect the cascade in the CSS panel.`);
713
+ var _tmpl$7 = template(`<p>`);
714
+ var _tmpl$8 = template(`<label>Inspect element`);
715
+ var _tmpl$9 = template(`<select aria-label="Inspect element">`);
716
+ var _tmpl$10 = template(`<div><div><span>Relationship</span><span role=status> of <!> match</span></div><!><!>`);
717
+ var _tmpl$11 = template(`<option><!><!>`);
718
+ var _tmpl$12 = template(`<p>No element currently matches <!>.`);
719
+ var _tmpl$13 = template(`<ol><li><span>1</span><div><span>Condition lives on</span><code></code></div></li><li><span>2</span><div><span>Looks for</span><code></code></div></li><li><span>3</span><div><span>Rule targets</span><code>`);
720
+ var _tmpl$14 = template(`<p role=status>`);
721
+ var _tmpl$15 = template(`<p>Hold ⌥ over the page to interact. This panel follows the real state; canvas labels return when you release it.`);
722
+ var _tmpl$16 = template(`<div><button type=button>Select styled element`);
723
+ function ExplorePanel(props) {
724
+ const { dd, p, source, publish } = untrack(() => props);
725
+ const { view, reading, selected, setRuleId, setCarrier, drawing, setDrawing } = observeRelationships(dd, source, publish);
726
+ return createComponent(Show, {
727
+ get when() {
728
+ return view();
729
+ },
730
+ get fallback() {
731
+ var _el$ = _tmpl$$1();
732
+ var _el$2 = _el$.firstChild;
733
+ insert(_el$2, () => {
734
+ return source.reason();
735
+ });
736
+ effect(() => `${p}-empty`, (_v$, _$p) => {
737
+ className(_el$, _v$, _$p);
738
+ });
739
+ return _el$;
740
+ },
741
+ children: (view) => [(() => {
742
+ var _el$3 = _tmpl$3();
743
+ var _el$4 = _el$3.firstChild;
744
+ var _el$6 = _el$4.firstChild.nextSibling;
745
+ var _el$7 = _el$4.nextSibling;
746
+ insert(_el$6, () => {
747
+ return view().rules.length;
748
+ });
749
+ insert(_el$7, () => {
750
+ return view().target.page;
751
+ });
752
+ insert(_el$3, createComponent(Show, {
753
+ get when() {
754
+ return view().rules.length;
755
+ },
756
+ get fallback() {
757
+ return _tmpl$4();
758
+ },
759
+ get children() {
760
+ var _el$8 = _tmpl$2$1();
761
+ insert(_el$8, createComponent(For, {
762
+ get each() {
763
+ return view().rules;
764
+ },
765
+ children: (rule) => (() => {
766
+ var _el$10 = _tmpl$5();
767
+ var _el$11 = _el$10.firstChild;
768
+ var _el$12 = _el$11.nextSibling;
769
+ _el$10._$$click = () => setRuleId(rule.id);
770
+ insert(_el$11, () => {
771
+ return view().rule?.id === rule.id ? "●" : "○";
772
+ });
773
+ insert(_el$12, () => {
774
+ return rule.selector;
775
+ });
776
+ effect(() => {
777
+ return {
778
+ e: `${p}-rule-row`,
779
+ t: view().rule?.id === rule.id ? "true" : "false"
780
+ };
781
+ }, ({ e, t }, _p$) => {
782
+ className(_el$10, e, _p$?.e);
783
+ t !== _p$?.t && setAttribute(_el$10, "aria-pressed", t);
784
+ });
785
+ return _el$10;
786
+ })()
787
+ }));
788
+ effect(() => `${p}-rules`, (_v$, _$p) => {
789
+ className(_el$8, _v$, _$p);
790
+ });
791
+ return _el$8;
792
+ }
793
+ }), null);
794
+ effect(() => {
795
+ return {
796
+ e: `${p}-section`,
797
+ t: `${p}-heading`,
798
+ a: `${p}-muted ${p}-path`
799
+ };
800
+ }, ({ e, t, a }, _p$) => {
801
+ className(_el$3, e, _p$?.e);
802
+ className(_el$4, t, _p$?.t);
803
+ className(_el$7, a, _p$?.a);
804
+ });
805
+ return _el$3;
806
+ })(), createComponent(Show, {
807
+ get when() {
808
+ return view().rule;
809
+ },
810
+ children: (rule) => [createComponent(Show, {
811
+ get when() {
812
+ return rule().relationship;
813
+ },
814
+ get fallback() {
815
+ var _el$19 = _tmpl$7();
816
+ insert(_el$19, () => {
817
+ return rule().limitation;
818
+ });
819
+ effect(() => `${p}-muted`, (_v$, _$p) => {
820
+ className(_el$19, _v$, _$p);
821
+ });
822
+ return _el$19;
823
+ },
824
+ children: (relationship) => (() => {
825
+ var _el$20 = _tmpl$10();
826
+ var _el$21 = _el$20.firstChild;
827
+ var _el$23 = _el$21.firstChild.nextSibling;
828
+ var _el$24 = _el$23.firstChild;
829
+ var _el$25 = _el$24.nextSibling;
830
+ _el$25.nextSibling;
831
+ var _el$29 = _el$21.nextSibling;
832
+ var _el$30 = _el$29.nextSibling;
833
+ insert(_el$23, () => {
834
+ return reading()?.matched;
835
+ }, _el$24);
836
+ insert(_el$23, () => {
837
+ return reading()?.entries.length;
838
+ }, _el$25);
839
+ insert(_el$20, createComponent(Show, {
840
+ get when() {
841
+ return (reading()?.entries.length ?? 0) > 1;
842
+ },
843
+ get children() {
844
+ return [(() => {
845
+ var _el$27 = _tmpl$8();
846
+ setAttribute(_el$27, "for", `${p}-carrier`);
847
+ effect(() => `${p}-sr-only`, (_v$, _$p) => {
848
+ className(_el$27, _v$, _$p);
849
+ });
850
+ return _el$27;
851
+ })(), (() => {
852
+ var _el$28 = _tmpl$9();
853
+ _el$28.addEventListener("change", (event) => setCarrier(event.currentTarget.value));
854
+ setAttribute(_el$28, "id", `${p}-carrier`);
855
+ insert(_el$28, createComponent(For, {
856
+ get each() {
857
+ return reading()?.entries;
858
+ },
859
+ children: (entry) => (() => {
860
+ var _el$31 = _tmpl$11();
861
+ var _el$32 = _el$31.firstChild;
862
+ var _el$33 = _el$32.nextSibling;
863
+ insert(_el$31, () => {
864
+ return entry.name;
865
+ }, _el$32);
866
+ insert(_el$31, () => {
867
+ return entry.matches ? " · matches" : " · waiting";
868
+ }, _el$33);
869
+ effect(() => {
870
+ return {
871
+ e: entry.key,
872
+ t: selected()?.key === entry.key
873
+ };
874
+ }, ({ e, t }, _p$) => {
875
+ _el$31.value = e;
876
+ _el$31.selected = t;
877
+ });
878
+ return _el$31;
879
+ })()
880
+ }));
881
+ effect(() => selected()?.key ?? "", (_v$) => {
882
+ queueMicrotask(() => {
883
+ return _el$28.value = _v$;
884
+ }) || (_el$28.value = _v$);
885
+ });
886
+ return _el$28;
887
+ })()];
888
+ }
889
+ }), _el$29);
890
+ insert(_el$20, createComponent(Show, {
891
+ get when() {
892
+ return selected();
893
+ },
894
+ get fallback() {
895
+ var _el$34 = _tmpl$12();
896
+ var _el$36 = _el$34.firstChild.nextSibling;
897
+ _el$36.nextSibling;
898
+ insert(_el$34, () => {
899
+ return relationship().carrier;
900
+ }, _el$36);
901
+ return _el$34;
902
+ },
903
+ children: (entry) => [
904
+ (() => {
905
+ var _el$38 = _tmpl$13();
906
+ var _el$39 = _el$38.firstChild;
907
+ var _el$40 = _el$39.firstChild;
908
+ var _el$42 = _el$40.nextSibling.firstChild;
909
+ var _el$43 = _el$42.nextSibling;
910
+ var _el$44 = _el$39.nextSibling;
911
+ var _el$45 = _el$44.firstChild;
912
+ var _el$47 = _el$45.nextSibling.firstChild;
913
+ var _el$48 = _el$47.nextSibling;
914
+ var _el$50 = _el$44.nextSibling.firstChild;
915
+ var _el$52 = _el$50.nextSibling.firstChild;
916
+ var _el$53 = _el$52.nextSibling;
917
+ insert(_el$43, () => {
918
+ return entry().name;
919
+ });
920
+ insert(_el$48, () => {
921
+ return relationship().condition;
922
+ });
923
+ insert(_el$53, () => {
924
+ return entry().recipientNames.join(", ") || "No target found";
925
+ });
926
+ effect(() => {
927
+ return {
928
+ e: `${p}-roles`,
929
+ t: `${p}-step`,
930
+ a: `${p}-muted`,
931
+ o: `${p}-step`,
932
+ i: `${p}-muted`,
933
+ n: `${p}-step`,
934
+ s: `${p}-muted`
935
+ };
936
+ }, ({ e, t, a, o, i, n, s }, _p$) => {
937
+ className(_el$38, e, _p$?.e);
938
+ className(_el$40, t, _p$?.t);
939
+ className(_el$42, a, _p$?.a);
940
+ className(_el$45, o, _p$?.o);
941
+ className(_el$47, i, _p$?.i);
942
+ className(_el$50, n, _p$?.n);
943
+ className(_el$52, s, _p$?.s);
944
+ });
945
+ return _el$38;
946
+ })(),
947
+ (() => {
948
+ var _el$54 = _tmpl$14();
949
+ insert(_el$54, () => {
950
+ return entry().explanation;
951
+ });
952
+ effect(() => {
953
+ return {
954
+ e: `${p}-verdict`,
955
+ t: entry().matches
956
+ };
957
+ }, ({ e, t }, _p$) => {
958
+ className(_el$54, e, _p$?.e);
959
+ t !== _p$?.t && setAttribute(_el$54, "data-matches", t);
960
+ });
961
+ return _el$54;
962
+ })(),
963
+ (() => {
964
+ var _el$55 = _tmpl$15();
965
+ effect(() => `${p}-hint`, (_v$, _$p) => {
966
+ className(_el$55, _v$, _$p);
967
+ });
968
+ return _el$55;
969
+ })(),
970
+ (() => {
971
+ var _el$56 = _tmpl$16();
972
+ var _el$57 = _el$56.firstChild;
973
+ _el$57._$$click = () => {
974
+ const id = entry().recipientId;
975
+ if (id) dd.select(id);
976
+ };
977
+ insert(_el$56, createComponent(LensControls, {
978
+ p,
979
+ drawing,
980
+ setDrawing,
981
+ reading
982
+ }), null);
983
+ effect(() => {
984
+ return {
985
+ e: `${p}-row`,
986
+ t: !entry().recipientId
987
+ };
988
+ }, ({ e, t }, _p$) => {
989
+ className(_el$56, e, _p$?.e);
990
+ t !== _p$?.t && setAttribute(_el$57, "disabled", t);
991
+ });
992
+ return _el$56;
993
+ })()
994
+ ]
995
+ }), _el$30);
996
+ effect(() => {
997
+ return {
998
+ e: `${p}-section`,
999
+ t: `${p}-heading`
1000
+ };
1001
+ }, ({ e, t }, _p$) => {
1002
+ className(_el$20, e, _p$?.e);
1003
+ className(_el$21, t, _p$?.t);
1004
+ });
1005
+ return _el$20;
1006
+ })()
1007
+ }), (() => {
1008
+ var _el$13 = _tmpl$6();
1009
+ var _el$14 = _el$13.firstChild;
1010
+ var _el$16 = _el$14.firstChild.nextSibling;
1011
+ var _el$17 = _el$14.nextSibling;
1012
+ var _el$18 = _el$17.nextSibling;
1013
+ insert(_el$16, () => {
1014
+ return rule().source;
1015
+ });
1016
+ insert(_el$17, () => {
1017
+ return rule().declarations;
1018
+ });
1019
+ effect(() => {
1020
+ return {
1021
+ e: `${p}-details`,
1022
+ t: `${p}-rule`,
1023
+ a: `${p}-muted`
1024
+ };
1025
+ }, ({ e, t, a }, _p$) => {
1026
+ className(_el$13, e, _p$?.e);
1027
+ className(_el$17, t, _p$?.t);
1028
+ className(_el$18, a, _p$?.a);
1029
+ });
1030
+ return _el$13;
1031
+ })()]
1032
+ })]
1033
+ });
1034
+ }
1035
+ delegateEvents(["click"]);
1036
+ var playground = `<!doctype html>
1037
+ <html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1"><title>Has lab playground</title>
1038
+ <style>
1039
+ * { box-sizing: border-box; }
1040
+ body { margin: 0; padding: 40px; background: #f5f4ef; color: #242b23; font: 16px/1.5 system-ui, sans-serif; }
1041
+ h1 { font-weight: 500; font-size: 32px; margin: 0 0 6px; }
1042
+ h2 { font-size: 20px; font-weight: 500; margin: 0 0 12px; }
1043
+ p { margin: 0; color: #60675b; }
1044
+ main { display: grid; gap: 32px; }
1045
+ header { margin-bottom: 32px; }
1046
+ .tickets { display: grid; grid-template-columns: 1fr 1fr; gap: 24px; }
1047
+ .ticket { display: grid; gap: 16px; padding: 24px; border: 1px solid #cdd2c5; border-radius: 8px; background: white; }
1048
+ .ticket header { display: flex; justify-content: space-between; align-items: center; margin: 0; }
1049
+ .letter { font: 64px Georgia, serif; }
1050
+ .ticket footer { border-top: 1px dashed #cdd2c5; padding-top: 12px; font-size: 13px; }
1051
+ input { accent-color: #38672b; }
1052
+ input[type=checkbox] { width: 20px; height: 20px; }
1053
+ .steps { display: flex; gap: 16px; }
1054
+ .step { flex: 1; min-width: 0; padding: 20px; border: 1px solid #cdd2c5; background: white; border-radius: 6px; }
1055
+ .signup { padding: 24px; border: 1px solid #cdd2c5; background: white; border-radius: 8px; }
1056
+ .signup label { display: grid; gap: 8px; }
1057
+ input[type=email] { font: inherit; padding: 10px; border: 1px solid #a2aa99; border-radius: 4px; }
1058
+ details { padding: 20px; background: white; border: 1px solid #cdd2c5; border-radius: 6px; }
1059
+ summary { cursor: pointer; }
1060
+ .ticket:has(input:checked) footer { background: #e4f1ce; color: #365829; border-color: #679b41; }
1061
+ .step:has(+ .step:hover) { background: #efe2c7; border-color: #a9864f; }
1062
+ .signup:has(input:invalid:not(:placeholder-shown)) button { background: #a34d3c; }
1063
+ .ticket footer { margin: 0 -12px -12px; padding: 12px; border-radius: 0 0 4px 4px; }
1064
+ .signup button { justify-self: start; margin-top: 12px; padding: 10px 16px; border: 0; border-radius: 4px; color: white; background: #38672b; font: inherit; }
1065
+ details p { margin-top: 12px; }
1066
+ @media(max-width:600px) { body { padding: 24px; } .tickets { grid-template-columns: 1fr; } .steps { flex-direction: column; } }
1067
+ </style></head><body>
1068
+ <header><h1>Small causes. Visible effects.</h1><p>A page to explore with the :has lab panel.</p></header>
1069
+ <main>
1070
+ <section class="tickets" aria-label="Tickets">
1071
+ <label class="ticket" id="field-ticket"><header><span>Field notes / 01</span><input type="checkbox" aria-label="Select field ticket"></header><span class="letter">Aa</span><h2>Type in the wild</h2><p>A walk through the city’s letterforms.</p><footer>Saturday · 10:00</footer></label>
1072
+ <label class="ticket" id="studio-ticket"><header><span>Studio visit / 02</span><input type="checkbox" checked aria-label="Select studio ticket"></header><span class="letter">&amp;</span><h2>Objects with stories</h2><p>Inside an independent design studio.</p><footer>Sunday · 14:00</footer></label>
1073
+ </section>
1074
+ <section><h2>A sibling can look ahead</h2><div class="steps"><div class="step" id="step-one">Choose a focus</div><div class="step" id="step-two">Make it tangible</div><div class="step" id="step-three">Keep a trail</div></div></section>
1075
+ <section class="signup" id="signup"><h2>A field can change its surroundings</h2><label>Your email<input type="email" required placeholder="you@example.com"></label><button type="button">Join updates</button></section>
1076
+ <section id="disclosure"><details><summary>What makes a good experiment?</summary><p>One clear question, and something you can change.</p></details></section>
1077
+ </main></body></html>`;
1078
+ var classPrefix = (id) => id.replace(/[^A-Za-z0-9_-]/g, "-");
1079
+ var errorMessage = (cause) => cause instanceof Error ? cause.message : String(cause);
1080
+ function operations(dd) {
1081
+ const [trial, setTrial] = createSignal(null);
1082
+ const [creating, setCreating] = createSignal(false);
1083
+ const [status, setStatus] = createSignal(null);
1084
+ const clearStatus = () => setStatus(null);
1085
+ const message = (text) => setStatus(text ? {
1086
+ kind: "message",
1087
+ text
1088
+ } : null);
1089
+ const error = (cause) => setStatus({
1090
+ kind: "error",
1091
+ text: errorMessage(cause)
1092
+ });
1093
+ let generation = 0;
1094
+ function revert() {
1095
+ const current = trial();
1096
+ setTrial(null);
1097
+ current?.transaction.cancel();
1098
+ message(current ? "Trial reverted." : "");
1099
+ }
1100
+ function keep() {
1101
+ const current = trial();
1102
+ if (!current) return;
1103
+ setTrial(null);
1104
+ current.transaction.commit();
1105
+ const id = dd.pageFind(current.target.viewportId, current.target.selector);
1106
+ if (id) dd.select(id);
1107
+ message("Rule kept. Undo with ⌘Z.");
1108
+ }
1109
+ function tryRule(target, selector, declarations) {
1110
+ if (trial() || creating() || !target.sheet) return;
1111
+ clearStatus();
1112
+ const transaction = dd.beginItemTransaction({ onInterrupted: () => {
1113
+ setTrial(null);
1114
+ message("The trial ended after another edit. Use Daydream’s undo to review it.");
1115
+ } });
1116
+ setTrial({
1117
+ transaction,
1118
+ target: {
1119
+ viewportId: target.viewportId,
1120
+ selector: target.selector,
1121
+ elementId: target.elementId
1122
+ }
1123
+ });
1124
+ try {
1125
+ const result = transaction.write({
1126
+ kind: "add",
1127
+ viewportId: target.viewportId,
1128
+ sheet: target.sheet,
1129
+ selector,
1130
+ declarations
1131
+ });
1132
+ if (typeof result === "string") throw new Error(result);
1133
+ message("Trial is live. Move over the page, hold ⌥, then interact.");
1134
+ } catch (cause) {
1135
+ setTrial(null);
1136
+ transaction.cancel();
1137
+ error(cause);
1138
+ }
1139
+ }
1140
+ async function addPlayground() {
1141
+ if (creating() || trial()) return;
1142
+ setCreating(true);
1143
+ clearStatus();
1144
+ const started = generation;
1145
+ try {
1146
+ const made = await dd.createPage(playground, { frame: { width: 820 } });
1147
+ if (started !== generation) return;
1148
+ dd.select(made.viewportId);
1149
+ dd.canvas.reveal([made.viewportId]);
1150
+ message(`Added ${made.path}. Explore its rules or select an element to create one.`);
1151
+ } catch (cause) {
1152
+ if (started === generation) error(cause);
1153
+ } finally {
1154
+ if (started === generation) setCreating(false);
1155
+ }
1156
+ }
1157
+ const leave = () => {
1158
+ generation++;
1159
+ revert();
1160
+ setCreating(false);
1161
+ };
1162
+ dd.on("leave", () => untrack(leave));
1163
+ onCleanup(() => leave());
1164
+ return {
1165
+ trial,
1166
+ creating,
1167
+ status,
1168
+ clearStatus,
1169
+ revert,
1170
+ keep,
1171
+ tryRule,
1172
+ addPlayground
1173
+ };
1174
+ }
1175
+ function pageEvents(dd) {
1176
+ const [version, setVersion] = createSignal(0);
1177
+ let root = null;
1178
+ let frame = 0;
1179
+ const changed = () => setVersion((value) => value + 1);
1180
+ const reset = () => {
1181
+ cancelAnimationFrame(frame);
1182
+ frame = requestAnimationFrame(changed);
1183
+ };
1184
+ function connect(next) {
1185
+ if (next === root) return;
1186
+ cancelAnimationFrame(frame);
1187
+ for (const name of [
1188
+ "input",
1189
+ "change",
1190
+ "toggle"
1191
+ ]) {
1192
+ root?.removeEventListener(name, changed, true);
1193
+ next?.addEventListener(name, changed, true);
1194
+ }
1195
+ root?.removeEventListener("reset", reset, true);
1196
+ next?.addEventListener("reset", reset, true);
1197
+ root = next;
1198
+ }
1199
+ onCleanup(() => connect(null));
1200
+ const hook = dd.on("leave", () => connect(null));
1201
+ onCleanup(() => hook.dispose());
1202
+ return {
1203
+ version,
1204
+ connect
1205
+ };
1206
+ }
1207
+ function content(root) {
1208
+ const out = [];
1209
+ const visit = (node) => {
1210
+ if ([
1211
+ "head",
1212
+ "style",
1213
+ "script",
1214
+ "link",
1215
+ "meta",
1216
+ "title",
1217
+ "base"
1218
+ ].includes(node.localName)) return;
1219
+ out.push(node);
1220
+ if (node.localName !== "noscript") for (const child of node.children) visit(child);
1221
+ };
1222
+ visit(root);
1223
+ return out;
1224
+ }
1225
+ function storedIds(dd, viewport, stored, mounted) {
1226
+ const root = mounted.querySelector("html");
1227
+ const ours = content(stored.documentElement), theirs = root ? content(root) : [];
1228
+ const paired = ours.length === theirs.length && ours.every((node, i) => node.localName === theirs[i].localName);
1229
+ const twins = new Map(paired ? theirs.map((node, i) => [node, ours[i]]) : []);
1230
+ const ids = new Map();
1231
+ return (node) => {
1232
+ if (ids.has(node)) return ids.get(node);
1233
+ const twin = twins.get(node);
1234
+ const selector = dd.core.uniqueSelector(twin ?? node, twin ? stored : mounted);
1235
+ const id = dd.pageFind(viewport, selector);
1236
+ const resolved = id && dd.geometry.node(id) === node ? id : null;
1237
+ ids.set(node, resolved);
1238
+ return resolved;
1239
+ };
1240
+ }
1241
+ function targetOf(dd, selection) {
1242
+ const empty = {
1243
+ target: null,
1244
+ reason: "Select a page element to see its :has() relationships.",
1245
+ nodeId: selection,
1246
+ node: selection ? dd.geometry.node(selection) : void 0,
1247
+ pending: false
1248
+ };
1249
+ if (!selection) return empty;
1250
+ const selected = dd.pageElement(selection);
1251
+ const item = dd.core.viewportItems(dd.document()).find((item) => item.id === (selected?.viewportId ?? selection));
1252
+ if (!item) return empty;
1253
+ const id = selected ? selection : dd.pageFind(item.id, "body");
1254
+ const node = id ? dd.geometry.node(id) : void 0;
1255
+ const unavailable = {
1256
+ ...empty,
1257
+ nodeId: id,
1258
+ node,
1259
+ pending: !id,
1260
+ reason: "This viewport does not expose rendered elements to the lens. Select an element in a simulated viewport."
1261
+ };
1262
+ if (!id || !node) return unavailable;
1263
+ const info = selected ?? dd.pageElement(id);
1264
+ if (!info?.selector) return unavailable;
1265
+ const stack = dd.pageStack(id);
1266
+ const page = dd.page(item.payload.page);
1267
+ if (!stack || !page) return {
1268
+ ...unavailable,
1269
+ reason: "The selected page is not available yet."
1270
+ };
1271
+ const recipes = [];
1272
+ const offer = (source, recipe) => {
1273
+ if (node.querySelector(source)) recipes.push(recipe);
1274
+ };
1275
+ offer("input[type=\"checkbox\"], input[type=\"radio\"]", {
1276
+ id: "checked",
1277
+ title: "An input is checked",
1278
+ relative: "input:checked"
1279
+ });
1280
+ offer("input, select, textarea, button, a[href]", {
1281
+ id: "focus",
1282
+ title: "A control has keyboard focus",
1283
+ relative: ":focus-visible"
1284
+ });
1285
+ offer("input[required], input[type=\"email\"], input[type=\"url\"], input[pattern], input[min], input[max], select[required], textarea[required]", {
1286
+ id: "invalid",
1287
+ title: "A field is invalid",
1288
+ relative: ":invalid"
1289
+ });
1290
+ offer("img", {
1291
+ id: "image",
1292
+ title: "Contains an image",
1293
+ relative: "img"
1294
+ });
1295
+ offer("details", {
1296
+ id: "open",
1297
+ title: "A disclosure is open",
1298
+ relative: "details[open]"
1299
+ });
1300
+ offer(":scope > *", {
1301
+ id: "hover",
1302
+ title: "A direct child is hovered",
1303
+ relative: "> :hover"
1304
+ });
1305
+ offer(":scope > :nth-child(3)", {
1306
+ id: "three",
1307
+ title: "Has at least three children",
1308
+ relative: "> :nth-child(3)"
1309
+ });
1310
+ if (node.nextElementSibling) recipes.push({
1311
+ id: "sibling",
1312
+ title: "The next sibling is hovered",
1313
+ relative: "+ :hover"
1314
+ });
1315
+ const className = [...node.classList].find((name) => !name.startsWith("dream-"));
1316
+ const stored = dd.core.parsePage(page.html);
1317
+ return {
1318
+ target: {
1319
+ viewportId: info.viewportId,
1320
+ elementId: id,
1321
+ selector: info.selector,
1322
+ group: className ? `.${CSS.escape(className)}` : null,
1323
+ page: item.payload.page,
1324
+ markup: page.html,
1325
+ recipes,
1326
+ sheet: [...stack.sheets].reverse().find((sheet) => sheet.editable && (sheet.absent || unconditional(sheet.source, page, stored)))?.source ?? null,
1327
+ sources: stack.sheets.map((sheet) => sheet.source),
1328
+ idOf: storedIds(dd, info.viewportId, stored, node.getRootNode())
1329
+ },
1330
+ reason: "",
1331
+ nodeId: id,
1332
+ node,
1333
+ pending: false
1334
+ };
1335
+ }
1336
+ function unconditional(source, page, stored) {
1337
+ const sheet = page.sheets.find((entry) => sourceKey(entry.source) === sourceKey(source));
1338
+ if (!sheet) return false;
1339
+ const base = new URL(page.path, "https://project.invalid/");
1340
+ return [...stored.querySelectorAll("link, style")].filter((node) => {
1341
+ if (node.localName === "style") return "style" in source && node.textContent === sheet.text;
1342
+ if (!("file" in source)) return false;
1343
+ try {
1344
+ return new URL(node.getAttribute("href") ?? "", base).href === new URL(source.file, "https://project.invalid/").href;
1345
+ } catch {
1346
+ return false;
1347
+ }
1348
+ }).some((node) => !node.getAttribute("media")?.trim() || node.getAttribute("media")?.trim() === "all");
1349
+ }
1350
+ function trialSelection(dd, selected, trial, previous) {
1351
+ if (!trial) return selected;
1352
+ const pinned = trial.target;
1353
+ const id = previous?.viewportId === pinned.viewportId && previous.selector === pinned.selector ? previous.elementId : pinned.elementId;
1354
+ return dd.geometry.node(id) ? id : dd.pageFind(pinned.viewportId, pinned.selector);
1355
+ }
1356
+ function panelSource(dd, context, ops) {
1357
+ const [result, setResult] = createSignal(null);
1358
+ const events = pageEvents(dd);
1359
+ let last = null;
1360
+ createEffect(() => ({
1361
+ selection: context.selection(),
1362
+ version: dd.documentVersion(),
1363
+ load: dd.loadVersion(),
1364
+ geometry: dd.geometry.version(),
1365
+ trial: ops.trial()
1366
+ }), (state) => untrack(() => {
1367
+ const key = state.trial ? `${state.trial.target.viewportId}:${state.trial.target.selector}` : state.selection;
1368
+ const node = last?.result.nodeId ? dd.geometry.node(last.result.nodeId) : void 0;
1369
+ if (last && !last.result.pending && last.key === key && last.version === state.version && last.load === state.load && last.result.node === node) return;
1370
+ const next = targetOf(dd, trialSelection(dd, state.selection, state.trial, result()?.target ?? null));
1371
+ last = {
1372
+ key,
1373
+ version: state.version,
1374
+ load: state.load,
1375
+ result: next
1376
+ };
1377
+ events.connect(next.target ? next.node?.getRootNode() ?? null : null);
1378
+ setResult(next);
1379
+ }));
1380
+ return {
1381
+ target: () => result()?.target ?? null,
1382
+ reason: () => result()?.reason ?? "Select a page element.",
1383
+ input: events.version
1384
+ };
1385
+ }
1386
+ var roles = (selector) => `
1387
+ ${selector}[data-role=carrier] { border:1.5px solid var(--dream-app-sage, #8ea99b); }
1388
+ ${selector}[data-role=condition] { border:1.5px solid var(--dream-app-amber, #c5a16b); background:color-mix(in srgb, var(--dream-app-amber, #c5a16b) 10%, transparent); }
1389
+ ${selector}[data-role=recipient] { border:1.5px dashed var(--dream-app-sage, #8ea99b); }
1390
+ `;
1391
+ var panelStyles = (p) => `
1392
+ .${p}-panel { display:flex; flex-direction:column; gap:16px; padding:8px 14px 14px; min-width:0; color:var(--dream-app-text, #b8b8be); font-size:11px; line-height:1.5; --lens-line:var(--dream-app-line, #34343b); }
1393
+ .${p}-panel p { margin:0; }
1394
+ .${p}-panel label { display:flex; flex-direction:column; gap:5px; }
1395
+ .${p}-panel .${p}-check { flex-direction:row; align-items:center; }
1396
+ .${p}-panel input[type=checkbox] { margin:0; }
1397
+ .${p}-panel button, .${p}-panel select { font:inherit; padding:6px 8px; border:1px solid var(--dream-app-line, #414149); border-radius:4px; background:var(--dream-app-field, #26262b); color:var(--dream-app-bright, #c8c8ce); }
1398
+ .${p}-panel button:disabled { opacity:.45; }
1399
+ .${p}-panel .${p}-primary { background:var(--dream-app-success-surface, #34452e); border-color:var(--dream-app-success, #6e8d54); }
1400
+ .${p}-panel textarea { width:100%; resize:vertical; min-height:76px; padding:8px; font-size:11px; line-height:1.5; font-family:var(--dream-app-mono, monospace); border:1px solid var(--dream-app-line, #414149); border-radius:4px; background:var(--dream-app-field, #26262b); color:var(--dream-app-bright, #c8c8ce); }
1401
+ .${p}-row { display:flex; align-items:center; gap:8px; flex-wrap:wrap; }
1402
+ .${p}-row > code { flex:1; }
1403
+ .${p}-panel code { font-size:11px; line-height:1.5; font-family:var(--dream-app-mono, monospace); overflow-wrap:anywhere; }
1404
+ .${p}-rule { white-space:pre-wrap; overflow-wrap:anywhere; margin:0; padding:8px; background:var(--dream-app-line, #232328); border-radius:4px; font-size:11px; line-height:1.5; font-family:var(--dream-app-mono, monospace); color:var(--dream-app-bright, #c8c8ce); }
1405
+ .${p}-muted { color:var(--dream-app-dim, #9a9aa0); }
1406
+ .${p}-status { color:var(--dream-app-success, #a2c987); }
1407
+ .${p}-error { color:var(--dream-app-danger, #ed9d89); }
1408
+ .${p}-legend { display:flex; flex-wrap:wrap; gap:12px; font-size:10px; }
1409
+ .${p}-legend span { display:flex; gap:4px; align-items:center; }
1410
+ .${p}-panel .${p}-panel { padding:0; }
1411
+ .${p}-tabs { display:flex; gap:4px; border-bottom:1px solid var(--lens-line); padding-bottom:6px; }
1412
+ .${p}-tabs button { flex:1; border:0; background:transparent; padding:6px 4px; color:var(--dream-app-dim, #9a9aa0); }
1413
+ .${p}-tabs button[aria-pressed=true] { color:var(--dream-app-bright, #e0e4df); background:var(--dream-app-hover, #27292a); }
1414
+ .${p}-section { display:grid; gap:10px; }
1415
+ .${p}-heading { display:flex; justify-content:space-between; gap:12px; color:var(--dream-app-bright, #c8c8ce); }
1416
+ .${p}-heading > span:last-child { color:var(--dream-app-text, #a7b4ab); font-variant-numeric:tabular-nums; }
1417
+ .${p}-path { overflow-wrap:anywhere; }
1418
+ .${p}-rules { display:grid; gap:2px; }
1419
+ .${p}-panel .${p}-rule-row { display:grid; grid-template-columns:8px minmax(0,1fr); gap:8px; text-align:left; padding:8px 6px; border:0; background:transparent; align-items:baseline; }
1420
+ .${p}-rule-row > span { color:var(--dream-app-muted, #87938c); font-size:9px; }
1421
+ .${p}-panel .${p}-rule-row:hover { background:var(--dream-app-hover, #222225); }
1422
+ .${p}-panel .${p}-rule-row[aria-pressed=true] { background:var(--dream-app-selection, #292e2c); color:var(--dream-app-bright, #d3e6dc); }
1423
+ .${p}-roles { margin:0; padding:0; list-style:none; display:grid; gap:10px; }
1424
+ .${p}-roles li { display:flex; align-items:start; gap:10px; }
1425
+ .${p}-roles li > div { display:grid; gap:2px; min-width:0; }
1426
+ .${p}-step { display:grid; place-items:center; width:18px; height:18px; flex:none; border:1px solid var(--dream-app-line-strong, #4a5951); color:var(--dream-app-text, #adc5b8); border-radius:50%; font-size:10px; }
1427
+ .${p}-verdict { border-left:2px solid var(--dream-app-muted, #74767a); padding:6px 0 6px 10px; color:var(--dream-app-bright, #c8c8ce); }
1428
+ .${p}-verdict[data-matches=true] { border-color:var(--dream-app-sage, #9dc3ae); color:var(--dream-app-bright, #c4dfcf); }
1429
+ .${p}-hint { color:var(--dream-app-dim, #9a9aa0); }
1430
+ .${p}-details { border-top:1px solid var(--lens-line); padding-top:10px; }
1431
+ .${p}-details summary { cursor:pointer; color:var(--dream-app-text, #b8b8be); }
1432
+ .${p}-details summary > span { display:block; font-size:10px; color:var(--dream-app-dim, #9a9aa0); overflow-wrap:anywhere; margin:4px 0 8px; }
1433
+ .${p}-empty { display:grid; gap:12px; }
1434
+ .${p}-sr-only { position:absolute; width:1px; height:1px; overflow:hidden; clip-path:inset(50%); }
1435
+ .${p}-panel button:focus-visible, .${p}-panel select:focus-visible, .${p}-panel textarea:focus-visible, .${p}-panel summary:focus-visible { outline:2px solid var(--dream-app-accent, #8bb7e0); outline-offset:2px; }
1436
+ .${p}-lens-controls { display:grid; gap:8px; width:100%; }
1437
+ .${p}-legend i { width:9px; height:9px; box-sizing:border-box; }
1438
+ ${roles(`.${p}-legend i`)}
1439
+ `;
1440
+ var overlayStyles = (p) => `
1441
+ .${p}-box { position:absolute; box-sizing:border-box; border:1px dashed var(--dream-app-muted, #9b9e95); pointer-events:none; }
1442
+ .${p}-wires { position:absolute; width:1px; height:1px; overflow:visible; pointer-events:none; }
1443
+ .${p}-wires path { fill:none; stroke:var(--dream-app-sage, #b2c7bd); stroke-width:1; opacity:.8; }
1444
+ .${p}-box > span { position:absolute; top:0; left:-1px; transform:translateY(-100%); white-space:nowrap; background:var(--dream-app-selection, #26372e); color:var(--dream-app-bright, #d5e7dd); border-radius:2px 2px 0 0; padding:1px 5px; font-size:10px; line-height:16px; }
1445
+ .${p}-box[data-role=condition] > span { background:var(--dream-app-raised, #473b28); color:var(--dream-app-bright, #f1dfbd); }
1446
+ ${roles(`.${p}-box`)}
1447
+ `;
1448
+ var _tmpl$ = template(`<div><div aria-label=":has lab mode"><button type=button>Explore rules</button><button type=button>Create a rule</button></div><!><button type=button>`);
1449
+ var _tmpl$2 = template(`<p>`);
1450
+ function LabPanel(props) {
1451
+ const { dd, p, ops, context, publish } = untrack(() => props);
1452
+ const [mode, setMode] = createSignal("inspect");
1453
+ const source = panelSource(dd, context, ops);
1454
+ var _el$ = _tmpl$();
1455
+ var _el$2 = _el$.firstChild;
1456
+ var _el$3 = _el$2.firstChild;
1457
+ var _el$4 = _el$3.nextSibling;
1458
+ var _el$5 = _el$2.nextSibling;
1459
+ var _el$6 = _el$5.nextSibling;
1460
+ _el$3._$$click = () => setMode("inspect");
1461
+ _el$4._$$click = () => setMode("create");
1462
+ insert(_el$, createComponent(Show, {
1463
+ get when() {
1464
+ return mode() === "inspect";
1465
+ },
1466
+ get fallback() {
1467
+ return createComponent(CreatePanel, {
1468
+ dd,
1469
+ p,
1470
+ source,
1471
+ ops,
1472
+ publish
1473
+ });
1474
+ },
1475
+ get children() {
1476
+ return createComponent(ExplorePanel, {
1477
+ dd,
1478
+ p,
1479
+ source,
1480
+ publish
1481
+ });
1482
+ }
1483
+ }), _el$5);
1484
+ insert(_el$, createComponent(Show, {
1485
+ get when() {
1486
+ return ops.status();
1487
+ },
1488
+ children: (status) => (() => {
1489
+ var _el$7 = _tmpl$2();
1490
+ insert(_el$7, () => {
1491
+ return status().text;
1492
+ });
1493
+ effect(() => {
1494
+ return {
1495
+ e: `${p}-${status().kind === "error" ? "error" : "status"}`,
1496
+ t: status().kind === "error" ? "alert" : "status"
1497
+ };
1498
+ }, ({ e, t }, _p$) => {
1499
+ className(_el$7, e, _p$?.e);
1500
+ t !== _p$?.t && setAttribute(_el$7, "role", t);
1501
+ });
1502
+ return _el$7;
1503
+ })()
1504
+ }), _el$6);
1505
+ _el$6._$$click = () => void ops.addPlayground();
1506
+ insert(_el$6, () => {
1507
+ return ops.creating() ? "Adding playground…" : "Add playground page";
1508
+ });
1509
+ effect(() => {
1510
+ return {
1511
+ e: `${p}-panel`,
1512
+ t: `${p}-tabs`,
1513
+ a: !!ops.trial(),
1514
+ o: mode() === "inspect" ? "true" : "false",
1515
+ i: !!ops.trial(),
1516
+ n: mode() === "create" ? "true" : "false",
1517
+ s: ops.creating() || !!ops.trial()
1518
+ };
1519
+ }, ({ e, t, a, o, i, n, s }, _p$) => {
1520
+ className(_el$, e, _p$?.e);
1521
+ className(_el$2, t, _p$?.t);
1522
+ a !== _p$?.a && setAttribute(_el$3, "disabled", a);
1523
+ o !== _p$?.o && setAttribute(_el$3, "aria-pressed", o);
1524
+ i !== _p$?.i && setAttribute(_el$4, "disabled", i);
1525
+ n !== _p$?.n && setAttribute(_el$4, "aria-pressed", n);
1526
+ s !== _p$?.s && setAttribute(_el$6, "disabled", s);
1527
+ });
1528
+ return _el$;
1529
+ }
1530
+ function activate(dd) {
1531
+ const p = classPrefix(dd.plugin.id);
1532
+ const ops = operations(dd);
1533
+ dd.registerCommand({
1534
+ id: `${dd.plugin.id}.playground`,
1535
+ title: "Add :has playground",
1536
+ scope: "canvas",
1537
+ run: () => {
1538
+ ops.addPlayground();
1539
+ }
1540
+ });
1541
+ const [marks, setMarks] = createSignal([]);
1542
+ dd.registerPanel({
1543
+ id: "relationships",
1544
+ title: ":has lab",
1545
+ ariaLabel: ":has relationship lab",
1546
+ lands: "right",
1547
+ grow: 1,
1548
+ styles: panelStyles(p),
1549
+ render: (context) => createComponent(LabPanel, {
1550
+ dd,
1551
+ context,
1552
+ p,
1553
+ ops,
1554
+ publish: setMarks
1555
+ })
1556
+ });
1557
+ dd.registerOverlay({
1558
+ id: "relationships",
1559
+ slot: "overlay.screen",
1560
+ styles: overlayStyles(p),
1561
+ render: () => createComponent(RelationshipOverlay, {
1562
+ p,
1563
+ marks
1564
+ })
1565
+ });
1566
+ }
1567
+ delegateEvents(["click"]);
1568
+ export { activate as default };