@motion-proto/live-tokens 0.77.0 → 0.78.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/.claude/skills/live-tokens-check-compliance/SKILL.md +2 -0
- package/.claude/skills/live-tokens-create-page/SKILL.md +5 -4
- package/.claude/skills/live-tokens-fix-findings/SKILL.md +7 -0
- package/CHANGELOG.md +53 -0
- package/bin/check-page.mjs +131 -9
- package/bin/cli.mjs +25 -3
- package/bin/contractRunner.mjs +343 -29
- package/bin/lib/pageRoutes.mjs +309 -0
- package/package.json +5 -3
- package/src/editor/overlay/ColumnsOverlay.svelte +1 -1
- package/src/editor/overlay/LiveEditorOverlay.svelte +1 -0
- package/src/editor/overlay/LiveTokensRouter.svelte +1 -0
- package/src/editor/skill-atlas/skillSources.generated.ts +3 -3
- package/src/editor/skill-atlas/trees/check-compliance.ts +18 -18
- package/src/editor/skill-atlas/trees/create-page.ts +26 -26
- package/src/editor/skill-atlas/trees/fix-findings.ts +46 -11
- package/src/testing-js/{chunk-7TI7Z6Y6.js → chunk-GNIUPIU2.js} +2 -2
- package/src/testing-js/{chunk-ZMSX6CXR.js → chunk-NB3NZRBM.js} +13 -2
- package/src/testing-js/chunk-NB3NZRBM.js.map +1 -0
- package/src/testing-js/{chunk-L73N4NSO.js → chunk-WIZ6W7UT.js} +2 -2
- package/src/testing-js/component-alias.contract.js +2 -2
- package/src/testing-js/component-editor.contract.js +3 -3
- package/src/testing-js/component-render.contract.js +3 -3
- package/src/testing-js/index.d.ts +8 -2
- package/src/testing-js/index.js +19 -1
- package/src/testing-js/index.js.map +1 -1
- package/src/testing-js/page-compliance.contract.js +829 -0
- package/src/testing-js/page-compliance.contract.js.map +1 -0
- package/src/testing-js/{vitest-BE6uGF31.d.ts → vitest-C-wNWcoA.d.ts} +21 -1
- package/src/testing-js/vitest.d.ts +1 -1
- package/src/testing-js/vitest.js +1 -1
- package/template/README.md +16 -9
- package/template/package.json +1 -1
- package/template/src/pages/Home.svelte +13 -0
- package/src/testing-js/chunk-ZMSX6CXR.js.map +0 -1
- /package/src/testing-js/{chunk-7TI7Z6Y6.js.map → chunk-GNIUPIU2.js.map} +0 -0
- /package/src/testing-js/{chunk-L73N4NSO.js.map → chunk-WIZ6W7UT.js.map} +0 -0
|
@@ -0,0 +1,309 @@
|
|
|
1
|
+
// Page targets for `check-page --tests`: a page source file paired with the
|
|
2
|
+
// route that renders it.
|
|
3
|
+
//
|
|
4
|
+
// The route table is the consumer's own `pages` object, found by the file that
|
|
5
|
+
// imports `LiveTokensRouter`, and read statically the way `missing-source`
|
|
6
|
+
// already reads it. A route that object cannot express — one served by
|
|
7
|
+
// `resolve()` — is mapped by hand under `pageRoutes` in the testing settings.
|
|
8
|
+
// Parameter values are never guessed and the app entry point is never imported
|
|
9
|
+
// into Node.
|
|
10
|
+
|
|
11
|
+
import { existsSync, readFileSync, statSync } from 'node:fs';
|
|
12
|
+
import { join, relative, resolve } from 'node:path';
|
|
13
|
+
import { isExcluded } from './findings.mjs';
|
|
14
|
+
import { walk } from './tokenVocabulary.mjs';
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Directories that hold the system rather than pages built on it. Mirrors
|
|
18
|
+
* `NOT_PAGES` in `check-page.mjs`; duplicated so the static checker can import
|
|
19
|
+
* this module without an import cycle, and pinned to it by
|
|
20
|
+
* `pageRoutes.test.ts`.
|
|
21
|
+
*/
|
|
22
|
+
export const SYSTEM_DIRS = ['src/system', 'src/editor', 'src/lib', 'src/live-tokens'];
|
|
23
|
+
|
|
24
|
+
const SETTINGS_FILES = [
|
|
25
|
+
'live-tokens.testing.ts',
|
|
26
|
+
'live-tokens.testing.mts',
|
|
27
|
+
'live-tokens.testing.js',
|
|
28
|
+
'live-tokens.testing.mjs',
|
|
29
|
+
];
|
|
30
|
+
|
|
31
|
+
export function settingsFilePath(root) {
|
|
32
|
+
for (const name of SETTINGS_FILES) {
|
|
33
|
+
const path = join(root, name);
|
|
34
|
+
if (existsSync(path)) return path;
|
|
35
|
+
}
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** Block comments first: a `//` inside one must not seed a second, overlapping
|
|
40
|
+
* strip. Same reasoning as `contractRunner.mjs`'s own scraper. */
|
|
41
|
+
function stripComments(text) {
|
|
42
|
+
return text.replace(/\/\*[\s\S]*?\*\//g, '').replace(/\/\/.*$/gm, '');
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function skipString(text, start) {
|
|
46
|
+
const quote = text[start];
|
|
47
|
+
for (let i = start + 1; i < text.length; i++) {
|
|
48
|
+
if (text[i] === '\\') { i++; continue; }
|
|
49
|
+
if (text[i] === quote) return i;
|
|
50
|
+
}
|
|
51
|
+
return text.length;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** The text between the braces of the object literal opening at `open`. */
|
|
55
|
+
function objectBody(text, open) {
|
|
56
|
+
let depth = 0;
|
|
57
|
+
for (let i = open; i < text.length; i++) {
|
|
58
|
+
const ch = text[i];
|
|
59
|
+
if (ch === "'" || ch === '"' || ch === '`') { i = skipString(text, i); continue; }
|
|
60
|
+
if (ch === '{') depth++;
|
|
61
|
+
else if (ch === '}') {
|
|
62
|
+
depth--;
|
|
63
|
+
if (depth === 0) return text.slice(open + 1, i);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/** The text between the brackets of the array literal opening at `open`.
|
|
70
|
+
* Mirrors `objectBody`, bracket-keyed rather than brace-keyed. */
|
|
71
|
+
function arrayBody(text, open) {
|
|
72
|
+
let depth = 0;
|
|
73
|
+
for (let i = open; i < text.length; i++) {
|
|
74
|
+
const ch = text[i];
|
|
75
|
+
if (ch === "'" || ch === '"' || ch === '`') { i = skipString(text, i); continue; }
|
|
76
|
+
if (ch === '[') depth++;
|
|
77
|
+
else if (ch === ']') {
|
|
78
|
+
depth--;
|
|
79
|
+
if (depth === 0) return text.slice(open + 1, i);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
return null;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/** Top-level comma-separated chunks of an object or array body, respecting
|
|
86
|
+
* strings and nested brackets. Shared by `objectEntries` (each chunk is
|
|
87
|
+
* `key: value`) and `settingsPageViewports` (each chunk is a `{ width,
|
|
88
|
+
* height }` literal). */
|
|
89
|
+
function splitTopLevel(body) {
|
|
90
|
+
const chunks = [];
|
|
91
|
+
let depth = 0;
|
|
92
|
+
let start = 0;
|
|
93
|
+
for (let i = 0; i < body.length; i++) {
|
|
94
|
+
const ch = body[i];
|
|
95
|
+
if (ch === "'" || ch === '"' || ch === '`') { i = skipString(body, i); continue; }
|
|
96
|
+
if (ch === '{' || ch === '[' || ch === '(') depth++;
|
|
97
|
+
else if (ch === '}' || ch === ']' || ch === ')') depth--;
|
|
98
|
+
else if (ch === ',' && depth === 0) {
|
|
99
|
+
chunks.push(body.slice(start, i));
|
|
100
|
+
start = i + 1;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
chunks.push(body.slice(start));
|
|
104
|
+
return chunks;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/** Top-level `key: value` pairs of an object body, values kept as source text. */
|
|
108
|
+
function objectEntries(body) {
|
|
109
|
+
const entries = [];
|
|
110
|
+
for (const chunk of splitTopLevel(body)) {
|
|
111
|
+
const match = /^\s*(?:'([^']*)'|"([^"]*)"|([A-Za-z_$][\w$]*))\s*:([\s\S]*)$/.exec(chunk);
|
|
112
|
+
if (!match) continue;
|
|
113
|
+
entries.push({ key: match[1] ?? match[2] ?? match[3], value: match[4].trim() });
|
|
114
|
+
}
|
|
115
|
+
return entries;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function namedObject(text, field) {
|
|
119
|
+
const at = new RegExp(`\\b${field}\\s*[:=]\\s*\\{`).exec(text);
|
|
120
|
+
if (!at) return null;
|
|
121
|
+
return objectBody(text, at.index + at[0].length - 1);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const ROUTER_IMPORT = /import\s*\{[^}]*\bLiveTokensRouter\b[^}]*\}\s*from\s*['"]/;
|
|
125
|
+
|
|
126
|
+
/** Every file under `src/` that mounts the router, sorted so a project with
|
|
127
|
+
* more than one entry point resolves the same way on every run. */
|
|
128
|
+
export function routerFiles(root) {
|
|
129
|
+
const src = join(root, 'src');
|
|
130
|
+
if (!existsSync(src)) return [];
|
|
131
|
+
return walk(src, ['.svelte'])
|
|
132
|
+
.filter((file) => ROUTER_IMPORT.test(stripComments(readFileSync(file, 'utf8'))))
|
|
133
|
+
.sort();
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/** Route path -> page source, from the `pages` object of every router file.
|
|
137
|
+
* The first file to claim a route keeps it. */
|
|
138
|
+
export function routeTable(root) {
|
|
139
|
+
const table = new Map();
|
|
140
|
+
for (const file of routerFiles(root)) {
|
|
141
|
+
const body = namedObject(stripComments(readFileSync(file, 'utf8')), 'pages');
|
|
142
|
+
if (body === null) continue;
|
|
143
|
+
for (const { key, value } of objectEntries(body)) {
|
|
144
|
+
const source = /\bsource\s*:\s*['"]([^'"]+)['"]/.exec(value);
|
|
145
|
+
if (!source || table.has(key)) continue;
|
|
146
|
+
table.set(key, source[1]);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
return table;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* `pageRoutes` from the testing settings: page source -> concrete URL. Read
|
|
154
|
+
* from the file's source text, like `contractRunner.mjs`'s `dataDir` scrape,
|
|
155
|
+
* because importing the settings file would fail on its own extensionless
|
|
156
|
+
* imports. A `pageRoutes` key present in live code that this cannot read as
|
|
157
|
+
* a plain string map throws rather than falling through to a silent default:
|
|
158
|
+
* an unread mapping reads as a page with no route.
|
|
159
|
+
*/
|
|
160
|
+
export function settingsPageRoutes(root) {
|
|
161
|
+
const settings = settingsFilePath(root);
|
|
162
|
+
if (!settings) return new Map();
|
|
163
|
+
const live = stripComments(readFileSync(settings, 'utf8'));
|
|
164
|
+
const body = namedObject(live, 'pageRoutes');
|
|
165
|
+
if (body === null) {
|
|
166
|
+
if (/\bpageRoutes\s*:/.test(live)) {
|
|
167
|
+
throw new Error(
|
|
168
|
+
`"pageRoutes" in ${settings} is not a plain object literal, so --tests cannot read it statically. `
|
|
169
|
+
+ 'Write it as literal page-path to URL string pairs, or remove the key.',
|
|
170
|
+
);
|
|
171
|
+
}
|
|
172
|
+
return new Map();
|
|
173
|
+
}
|
|
174
|
+
const routes = new Map();
|
|
175
|
+
for (const { key, value } of objectEntries(body)) {
|
|
176
|
+
const url = /^['"]([^'"]+)['"]$/.exec(value);
|
|
177
|
+
if (!url) {
|
|
178
|
+
throw new Error(
|
|
179
|
+
`"pageRoutes['${key}']" in ${settings} is not a plain string literal, so --tests cannot read it statically.`,
|
|
180
|
+
);
|
|
181
|
+
}
|
|
182
|
+
routes.set(key, url[1]);
|
|
183
|
+
}
|
|
184
|
+
return routes;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/** The contract project's own desktop size, and the phone the text styles
|
|
188
|
+
* carry media overrides for (decision 4). Mirrors `DEFAULT_PAGE_VIEWPORTS`
|
|
189
|
+
* in `src/testing/config.ts`; duplicated for the reason `settingsPageRoutes`
|
|
190
|
+
* above reads its own settings statically rather than importing that module. */
|
|
191
|
+
export const DEFAULT_PAGE_VIEWPORTS = [
|
|
192
|
+
{ width: 1280, height: 900 },
|
|
193
|
+
{ width: 390, height: 844 },
|
|
194
|
+
];
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* `pageViewports` from the testing settings, read the same way
|
|
198
|
+
* `settingsPageRoutes` reads `pageRoutes`. Falls back to
|
|
199
|
+
* `DEFAULT_PAGE_VIEWPORTS` when the key is absent, so a caller building the
|
|
200
|
+
* set of viewports a page run is expected to cover sees the same list the
|
|
201
|
+
* generated Playwright config resolves at runtime (`src/testing/
|
|
202
|
+
* playwright.ts`'s own `settings.pageViewports`), rather than a shipped
|
|
203
|
+
* default that a project replaced. A `pageViewports` present in live code
|
|
204
|
+
* that this cannot read as a plain array of `{ width, height }` literals
|
|
205
|
+
* throws rather than falling through to a silent default.
|
|
206
|
+
*/
|
|
207
|
+
export function settingsPageViewports(root) {
|
|
208
|
+
const settings = settingsFilePath(root);
|
|
209
|
+
if (!settings) return DEFAULT_PAGE_VIEWPORTS;
|
|
210
|
+
const live = stripComments(readFileSync(settings, 'utf8'));
|
|
211
|
+
const at = /\bpageViewports\s*:\s*\[/.exec(live);
|
|
212
|
+
if (!at) {
|
|
213
|
+
if (/\bpageViewports\s*:/.test(live)) {
|
|
214
|
+
throw new Error(
|
|
215
|
+
`"pageViewports" in ${settings} is not a plain array literal, so --tests cannot read it statically. `
|
|
216
|
+
+ 'Write it as literal { width, height } entries, or remove the key.',
|
|
217
|
+
);
|
|
218
|
+
}
|
|
219
|
+
return DEFAULT_PAGE_VIEWPORTS;
|
|
220
|
+
}
|
|
221
|
+
const body = arrayBody(live, at.index + at[0].length - 1) ?? '';
|
|
222
|
+
const items = splitTopLevel(body).map((item) => item.trim()).filter(Boolean);
|
|
223
|
+
if (items.length === 0) return DEFAULT_PAGE_VIEWPORTS;
|
|
224
|
+
return items.map((item) => {
|
|
225
|
+
const width = /\bwidth\s*:\s*(\d+)/.exec(item);
|
|
226
|
+
const height = /\bheight\s*:\s*(\d+)/.exec(item);
|
|
227
|
+
if (!width || !height) {
|
|
228
|
+
throw new Error(
|
|
229
|
+
`"pageViewports" in ${settings} has an entry that is not a plain { width, height } literal, so --tests cannot read it statically.`,
|
|
230
|
+
);
|
|
231
|
+
}
|
|
232
|
+
return { width: Number(width[1]), height: Number(height[1]) };
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
function isSystemPath(rel) {
|
|
237
|
+
return SYSTEM_DIRS.some((dir) => rel === dir || rel.startsWith(`${dir}/`));
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
function toRelative(root, target) {
|
|
241
|
+
return relative(root, resolve(root, target)).split('\\').join('/');
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
const NO_ROUTE =
|
|
245
|
+
'no route renders this page. Give its entry in the pages object a `source` field, '
|
|
246
|
+
+ 'or map the file to a concrete URL under `pageRoutes` in live-tokens.testing.ts.';
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* The pages `--tests` opens, each with the route that renders it.
|
|
250
|
+
*
|
|
251
|
+
* With no paths, every mapped page outside the system directories is a target.
|
|
252
|
+
* With paths, each one is looked up in the same map; a file no route names
|
|
253
|
+
* comes back with `route: null` and the `reason` the caller reports as a
|
|
254
|
+
* `tests-setup` finding.
|
|
255
|
+
*/
|
|
256
|
+
export function resolvePageTargets(paths = [], root = process.cwd()) {
|
|
257
|
+
const mapped = new Map();
|
|
258
|
+
for (const [route, source] of routeTable(root)) {
|
|
259
|
+
const rel = toRelative(root, source);
|
|
260
|
+
if (!mapped.has(rel)) mapped.set(rel, route);
|
|
261
|
+
}
|
|
262
|
+
// An explicit mapping wins: it exists because the route table could not
|
|
263
|
+
// express the route in the first place.
|
|
264
|
+
for (const [source, url] of settingsPageRoutes(root)) {
|
|
265
|
+
mapped.set(toRelative(root, source), url);
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
const pages = [...mapped.entries()]
|
|
269
|
+
.filter(([source]) => !isSystemPath(source))
|
|
270
|
+
.sort(([a], [b]) => a.localeCompare(b))
|
|
271
|
+
.map(([source, route]) => ({ source, route }));
|
|
272
|
+
|
|
273
|
+
if (paths.length === 0) return pages;
|
|
274
|
+
|
|
275
|
+
const targets = [];
|
|
276
|
+
for (const path of paths) {
|
|
277
|
+
const full = resolve(root, path);
|
|
278
|
+
if (existsSync(full) && statSync(full).isDirectory()) {
|
|
279
|
+
const prefix = `${toRelative(root, path)}/`;
|
|
280
|
+
targets.push(...pages.filter((page) => page.source.startsWith(prefix)));
|
|
281
|
+
continue;
|
|
282
|
+
}
|
|
283
|
+
const rel = toRelative(root, path);
|
|
284
|
+
const route = mapped.get(rel);
|
|
285
|
+
targets.push(route === undefined ? { source: rel, route: null, reason: NO_ROUTE } : { source: rel, route });
|
|
286
|
+
}
|
|
287
|
+
return targets;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
/**
|
|
291
|
+
* `resolvePageTargets`, filtered by `checks.exclude` the same way
|
|
292
|
+
* `checkPages` (`bin/check-page.mjs`) filters the static half of the same
|
|
293
|
+
* command: an explicit *file* argument always checks, `isExcluded`'s own
|
|
294
|
+
* contract, but a directory `checkPages` walks — an explicit directory
|
|
295
|
+
* argument, or the no-paths-given discovery — drops excluded files. The two
|
|
296
|
+
* halves of `check-page --tests` have to agree on which pages a directory or
|
|
297
|
+
* omitted target reaches, or `--tests` fails a page the static half
|
|
298
|
+
* deliberately skips.
|
|
299
|
+
*/
|
|
300
|
+
export function resolvePageTestTargets(paths = [], root = process.cwd()) {
|
|
301
|
+
const allTargets = resolvePageTargets(paths, root);
|
|
302
|
+
const explicitFiles = new Set(
|
|
303
|
+
paths
|
|
304
|
+
.map((p) => resolve(root, p))
|
|
305
|
+
.filter((full) => existsSync(full) && !statSync(full).isDirectory())
|
|
306
|
+
.map((full) => toRelative(root, full)),
|
|
307
|
+
);
|
|
308
|
+
return allTargets.filter((t) => explicitFiles.has(t.source) || !isExcluded(t.source, root));
|
|
309
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@motion-proto/live-tokens",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.78.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Design token editor with live CSS variable editing. Svelte 5 + Vite 8.",
|
|
6
6
|
"keywords": [
|
|
@@ -148,10 +148,11 @@
|
|
|
148
148
|
"check": "svelte-check --tsconfig ./tsconfig.json",
|
|
149
149
|
"test": "vitest run",
|
|
150
150
|
"test:watch": "vitest",
|
|
151
|
-
"test:e2e": "npm run test:e2e:contract && npm run test:e2e:contract-defects && npm run test:e2e:stateful",
|
|
151
|
+
"test:e2e": "npm run test:e2e:contract && npm run test:e2e:contract-defects && npm run test:e2e:page-defects && npm run test:e2e:stateful",
|
|
152
152
|
"test:e2e:contract": "playwright test --project=contract --workers=${PLAYWRIGHT_WORKERS:-4}",
|
|
153
153
|
"test:e2e:stateful": "playwright test tests/e2e/live-editing.spec.ts tests/e2e/theme-workflow.spec.ts --workers=1",
|
|
154
154
|
"test:e2e:contract-defects": "playwright test --project=contract-defects --workers=${PLAYWRIGHT_WORKERS:-4}",
|
|
155
|
+
"test:e2e:page-defects": "playwright test --project=page-defects --workers=${PLAYWRIGHT_WORKERS:-4}",
|
|
155
156
|
"test:e2e:components": "playwright test --project=contract src/testing/component-render.contract.ts",
|
|
156
157
|
"test:e2e:ui": "playwright test --ui",
|
|
157
158
|
"test:registry-contract": "vitest run --config vitest.contract.config.ts",
|
|
@@ -183,7 +184,8 @@
|
|
|
183
184
|
"check:smoke-install": "bash scripts/smoke-install.sh",
|
|
184
185
|
"check:smoke-create": "bash scripts/smoke-create.sh",
|
|
185
186
|
"check:smoke-component-tests": "bash scripts/smoke-component-tests.sh",
|
|
186
|
-
"
|
|
187
|
+
"check:smoke-page-tests": "bash scripts/smoke-page-tests.sh",
|
|
188
|
+
"prepublishOnly": "npm run check:no-style-imports && npm run check:no-tooling-imports && npm run check:slot-prose && npm run check:overlay-portal && npm run check:editor-font-isolation && npm run check:component-defaults && npm run check:pages && npm run check:production-is-default && npm run check:docs-content && npm run build:lib && npm run check:token-contract && npm run check:preset-themes && npm run check:skills && npm run check:cli-strings && npm run check:skill-sources && npm run check:skill-atlas && npm run check:smoke-install && npm run check:smoke-create && npm run check:smoke-component-tests && npm run check:smoke-page-tests"
|
|
187
189
|
},
|
|
188
190
|
"peerDependencies": {
|
|
189
191
|
"@playwright/test": "^1.55",
|
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
</script>
|
|
39
39
|
|
|
40
40
|
{#if enabled && $columnsVisible}
|
|
41
|
-
<div class="columns-overlay" aria-hidden="true">
|
|
41
|
+
<div class="columns-overlay" data-live-tokens-chrome aria-hidden="true">
|
|
42
42
|
<div class="columns-overlay__inner">
|
|
43
43
|
{#each Array(count) as _, i}
|
|
44
44
|
<div class="columns-overlay__col">
|
|
@@ -266,6 +266,7 @@
|
|
|
266
266
|
<!-- svelte-ignore a11y_click_events_have_key_events, a11y_no_noninteractive_element_interactions, a11y_no_static_element_interactions -->
|
|
267
267
|
<div
|
|
268
268
|
class="lt-app"
|
|
269
|
+
data-live-tokens-page
|
|
269
270
|
class:is-editor={isEditor}
|
|
270
271
|
class:is-component-editor={isComponentEditor}
|
|
271
272
|
class:is-colors={isColors}
|