@12ui/design 0.2.51 → 0.2.52
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/SKILL.md +1 -1
- package/dist/branch-client.d.ts +5 -0
- package/dist/branch-client.d.ts.map +1 -1
- package/dist/branch-client.js.map +1 -1
- package/dist/branch-completion.d.ts.map +1 -1
- package/dist/branch-completion.js +7 -0
- package/dist/branch-completion.js.map +1 -1
- package/dist/branch-identity.d.ts +25 -0
- package/dist/branch-identity.d.ts.map +1 -1
- package/dist/branch-identity.js +28 -15
- package/dist/branch-identity.js.map +1 -1
- package/dist/branch-page-conversion.d.ts.map +1 -1
- package/dist/branch-page-conversion.js +49 -4
- package/dist/branch-page-conversion.js.map +1 -1
- package/dist/branch-plan.d.ts +4 -1
- package/dist/branch-plan.d.ts.map +1 -1
- package/dist/branch-plan.js +7 -0
- package/dist/branch-plan.js.map +1 -1
- package/dist/branch-run-record.d.ts +4 -0
- package/dist/branch-run-record.d.ts.map +1 -1
- package/dist/branch-run-record.js.map +1 -1
- package/dist/package-client.d.ts +11 -0
- package/dist/package-client.d.ts.map +1 -1
- package/dist/package-client.js.map +1 -1
- package/dist/package-submission.d.ts +13 -2
- package/dist/package-submission.d.ts.map +1 -1
- package/dist/package-submission.js +34 -1
- package/dist/package-submission.js.map +1 -1
- package/dist/prototype-retention.d.ts +10 -0
- package/dist/prototype-retention.d.ts.map +1 -0
- package/dist/prototype-retention.js +14 -0
- package/dist/prototype-retention.js.map +1 -0
- package/dist/prototype-revision.d.ts +1 -1
- package/dist/prototype-revision.js +1 -1
- package/dist/prototype-runner.d.ts.map +1 -1
- package/dist/prototype-runner.js +39 -15
- package/dist/prototype-runner.js.map +1 -1
- package/dist/review-server.d.ts.map +1 -1
- package/dist/review-server.js +10 -1
- package/dist/review-server.js.map +1 -1
- package/open-design.json +2 -2
- package/package.json +1 -1
- package/prototype-kit/build.mjs +54 -9
- package/prototype-kit/lib/action-target.mjs +34 -8
- package/prototype-kit/lib/runtime-check.mjs +31 -12
- package/prototype-kit/polish/ground-probe.mjs +4 -1
- package/prototype-kit/polish/measure.mjs +6 -6
- package/prototype-kit/polish/rules/section-breathing.mjs +461 -237
- package/prototype-kit/polish/tests/breathing-check.mjs +75 -27
- package/prototype-kit/polish/tests/fixtures/section-breathing/diagram-silent.html +20 -0
- package/prototype-kit/polish/tests/fixtures/section-breathing/fire.html +2 -2
- package/prototype-kit/polish/tests/fixtures/section-breathing/floor.html +3 -3
- package/prototype-kit/polish/tests/fixtures/section-breathing/no-scale-silent.html +18 -0
- package/prototype-kit/polish/tests/fixtures/section-breathing/rhythm-nested.html +21 -0
- package/prototype-kit/polish/tests/fixtures/section-breathing/semantic-fire.html +10 -4
- package/prototype-kit/polish/tests/fixtures/section-breathing/semantic-silent.html +2 -2
- package/prototype-kit/polish/tests/fixtures/section-breathing/silent.html +2 -2
- package/prototype-kit/polish/tests/fixtures/section-breathing/tight.html +4 -4
- package/prototype-kit/runtime/proto.js +52 -2
- package/prototype-kit/tests/adversarial-runtime.mjs +179 -4
- package/prototype-kit/tests/fixtures/blocking-runtime/broken-interaction.html +1 -0
- package/prototype-kit/tests/fixtures/interaction-runtime/PROVENANCE.md +5 -0
- package/prototype-kit/tests/fixtures/interaction-runtime/inert-field.html +23 -0
- package/prototype-kit/tests/fixtures/interaction-runtime/pointer-cascade.html +19 -0
- package/prototype-kit/tests/fixtures/multiple-trigger-runtime/probe-all.html +14 -0
- package/prototype-kit/tests/fixtures/nested-inert/branch.plan.json +5 -0
- package/prototype-kit/tests/fixtures/nested-inert/pages/p0.html +17 -0
- package/prototype-kit/tests/offline-check.mjs +6 -1
- package/prototype-kit/tests/rules-check.mjs +28 -9
- package/prototype-kit/verify-runtime.mjs +10 -5
- package/prototype-kit/polish/tests/fixtures/section-breathing/attachment-fire.html +0 -5
- package/prototype-kit/polish/tests/fixtures/section-breathing/attachment-silent.html +0 -5
- package/prototype-kit/polish/tests/fixtures/section-breathing/shadow-fire.html +0 -5
- package/prototype-kit/polish/tests/fixtures/section-breathing/shadow-silent.html +0 -5
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
// section-breathing: floor + background-integrity pairs.
|
|
2
|
+
// section-breathing: declared floor + background-integrity pairs.
|
|
3
3
|
// floor three solid sections, gaps ~42 / ~102 → target ceil8(median+32) = 104: seam 1 gets +62, seam 2 (+2) is DECLINED
|
|
4
4
|
// probe on the fire fixture the pixel probe accepts the accepted seam (9/9 probes in the owner's colour, document
|
|
5
5
|
// grew by exactly the addition); with a deliberately wrong owner colour it must fail with 'coverage'
|
|
6
|
+
// rhythm a semantic seam's amount is the page's own scale T = min(largest, 2 x median) structural gap, measured
|
|
7
|
+
// from the page's real air line; nested seams ride the enclosing painter; drawings are never opened
|
|
6
8
|
import path from 'node:path';
|
|
9
|
+
import fs from 'node:fs';
|
|
10
|
+
import crypto from 'node:crypto';
|
|
7
11
|
import { chromium } from '@playwright/test';
|
|
8
12
|
import * as rule from '../rules/section-breathing.mjs';
|
|
9
13
|
import { applyOverridesScript, overridesToCss, mergeOverrides } from '../overrides.mjs';
|
|
@@ -35,43 +39,87 @@ check('probe accepts a solid seam (9 probes, height +32)', good.ok && good.probe
|
|
|
35
39
|
const bad = await probeSeams(page, seamMeta(fire).map((s) => ({ ...s, color: 'rgb(0, 90, 200)' })), base);
|
|
36
40
|
check('probe rejects a seam whose pixels are not the declared ground', !bad.ok && bad.failures.every((f) => f.kind === 'coverage' || f.kind === 'floor-exposure') && bad.failures.length === 9, JSON.stringify({ ok: bad.ok, n: bad.failures.length, kinds: [...new Set(bad.failures.map((f) => f.kind))] }));
|
|
37
41
|
|
|
38
|
-
//
|
|
39
|
-
|
|
40
|
-
const attachmentBase = await page.evaluate(() => document.documentElement.scrollHeight);
|
|
41
|
-
const attachmentFire = await page.evaluate(rule.browserFn(), { metrics: {}, params: rule.params });
|
|
42
|
-
const attachmentMove = attachmentFire.find((o) => o.id === 'viewport-1-a-fruit');
|
|
43
|
-
await page.evaluate(applyOverridesScript(), overridesToCss(mergeOverrides(attachmentFire)));
|
|
44
|
-
const attachmentProbe = await probeSeams(page, seamMeta(attachmentFire), attachmentBase);
|
|
45
|
-
check('boundary-hugging decoration travels and keeps its seam attachment', attachmentMove?.dy === 32 && seamMeta(attachmentFire)[0]?.attachments?.[0]?.id === 'viewport-1-a-fruit' && attachmentProbe.ok, JSON.stringify({ move: attachmentMove, ground: attachmentProbe }));
|
|
46
|
-
await page.goto('file://' + path.join(here, 'fixtures/section-breathing/attachment-silent.html'), { waitUntil: 'load' });
|
|
47
|
-
const attachmentSilent = await page.evaluate(rule.browserFn(), { metrics: {}, params: rule.params });
|
|
48
|
-
check('ordinary boundary image is not reclassified as decoration', !attachmentSilent.some((o) => o.id === 'viewport-1-a-product-image'), JSON.stringify(attachmentSilent.filter((o) => o.id === 'viewport-1-a-product-image')));
|
|
49
|
-
|
|
50
|
-
// semantic pair: direct declared FAQ/footer boundaries breathe; a dense dashboard boundary stays silent
|
|
42
|
+
// semantic pair: model-declared FAQ/footer boundaries breathe; a model-declared
|
|
43
|
+
// content-bearing dashboard boundary refuses and the fixture bytes stay exact.
|
|
51
44
|
await page.goto('file://' + path.join(here, 'fixtures/section-breathing/semantic-fire.html'), { waitUntil: 'load' });
|
|
52
45
|
const semanticBase = await page.evaluate(() => document.documentElement.scrollHeight);
|
|
53
46
|
const semantic = await page.evaluate(rule.browserFn(), { metrics: {}, params: rule.params });
|
|
47
|
+
const semanticCensus = await page.evaluate(rule.browserFn(), { metrics: {}, params: { ...rule.params, census: true } });
|
|
54
48
|
const semanticMerged = mergeOverrides(semantic);
|
|
55
49
|
await page.evaluate(applyOverridesScript(), overridesToCss(semanticMerged));
|
|
56
50
|
const semanticProbe = await probeSeams(page, seamMeta(semantic), semanticBase);
|
|
57
51
|
const faq = semanticMerged.find((o) => o.id === 'viewport-1-a-faq');
|
|
58
52
|
const footer = semanticMerged.find((o) => o.id === 'viewport-1-a-footer');
|
|
59
53
|
const section = semanticMerged.find((o) => o.id === 'viewport-1-a');
|
|
60
|
-
|
|
61
|
-
|
|
54
|
+
// page scale: structural gaps 40 (comparison) and 24 (faq) → median 32, largest 40, T = min(40, 64) = 40.
|
|
55
|
+
// Both seams sit at 16 px, so each is brought up to 40 (+24) and never past it.
|
|
56
|
+
check('declared FAQ and footer boundaries each rise to the 40 px page scale',
|
|
57
|
+
faq?.dy === 24 && footer?.dy === 48 && section?.h === 1072
|
|
58
|
+
&& semanticCensus.every((row) => row.pageScale?.target === 40 && row.gap === 16 && row.addition === 24)
|
|
59
|
+
&& faq?.meta?.seam?.beforePeers?.includes('viewport-1-a-comparison-aside') && semanticProbe.ok,
|
|
60
|
+
JSON.stringify({ faq, footer, section, scale: semanticCensus.map((r) => r.pageScale), ground: semanticProbe }));
|
|
61
|
+
const silentFile = path.join(here, 'fixtures/section-breathing/semantic-silent.html');
|
|
62
|
+
const silentBefore = fs.readFileSync(silentFile);
|
|
63
|
+
await page.goto('file://' + silentFile, { waitUntil: 'load' });
|
|
62
64
|
const semanticSilent = await page.evaluate(rule.browserFn(), { metrics: {}, params: rule.params });
|
|
63
|
-
|
|
65
|
+
const silentCensus = await page.evaluate(rule.browserFn(), { metrics: {}, params: { ...rule.params, census: true } });
|
|
66
|
+
const silentAfter = fs.readFileSync(silentFile);
|
|
67
|
+
const sha = (bytes) => crypto.createHash('sha256').update(bytes).digest('hex');
|
|
68
|
+
check('declared content-bearing boundary stays byte-identical', semanticSilent.length === 0
|
|
69
|
+
&& silentCensus[0]?.declined === 'declared-not-openable'
|
|
70
|
+
&& sha(silentBefore) === sha(silentAfter), JSON.stringify({ census: silentCensus, sha256: sha(silentAfter) }));
|
|
71
|
+
|
|
72
|
+
// rhythm model: a nested seam, measured from the page's real air line, painted by the enclosing panel.
|
|
73
|
+
// Pairwise the declared intro→details pair reads 200 px of air and would propose nothing; the page
|
|
74
|
+
// actually shows 40 px because the floating note card outlives intro inside the seam.
|
|
75
|
+
await page.goto('file://' + path.join(here, 'fixtures/section-breathing/rhythm-nested.html'), { waitUntil: 'load' });
|
|
76
|
+
const nestedBase = await page.evaluate(() => document.documentElement.scrollHeight);
|
|
77
|
+
const nestedCensus = await page.evaluate(rule.browserFn(), { metrics: {}, params: { ...rule.params, census: true } });
|
|
78
|
+
const nestedRow = nestedCensus[0];
|
|
79
|
+
const nested = await page.evaluate(rule.browserFn(), { metrics: {}, params: rule.params });
|
|
80
|
+
const nestedMerged = mergeOverrides(nested);
|
|
81
|
+
const pairwiseGap = await page.evaluate(() => (
|
|
82
|
+
document.getElementById('viewport-1-a-panel-details').getBoundingClientRect().top
|
|
83
|
+
- document.getElementById('viewport-1-a-panel-intro').getBoundingClientRect().bottom));
|
|
84
|
+
check('nested seam measures the real air line, not the declared pair',
|
|
85
|
+
nestedRow?.gap === 40 && pairwiseGap === 200 && nestedRow?.airAnchor === 'viewport-1-a-panel-note'
|
|
86
|
+
&& nestedRow?.before === 'viewport-1-a-panel-intro' && nestedRow?.group === 'viewport-1-a-panel',
|
|
87
|
+
JSON.stringify({ realAir: nestedRow?.gap, pairwiseGap, anchor: nestedRow?.airAnchor }));
|
|
88
|
+
check('nested seam rises to the page scale and grows every container it sits in',
|
|
89
|
+
nestedRow?.addition === 40 && nestedRow?.pageScale?.target === 80 && nestedRow?.declined === null
|
|
90
|
+
&& nestedRow?.ground?.execution === 'group-shift'
|
|
91
|
+
&& nestedMerged.find((o) => o.id === 'viewport-1-a-panel-details')?.dy === 40
|
|
92
|
+
&& nestedMerged.find((o) => o.id === 'viewport-1-a-panel')?.h === 840
|
|
93
|
+
&& nestedMerged.find((o) => o.id === 'viewport-1-a')?.h === 1064,
|
|
94
|
+
JSON.stringify({ addition: nestedRow?.addition, scale: nestedRow?.pageScale, overrides: nestedMerged.map((o) => ({ id: o.id, dy: o.dy, h: o.h })) }));
|
|
95
|
+
await page.evaluate(applyOverridesScript(), overridesToCss(nestedMerged));
|
|
96
|
+
const nestedProbe = await probeSeams(page, seamMeta(nested), nestedBase);
|
|
97
|
+
check('the enclosing panel really paints the nested air (9 real-pixel probes)',
|
|
98
|
+
nestedProbe.ok && nestedProbe.probes === 9 && nestedProbe.documentHeight === nestedBase + 40,
|
|
99
|
+
JSON.stringify({ ok: nestedProbe.ok, probes: nestedProbe.probes, h: [nestedBase, nestedProbe.documentHeight], failures: nestedProbe.failures.slice(0, 2) }));
|
|
100
|
+
|
|
101
|
+
// do-not-touch: same shape, but the panel's children share no common edge and vary wildly in width,
|
|
102
|
+
// so vertical position is carrying meaning. This is the metro-diagram geometry; it is never opened.
|
|
103
|
+
const diagramFile = path.join(here, 'fixtures/section-breathing/diagram-silent.html');
|
|
104
|
+
const diagramBefore = fs.readFileSync(diagramFile);
|
|
105
|
+
await page.goto('file://' + diagramFile, { waitUntil: 'load' });
|
|
106
|
+
const diagram = await page.evaluate(rule.browserFn(), { metrics: {}, params: rule.params });
|
|
107
|
+
const diagramCensus = await page.evaluate(rule.browserFn(), { metrics: {}, params: { ...rule.params, census: true } });
|
|
108
|
+
check('a declared seam inside a drawing is refused and the fixture stays byte-identical',
|
|
109
|
+
diagram.length === 0 && diagramCensus[0]?.declined === 'declared-inside-diagram-group'
|
|
110
|
+
&& sha(diagramBefore) === sha(fs.readFileSync(diagramFile)),
|
|
111
|
+
JSON.stringify({ census: diagramCensus, sha256: sha(diagramBefore) }));
|
|
64
112
|
|
|
65
|
-
//
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
}
|
|
113
|
+
// stays silent: a page with no structural gap has no scale, so the model proposes nothing rather
|
|
114
|
+
// than falling back to a constant.
|
|
115
|
+
await page.goto('file://' + path.join(here, 'fixtures/section-breathing/no-scale-silent.html'), { waitUntil: 'load' });
|
|
116
|
+
const noScale = await page.evaluate(rule.browserFn(), { metrics: {}, params: rule.params });
|
|
117
|
+
const noScaleCensus = await page.evaluate(rule.browserFn(), { metrics: {}, params: { ...rule.params, census: true } });
|
|
118
|
+
check('a page with no structural gap proposes nothing',
|
|
119
|
+
noScale.length === 0 && noScaleCensus.length === 2
|
|
120
|
+
&& noScaleCensus.every((row) => row.addition === 0 && row.pageScale === null
|
|
121
|
+
&& row.declined === 'page has no structural gap to set a scale from'),
|
|
122
|
+
JSON.stringify(noScaleCensus.map((r) => ({ after: r.after, addition: r.addition, declined: r.declined }))));
|
|
75
123
|
await browser.close();
|
|
76
124
|
if (failures) { console.log(`${failures} failure(s)`); process.exit(1); }
|
|
77
125
|
console.log('section-breathing pairs green');
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
<!doctype html><meta charset="utf-8"><style>*{margin:0;padding:0;box-sizing:border-box}body{font-family:system-ui}.generated-screen{position:relative}#page-flow{position:relative;width:1536px;height:1024px}section{position:absolute;left:0;width:1536px}.text{position:absolute;white-space:nowrap}</style>
|
|
2
|
+
<!--
|
|
3
|
+
Do-not-touch: a drawing, not a stack.
|
|
4
|
+
Same shape as rhythm-nested.html — a declared seam between two children of a painted panel — but
|
|
5
|
+
the panel's children share no common left, right or centre edge and their widths vary wildly.
|
|
6
|
+
Vertical position carries meaning here (this is the geometry of the metro-line diagram on
|
|
7
|
+
r4-b13-kinetic--network-map), so the seam is refused as declared-inside-diagram-group.
|
|
8
|
+
-->
|
|
9
|
+
<div class="generated-screen"><div class="ly-anchor-canvas" style="position:relative;height:1024px"><div id="page-flow">
|
|
10
|
+
<section id="viewport-1-a" style="top:0;height:1024px">
|
|
11
|
+
<div id="viewport-1-a-page" data-ly-section-ground-owner="diagram-ground" style="position:absolute;inset:0;background:#ffffff"></div>
|
|
12
|
+
<div id="viewport-1-a-map" style="position:absolute;left:120px;top:120px;width:1296px;height:800px;background:#eef2f6">
|
|
13
|
+
<div id="viewport-1-a-map-westgate" style="position:absolute;left:0;top:0;width:200px;height:60px"><p class="text" id="viewport-1-a-map-westgate-name" style="left:0;top:0">Westgate</p><p class="text" id="viewport-1-a-map-westgate-time" style="left:0;top:30px">4 min</p></div>
|
|
14
|
+
<div id="viewport-1-a-map-harbourfront" style="position:absolute;left:320px;top:120px;width:60px;height:60px"><p class="text" id="viewport-1-a-map-harbourfront-name" style="left:0;top:0">Harbour</p><p class="text" id="viewport-1-a-map-harbourfront-time" style="left:0;top:30px">7</p></div>
|
|
15
|
+
<div id="viewport-1-a-map-riverside" style="position:absolute;left:140px;top:240px;width:480px;height:60px"><p class="text" id="viewport-1-a-map-riverside-name" style="left:0;top:0">Riverside Interchange</p><p class="text" id="viewport-1-a-map-riverside-time" style="left:0;top:30px">11 min</p></div>
|
|
16
|
+
<div id="viewport-1-a-map-oakwood" style="position:absolute;left:600px;top:360px;width:110px;height:60px"><p class="text" id="viewport-1-a-map-oakwood-name" style="left:0;top:0">Oakwood</p><p class="text" id="viewport-1-a-map-oakwood-time" style="left:0;top:30px">15</p></div>
|
|
17
|
+
<div id="viewport-1-a-map-hillcrest" data-ly-section-boundary="before" data-ly-section-boundary-revision="section-boundary-v1" data-ly-section-before="viewport-1-a-map-oakwood" data-ly-section-owner="diagram-ground" data-ly-section-openability="openable" data-ly-section-continuation="owner-fill" data-ly-section-fact="terminus" style="position:absolute;left:260px;top:480px;width:300px;height:60px"><p class="text" id="viewport-1-a-map-hillcrest-name" style="left:0;top:0">Hillcrest</p><p class="text" id="viewport-1-a-map-hillcrest-time" style="left:0;top:30px">22 min</p></div>
|
|
18
|
+
</div>
|
|
19
|
+
</section>
|
|
20
|
+
</div></div></div>
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<!doctype html><meta charset="utf-8"><style>*{margin:0;padding:0;box-sizing:border-box}body{font-family:system-ui}.generated-screen{position:relative}#page-flow{position:relative;width:1536px;height:1400px}section{position:absolute;left:0;width:1536px}.text{position:absolute;white-space:nowrap}</style>
|
|
2
|
-
<div class="generated-screen"><div class="ly-anchor-canvas" style="position:relative;height:1400px"><div id="page-flow">
|
|
2
|
+
<div class="generated-screen" data-ly-section-ground-owner="page-ground" style="background:#fbf6ee"><div class="ly-anchor-canvas" style="position:relative;height:1400px"><div id="page-flow">
|
|
3
3
|
<!-- solid → solid: the hero's own cream section is the full-band ground; ink gap 24 px → one seam → +32 -->
|
|
4
4
|
<section id="viewport-1-a" style="top:0;height:700px;background:#fbf6ee"><h1 id="viewport-1-a-heading" class="text" style="left:80px;top:200px;font-size:48px">Give your time.</h1><p id="viewport-1-a-text" class="text" style="left:80px;top:660px;font-size:16px">Join neighbors helping pack food.</p></section>
|
|
5
|
-
<section id="viewport-2-a" style="top:700px;height:700px;background:#fbf6ee"><p id="viewport-2-a-eyebrow" class="text" style="left:80px;top:20px;font-size:12px">UPCOMING OPPORTUNITIES</p><h2 id="viewport-2-a-heading" class="text" style="left:80px;top:60px;font-size:32px">This week</h2></section>
|
|
5
|
+
<section id="viewport-2-a" data-ly-section-boundary="before" data-ly-section-boundary-revision="section-boundary-v1" data-ly-section-before="viewport-1-a" data-ly-section-owner="page-ground" data-ly-section-openability="openable" data-ly-section-continuation="owner-fill" data-ly-section-fact="page" style="top:700px;height:700px;background:#fbf6ee"><p id="viewport-2-a-eyebrow" class="text" style="left:80px;top:20px;font-size:12px">UPCOMING OPPORTUNITIES</p><h2 id="viewport-2-a-heading" class="text" style="left:80px;top:60px;font-size:32px">This week</h2></section>
|
|
6
6
|
</div></div></div>
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<!doctype html><meta charset="utf-8"><style>*{margin:0;padding:0;box-sizing:border-box}body{font-family:system-ui}.generated-screen{position:relative}#page-flow{position:relative;width:1536px;height:1400px}section{position:absolute;left:0;width:1536px}.text{position:absolute;white-space:nowrap}</style>
|
|
2
|
-
<div class="generated-screen"><div class="ly-anchor-canvas" style="position:relative;height:2100px"><div id="page-flow">
|
|
2
|
+
<div class="generated-screen" data-ly-section-ground-owner="page-ground" style="background:#fff"><div class="ly-anchor-canvas" style="position:relative;height:2100px"><div id="page-flow">
|
|
3
3
|
<!-- three solid sections, ink gaps 40 / 100: median 70 → target ceil8(102)=104 → additions 64 (cap) and 4 (declined: below the floor) -->
|
|
4
4
|
<section id="viewport-1-a" style="top:0;height:700px;background:#fff"><h1 id="viewport-1-a-heading" class="text" style="left:80px;top:200px;font-size:48px">One</h1><p id="viewport-1-a-text" class="text" style="left:80px;top:660px;font-size:16px">end of one</p></section>
|
|
5
|
-
<section id="viewport-2-a" style="top:700px;height:700px;background:#fff"><p id="viewport-2-a-top" class="text" style="left:80px;top:20px;font-size:16px">start of two</p><p id="viewport-2-a-text" class="text" style="left:80px;top:600px;font-size:16px">end of two</p></section>
|
|
6
|
-
<section id="viewport-3-a" style="top:1400px;height:700px;background:#fff"><p id="viewport-3-a-top" class="text" style="left:80px;top:20px;font-size:16px">start of three</p></section>
|
|
5
|
+
<section id="viewport-2-a" data-ly-section-boundary="before" data-ly-section-boundary-revision="section-boundary-v1" data-ly-section-before="viewport-1-a" data-ly-section-owner="page-ground" data-ly-section-openability="openable" data-ly-section-continuation="owner-fill" data-ly-section-fact="page" style="top:700px;height:700px;background:#fff"><p id="viewport-2-a-top" class="text" style="left:80px;top:20px;font-size:16px">start of two</p><p id="viewport-2-a-text" class="text" style="left:80px;top:600px;font-size:16px">end of two</p></section>
|
|
6
|
+
<section id="viewport-3-a" data-ly-section-boundary="before" data-ly-section-boundary-revision="section-boundary-v1" data-ly-section-before="viewport-2-a" data-ly-section-owner="page-ground" data-ly-section-openability="openable" data-ly-section-continuation="owner-fill" data-ly-section-fact="page" style="top:1400px;height:700px;background:#fff"><p id="viewport-3-a-top" class="text" style="left:80px;top:20px;font-size:16px">start of three</p></section>
|
|
7
7
|
</div></div></div>
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
<!doctype html><meta charset="utf-8"><style>*{margin:0;padding:0;box-sizing:border-box}body{font-family:system-ui}.generated-screen{position:relative}#page-flow{position:relative;width:1536px;height:1024px}section{position:absolute;left:0;width:1536px}.text{position:absolute;white-space:nowrap}</style>
|
|
2
|
+
<!--
|
|
3
|
+
Stays silent: a page with nothing to measure a scale from.
|
|
4
|
+
Both declarations are valid and openable, but every group carries a single ink leaf, so the page
|
|
5
|
+
has no structural gap anywhere and no page scale exists. The rhythm model can only redistribute
|
|
6
|
+
air a page already contains somewhere; with no scale it proposes nothing rather than inventing a
|
|
7
|
+
constant. (This is the pre-rhythm semantic-fire fixture, kept for exactly that property.)
|
|
8
|
+
-->
|
|
9
|
+
<div class="generated-screen"><div class="ly-anchor-canvas" style="position:relative;height:1024px"><div id="page-flow">
|
|
10
|
+
<section id="viewport-1-a" style="top:0;height:1024px">
|
|
11
|
+
<div id="viewport-1-a-page" data-ly-section-ground-owner="semantic-ground" style="position:absolute;inset:0;background:#fefefe"></div>
|
|
12
|
+
<div id="viewport-1-a-comparison" style="position:absolute;left:120px;top:200px;width:1296px;height:400px;border:1px solid #ddd"><p class="text" id="viewport-1-a-comparison-text" style="left:20px;top:20px">Comparison</p></div>
|
|
13
|
+
<div id="viewport-1-a-comparison-aside" style="position:absolute;left:816px;top:240px;width:600px;height:320px"></div>
|
|
14
|
+
<div style="position:absolute;left:0;top:600px;width:1536px;height:16px;background:#eee8e2"></div>
|
|
15
|
+
<div id="viewport-1-a-faq" data-ly-section-boundary="before" data-ly-section-boundary-revision="section-boundary-v1" data-ly-section-before="viewport-1-a-comparison" data-ly-section-owner="semantic-ground" data-ly-section-openability="openable" data-ly-section-continuation="owner-fill" data-ly-section-fact="questions" style="position:absolute;left:120px;top:616px;width:1296px;height:120px"><p class="text" id="viewport-1-a-faq-text" style="left:0;top:0">Common purchase questions</p></div>
|
|
16
|
+
<footer id="viewport-1-a-footer" data-ly-section-boundary="before" data-ly-section-boundary-revision="section-boundary-v1" data-ly-section-before="viewport-1-a-faq" data-ly-section-owner="semantic-ground" data-ly-section-openability="openable" data-ly-section-continuation="owner-fill" data-ly-section-fact="footer" style="position:absolute;left:0;top:752px;width:1536px;height:272px;background:#f8f8f8"><p class="text" id="viewport-1-a-footer-text" style="left:120px;top:40px">Footer</p></footer>
|
|
17
|
+
</section>
|
|
18
|
+
</div></div></div>
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
<!doctype html><meta charset="utf-8"><style>*{margin:0;padding:0;box-sizing:border-box}body{font-family:system-ui}.generated-screen{position:relative}#page-flow{position:relative;width:1536px;height:1024px}section{position:absolute;left:0;width:1536px}.text{position:absolute;white-space:nowrap}</style>
|
|
2
|
+
<!--
|
|
3
|
+
Nested seam + real air line.
|
|
4
|
+
The declared pair is intro -> details, two children of a panel that is NOT a direct child of the
|
|
5
|
+
section. The panel's own solid ground paints the cut, so no new ownership proof is needed.
|
|
6
|
+
Pairwise, intro->details reads 200 px of air; the page actually shows 40 px, because the floating
|
|
7
|
+
note card outlives intro inside the seam. Page scale: panel gaps 80 / 40 / 40 -> median 40,
|
|
8
|
+
largest 80, T = min(80, 2*40) = 80. Addition = 80 - 40 = 40. Measured pairwise it would be
|
|
9
|
+
max(0, 80 - 200) = 0, so this fixture fires only when the air line is read correctly.
|
|
10
|
+
-->
|
|
11
|
+
<div class="generated-screen"><div class="ly-anchor-canvas" style="position:relative;height:1024px"><div id="page-flow">
|
|
12
|
+
<section id="viewport-1-a" style="top:0;height:1024px">
|
|
13
|
+
<div id="viewport-1-a-page" data-ly-section-ground-owner="nested-ground" style="position:absolute;inset:0;background:#ffffff"></div>
|
|
14
|
+
<div id="viewport-1-a-panel" style="position:absolute;left:120px;top:120px;width:1296px;height:800px;background:#f2efe9">
|
|
15
|
+
<div id="viewport-1-a-panel-heading" style="position:absolute;left:20px;top:0;width:1256px;height:40px"><p class="text" id="viewport-1-a-panel-heading-title" style="left:0;top:0">Release notes</p><p class="text" id="viewport-1-a-panel-heading-date" style="left:600px;top:0">March</p></div>
|
|
16
|
+
<div id="viewport-1-a-panel-intro" style="position:absolute;left:20px;top:120px;width:1256px;height:120px"><p class="text" id="viewport-1-a-panel-intro-lead" style="left:0;top:0">What changed this month</p><p class="text" id="viewport-1-a-panel-intro-body" style="left:0;top:60px">Faster exports and a calmer inbox.</p></div>
|
|
17
|
+
<div id="viewport-1-a-panel-note" style="position:absolute;left:900px;top:280px;width:300px;height:120px;background:#e6e0d4"><p class="text" id="viewport-1-a-panel-note-label" style="left:16px;top:16px">Note</p><p class="text" id="viewport-1-a-panel-note-body" style="left:16px;top:56px">Rolling out slowly.</p></div>
|
|
18
|
+
<div id="viewport-1-a-panel-details" data-ly-section-boundary="before" data-ly-section-boundary-revision="section-boundary-v1" data-ly-section-before="viewport-1-a-panel-intro" data-ly-section-owner="nested-ground" data-ly-section-openability="openable" data-ly-section-continuation="owner-fill" data-ly-section-fact="details" style="position:absolute;left:20px;top:440px;width:1256px;height:320px"><p class="text" id="viewport-1-a-panel-details-title" style="left:0;top:0">Every change</p><p class="text" id="viewport-1-a-panel-details-body" style="left:0;top:60px">Twelve fixes and two new panels.</p></div>
|
|
19
|
+
</div>
|
|
20
|
+
</section>
|
|
21
|
+
</div></div></div>
|
|
@@ -1,11 +1,17 @@
|
|
|
1
1
|
<!doctype html><meta charset="utf-8"><style>*{margin:0;padding:0;box-sizing:border-box}body{font-family:system-ui}.generated-screen{position:relative}#page-flow{position:relative;width:1536px;height:1024px}section{position:absolute;left:0;width:1536px}.text{position:absolute;white-space:nowrap}</style>
|
|
2
2
|
<div class="generated-screen"><div class="ly-anchor-canvas" style="position:relative;height:1024px"><div id="page-flow">
|
|
3
3
|
<section id="viewport-1-a" style="top:0;height:1024px">
|
|
4
|
-
<div id="viewport-1-a-page" style="position:absolute;inset:0;background:#fefefe"></div>
|
|
5
|
-
<div id="viewport-1-a-comparison" style="position:absolute;left:120px;top:200px;width:1296px;height:400px;border:1px solid #ddd"
|
|
4
|
+
<div id="viewport-1-a-page" data-ly-section-ground-owner="semantic-ground" style="position:absolute;inset:0;background:#fefefe"></div>
|
|
5
|
+
<div id="viewport-1-a-comparison" style="position:absolute;left:120px;top:200px;width:1296px;height:400px;border:1px solid #ddd">
|
|
6
|
+
<div id="viewport-1-a-comparison-plans" style="position:absolute;left:20px;top:20px;width:1256px;height:100px"><p class="text" id="viewport-1-a-comparison-plans-title" style="left:0;top:0">Compare plans</p><p class="text" id="viewport-1-a-comparison-plans-note" style="left:0;top:40px">Billed annually</p></div>
|
|
7
|
+
<div id="viewport-1-a-comparison-table" style="position:absolute;left:20px;top:160px;width:1256px;height:200px"><p class="text" id="viewport-1-a-comparison-table-head" style="left:0;top:0">Included</p><p class="text" id="viewport-1-a-comparison-table-body" style="left:0;top:40px">Unlimited seats</p></div>
|
|
8
|
+
</div>
|
|
6
9
|
<div id="viewport-1-a-comparison-aside" style="position:absolute;left:816px;top:240px;width:600px;height:320px"></div>
|
|
7
10
|
<div style="position:absolute;left:0;top:600px;width:1536px;height:16px;background:#eee8e2"></div>
|
|
8
|
-
<div id="viewport-1-a-faq"
|
|
9
|
-
|
|
11
|
+
<div id="viewport-1-a-faq" data-ly-section-boundary="before" data-ly-section-boundary-revision="section-boundary-v1" data-ly-section-before="viewport-1-a-comparison" data-ly-section-owner="semantic-ground" data-ly-section-openability="openable" data-ly-section-continuation="owner-fill" data-ly-section-fact="questions" style="position:absolute;left:120px;top:616px;width:1296px;height:120px">
|
|
12
|
+
<div id="viewport-1-a-faq-lead" style="position:absolute;left:0;top:0;width:1296px;height:40px"><p class="text" id="viewport-1-a-faq-lead-title" style="left:0;top:0">Common purchase questions</p><p class="text" id="viewport-1-a-faq-lead-note" style="left:600px;top:0">Updated weekly</p></div>
|
|
13
|
+
<div id="viewport-1-a-faq-items" style="position:absolute;left:0;top:64px;width:1296px;height:56px"><p class="text" id="viewport-1-a-faq-items-one" style="left:0;top:0">Can I change plans?</p><p class="text" id="viewport-1-a-faq-items-two" style="left:0;top:28px">Do you offer refunds?</p></div>
|
|
14
|
+
</div>
|
|
15
|
+
<footer id="viewport-1-a-footer" data-ly-section-boundary="before" data-ly-section-boundary-revision="section-boundary-v1" data-ly-section-before="viewport-1-a-faq" data-ly-section-owner="semantic-ground" data-ly-section-openability="openable" data-ly-section-continuation="owner-fill" data-ly-section-fact="footer" style="position:absolute;left:0;top:752px;width:1536px;height:272px;background:#f8f8f8"><p class="text" id="viewport-1-a-footer-text" style="left:120px;top:40px">Footer</p></footer>
|
|
10
16
|
</section>
|
|
11
17
|
</div></div></div>
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
<!doctype html><meta charset="utf-8"><style>*{margin:0;padding:0;box-sizing:border-box}body{font-family:system-ui}.generated-screen{position:relative}#page-flow{position:relative;width:1536px;height:1024px}section{position:absolute;left:0;width:1536px}.text{position:absolute;white-space:nowrap}</style>
|
|
2
2
|
<div class="generated-screen"><div class="ly-anchor-canvas" style="position:relative;height:1024px"><div id="page-flow">
|
|
3
3
|
<section id="viewport-1-a" style="top:0;height:1024px">
|
|
4
|
-
<div id="viewport-1-a-page" style="position:absolute;inset:0;background:#081018"></div>
|
|
4
|
+
<div id="viewport-1-a-page" data-ly-section-ground-owner="content-ground" style="position:absolute;inset:0;background:#081018"></div>
|
|
5
5
|
<div id="viewport-1-a-dashboard" style="position:absolute;left:24px;top:100px;width:1488px;height:700px;border:1px solid #334"><p class="text" id="viewport-1-a-dashboard-text" style="left:20px;top:20px;color:white">Dense operations dashboard</p></div>
|
|
6
|
-
<footer id="viewport-1-a-footer" style="position:absolute;left:0;top:804px;width:1536px;height:220px;background:#0b141d"><p class="text" id="viewport-1-a-footer-text" style="left:24px;top:40px;color:white">Compact dashboard footer</p></footer>
|
|
6
|
+
<footer id="viewport-1-a-footer" data-ly-section-boundary="before" data-ly-section-boundary-revision="section-boundary-v1" data-ly-section-before="viewport-1-a-dashboard" data-ly-section-owner="content-ground" data-ly-section-openability="not-openable" data-ly-section-fact="footer" style="position:absolute;left:0;top:804px;width:1536px;height:220px;background:#0b141d"><p class="text" id="viewport-1-a-footer-text" style="left:24px;top:40px;color:white">Compact dashboard footer</p></footer>
|
|
7
7
|
</section>
|
|
8
8
|
</div></div></div>
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<!doctype html><meta charset="utf-8"><style>*{margin:0;padding:0;box-sizing:border-box}body{font-family:system-ui}.generated-screen{position:relative}#page-flow{position:relative;width:1536px;height:1400px}section{position:absolute;left:0;width:1536px}.text{position:absolute;white-space:nowrap}</style>
|
|
2
|
-
<div class="generated-screen"><div class="ly-anchor-canvas" style="position:relative;height:1400px"><div id="page-flow">
|
|
2
|
+
<div class="generated-screen" data-ly-section-ground-owner="content-ground"><div class="ly-anchor-canvas" style="position:relative;height:1400px"><div id="page-flow">
|
|
3
3
|
<!-- gradient → image: both grounds are experimental classes; the seam's ink gap is narrow but the page must abstain -->
|
|
4
4
|
<section id="viewport-1-a" style="top:0;height:700px;background-image:linear-gradient(#fbf6ee,#efe6d2)"><h1 id="viewport-1-a-heading" class="text" style="left:80px;top:200px;font-size:48px">Give your time.</h1><p id="viewport-1-a-text" class="text" style="left:80px;top:660px;font-size:16px">Join neighbors.</p></section>
|
|
5
|
-
<section id="viewport-2-a" style="top:700px;height:700px;background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==);background-size:cover;background-repeat:no-repeat"><p id="viewport-2-a-eyebrow" class="text" style="left:80px;top:20px;font-size:12px">UPCOMING</p></section>
|
|
5
|
+
<section id="viewport-2-a" data-ly-section-boundary="before" data-ly-section-boundary-revision="section-boundary-v1" data-ly-section-before="viewport-1-a" data-ly-section-owner="content-ground" data-ly-section-openability="not-openable" data-ly-section-fact="page" style="top:700px;height:700px;background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==);background-size:cover;background-repeat:no-repeat"><p id="viewport-2-a-eyebrow" class="text" style="left:80px;top:20px;font-size:12px">UPCOMING</p></section>
|
|
6
6
|
</div></div></div>
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<!doctype html><meta charset="utf-8"><style>*{margin:0;padding:0;box-sizing:border-box}body{font-family:system-ui}.generated-screen{position:relative}#page-flow{position:relative;width:1536px;height:1600px}section{position:absolute;left:0;width:1536px;height:400px;background:#fff}.text{position:absolute;white-space:nowrap;font-size:16px;line-height:16px}</style>
|
|
2
|
-
<div class="generated-screen"><div class="ly-anchor-canvas" style="position:relative;height:1600px"><div id="page-flow">
|
|
2
|
+
<div class="generated-screen" data-ly-section-ground-owner="page-ground" style="background:#fff"><div class="ly-anchor-canvas" style="position:relative;height:1600px"><div id="page-flow">
|
|
3
3
|
<section id="viewport-1-a" style="top:0"><p id="viewport-1-a-end" class="text" style="left:80px;top:376px">One</p></section>
|
|
4
|
-
<section id="viewport-2-a" style="top:400px"><p id="viewport-2-a-start" class="text" style="left:80px;top:0">Two start</p><p id="viewport-2-a-end" class="text" style="left:80px;top:376px">Two end</p></section>
|
|
5
|
-
<section id="viewport-3-a" style="top:800px"><p id="viewport-3-a-start" class="text" style="left:80px;top:0">Three start</p><p id="viewport-3-a-end" class="text" style="left:80px;top:370px">Three end</p></section>
|
|
6
|
-
<section id="viewport-4-a" style="top:1200px"><p id="viewport-4-a-start" class="text" style="left:80px;top:16px">Four</p></section>
|
|
4
|
+
<section id="viewport-2-a" data-ly-section-boundary="before" data-ly-section-boundary-revision="section-boundary-v1" data-ly-section-before="viewport-1-a" data-ly-section-owner="page-ground" data-ly-section-openability="openable" data-ly-section-continuation="owner-fill" data-ly-section-fact="page" style="top:400px"><p id="viewport-2-a-start" class="text" style="left:80px;top:0">Two start</p><p id="viewport-2-a-end" class="text" style="left:80px;top:376px">Two end</p></section>
|
|
5
|
+
<section id="viewport-3-a" data-ly-section-boundary="before" data-ly-section-boundary-revision="section-boundary-v1" data-ly-section-before="viewport-2-a" data-ly-section-owner="page-ground" data-ly-section-openability="openable" data-ly-section-continuation="owner-fill" data-ly-section-fact="page" style="top:800px"><p id="viewport-3-a-start" class="text" style="left:80px;top:0">Three start</p><p id="viewport-3-a-end" class="text" style="left:80px;top:370px">Three end</p></section>
|
|
6
|
+
<section id="viewport-4-a" data-ly-section-boundary="before" data-ly-section-boundary-revision="section-boundary-v1" data-ly-section-before="viewport-3-a" data-ly-section-owner="page-ground" data-ly-section-openability="openable" data-ly-section-continuation="owner-fill" data-ly-section-fact="page" style="top:1200px"><p id="viewport-4-a-start" class="text" style="left:80px;top:16px">Four</p></section>
|
|
7
7
|
</div></div></div>
|
|
@@ -111,12 +111,59 @@
|
|
|
111
111
|
setTimeout(() => { t.classList.remove('proto-toast-in'); setTimeout(() => t.remove(), 300); }, 1800);
|
|
112
112
|
}
|
|
113
113
|
|
|
114
|
+
// WAI-ARIA 1.2: aria-disabled="true" on an ancestor disables the element
|
|
115
|
+
// and ALL focusable descendants — a nearer descendant's own
|
|
116
|
+
// aria-disabled="false" does not cancel it. closest('[aria-disabled]')
|
|
117
|
+
// stops at the first match regardless of its value, so it can find a
|
|
118
|
+
// descendant's "false" and miss a "true" further up. Walk the whole
|
|
119
|
+
// ancestor chain instead.
|
|
120
|
+
function ariaDisabledAncestor(el) {
|
|
121
|
+
for (let node = el; node; node = node.parentElement) {
|
|
122
|
+
const value = node.getAttribute && node.getAttribute('aria-disabled');
|
|
123
|
+
if (value != null && value.trim().toLowerCase() === 'true') return node;
|
|
124
|
+
}
|
|
125
|
+
return null;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function sourceInertReason(el) {
|
|
129
|
+
if (el.closest('[disabled]')) return 'disabled';
|
|
130
|
+
if (el.closest('[inert]')) return 'inert';
|
|
131
|
+
if (el.closest('[hidden]')) return 'hidden';
|
|
132
|
+
if (el.closest('[data-proto-demoted]')) return 'demoted';
|
|
133
|
+
// The converter's own marker for a non-native control it deliberately
|
|
134
|
+
// left presentational — must be honored here too, or a real click can
|
|
135
|
+
// reactivate a control the source explicitly declared static.
|
|
136
|
+
if (el.closest('[data-12ui-presentational-control]')) return 'presentational-control';
|
|
137
|
+
if (ariaDisabledAncestor(el)) return 'aria-disabled';
|
|
138
|
+
return null;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function markBoundAction(el, action) {
|
|
142
|
+
const inertReason = sourceInertReason(el);
|
|
143
|
+
if (inertReason) {
|
|
144
|
+
el.dataset.protoSuppressed = `source-${inertReason}`;
|
|
145
|
+
return false;
|
|
146
|
+
}
|
|
147
|
+
// This must run in the real browser DOM. The build-time Linkedom CSSOM
|
|
148
|
+
// drops declaration priority, while an inline important declaration here
|
|
149
|
+
// outranks every selector-based rule, including #id!important.
|
|
150
|
+
el.style.setProperty('pointer-events', 'auto', 'important');
|
|
151
|
+
el.dataset.protoPointerRestored = 'true';
|
|
152
|
+
el.dataset.protoRuntimeBound = action;
|
|
153
|
+
el.classList.add('proto-clickable');
|
|
154
|
+
return true;
|
|
155
|
+
}
|
|
156
|
+
|
|
114
157
|
// ---- bind actions
|
|
115
158
|
const bound = { navigate: 0, dropdown: 0, modal: 0, field: 0 };
|
|
116
159
|
for (const el of document.querySelectorAll('[data-proto-action]')) {
|
|
117
160
|
const action = el.dataset.protoAction;
|
|
118
|
-
el.classList.add('proto-clickable');
|
|
119
161
|
if (action === 'navigate') {
|
|
162
|
+
if (!el.dataset.protoTarget) {
|
|
163
|
+
el.dataset.protoSuppressed = 'unresolved-navigation';
|
|
164
|
+
continue;
|
|
165
|
+
}
|
|
166
|
+
if (!markBoundAction(el, action)) continue;
|
|
120
167
|
bound.navigate++;
|
|
121
168
|
el.addEventListener('click', (e) => {
|
|
122
169
|
e.preventDefault(); e.stopPropagation();
|
|
@@ -131,10 +178,12 @@
|
|
|
131
178
|
el.dataset.protoSuppressed = 'invalid-dropdown';
|
|
132
179
|
continue;
|
|
133
180
|
}
|
|
181
|
+
if (!markBoundAction(el, action)) continue;
|
|
134
182
|
bound.dropdown++;
|
|
135
183
|
const labelEl = el.dataset.protoLabel ? document.getElementById(el.dataset.protoLabel) : null;
|
|
136
184
|
el.addEventListener('click', (e) => { e.preventDefault(); e.stopPropagation(); openDropdown(el, options, labelEl); });
|
|
137
185
|
} else if (action === 'modal') {
|
|
186
|
+
if (!markBoundAction(el, action)) continue;
|
|
138
187
|
bound.modal++;
|
|
139
188
|
const modal = JSON.parse(el.dataset.protoModal || '{}');
|
|
140
189
|
el.addEventListener('click', (e) => { e.preventDefault(); e.stopPropagation(); openModal(modal); });
|
|
@@ -145,6 +194,7 @@
|
|
|
145
194
|
for (const el of document.querySelectorAll('[data-proto-field]')) {
|
|
146
195
|
const textEl = el.dataset.protoField ? document.getElementById(el.dataset.protoField) : null;
|
|
147
196
|
if (!textEl) continue;
|
|
197
|
+
if (!markBoundAction(el, 'field')) continue;
|
|
148
198
|
bound.field++;
|
|
149
199
|
const placeholder = textEl.textContent;
|
|
150
200
|
const submitLabel = el.dataset.protoSubmit || 'Send';
|
|
@@ -164,7 +214,7 @@
|
|
|
164
214
|
});
|
|
165
215
|
el.addEventListener('click', (e) => { if (e.target !== textEl) { textEl.focus(); } });
|
|
166
216
|
for (const btn of document.querySelectorAll(`[data-proto-submit-for="${textEl.id}"]`)) {
|
|
167
|
-
btn
|
|
217
|
+
if (!markBoundAction(btn, 'field-submit')) continue;
|
|
168
218
|
btn.addEventListener('click', (e) => { e.preventDefault(); e.stopPropagation(); textEl.dispatchEvent(new KeyboardEvent('keydown', { key: 'Enter', bubbles: true })); });
|
|
169
219
|
}
|
|
170
220
|
}
|