sliderpro-agentic-skills-etch 0.1.2 → 0.2.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/bin/cli.js +130 -41
- package/lib/skills-source.js +65 -0
- package/package.json +2 -1
- package/skills-package/manifest.json +43 -0
- package/skills-package/slider-skills/slider-pro-skills.md +113 -0
package/bin/cli.js
CHANGED
|
@@ -1,14 +1,33 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
// Installs the Slider Pro AI Connector skills into a project.
|
|
4
|
+
//
|
|
5
|
+
// The skills live in the docs repo and change more often than this package is
|
|
6
|
+
// published, so the current files are fetched at install time and the bundled
|
|
7
|
+
// copy is only a fallback for when the network is unavailable. Fetching is
|
|
8
|
+
// all-or-nothing: a partial fetch would mix a new skills file with stale
|
|
9
|
+
// component docs, which is worse than either source on its own.
|
|
10
|
+
|
|
11
|
+
import { cpSync, existsSync, mkdirSync, readFileSync, readdirSync, writeFileSync } from "node:fs";
|
|
4
12
|
import { dirname, join, resolve } from "node:path";
|
|
5
13
|
import { fileURLToPath } from "node:url";
|
|
14
|
+
import {
|
|
15
|
+
applyRewrites,
|
|
16
|
+
DEFAULT_REF,
|
|
17
|
+
looksLikeMarkdown,
|
|
18
|
+
OUT_COMPONENTS_DIR,
|
|
19
|
+
OUT_SKILLS_DIR,
|
|
20
|
+
rawUrl,
|
|
21
|
+
REPO,
|
|
22
|
+
REPO_MANIFEST,
|
|
23
|
+
} from "../lib/skills-source.js";
|
|
6
24
|
|
|
7
25
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
8
26
|
const packageRoot = join(__dirname, "..");
|
|
9
|
-
const
|
|
27
|
+
const bundleRoot = join(packageRoot, "skills-package");
|
|
10
28
|
|
|
11
|
-
const DIRS = [
|
|
29
|
+
const DIRS = [OUT_SKILLS_DIR, OUT_COMPONENTS_DIR];
|
|
30
|
+
const TIMEOUT_MS = 10000;
|
|
12
31
|
|
|
13
32
|
function printHelp() {
|
|
14
33
|
console.log(`
|
|
@@ -18,27 +37,37 @@ Usage:
|
|
|
18
37
|
npx sliderpro-agentic-skills-etch [directory] [options]
|
|
19
38
|
|
|
20
39
|
Options:
|
|
21
|
-
--force, -f
|
|
22
|
-
--
|
|
40
|
+
--force, -f Overwrite existing slider-skills/ or components/
|
|
41
|
+
--offline Skip the network and install the copy bundled with this package
|
|
42
|
+
--ref <ref> Install from a branch or tag (default: ${DEFAULT_REF})
|
|
43
|
+
--help, -h Show this help message
|
|
44
|
+
|
|
45
|
+
By default the current skills are fetched from ${REPO} so they are never
|
|
46
|
+
older than this package. Without a network the bundled copy is used instead.
|
|
23
47
|
|
|
24
48
|
Examples:
|
|
25
49
|
npx sliderpro-agentic-skills-etch
|
|
26
50
|
npx sliderpro-agentic-skills-etch ./my-site-project
|
|
27
|
-
npx sliderpro-agentic-skills-etch --force
|
|
51
|
+
npx sliderpro-agentic-skills-etch --offline --force
|
|
28
52
|
`);
|
|
29
53
|
}
|
|
30
54
|
|
|
31
55
|
function parseArgs(argv) {
|
|
32
|
-
const options = { force: false, help: false, target: process.cwd() };
|
|
33
|
-
|
|
34
|
-
for (
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
options.
|
|
41
|
-
|
|
56
|
+
const options = { force: false, help: false, offline: false, ref: DEFAULT_REF, target: process.cwd() };
|
|
57
|
+
|
|
58
|
+
for (let i = 0; i < argv.length; i++) {
|
|
59
|
+
const arg = argv[i];
|
|
60
|
+
if (arg === "--help" || arg === "-h") options.help = true;
|
|
61
|
+
else if (arg === "--force" || arg === "-f") options.force = true;
|
|
62
|
+
else if (arg === "--offline") options.offline = true;
|
|
63
|
+
else if (arg === "--ref") {
|
|
64
|
+
options.ref = argv[++i];
|
|
65
|
+
if (!options.ref) {
|
|
66
|
+
console.error("--ref needs a branch or tag name");
|
|
67
|
+
process.exit(1);
|
|
68
|
+
}
|
|
69
|
+
} else if (!arg.startsWith("-")) options.target = resolve(arg);
|
|
70
|
+
else {
|
|
42
71
|
console.error(`Unknown option: ${arg}`);
|
|
43
72
|
printHelp();
|
|
44
73
|
process.exit(1);
|
|
@@ -48,60 +77,120 @@ function parseArgs(argv) {
|
|
|
48
77
|
return options;
|
|
49
78
|
}
|
|
50
79
|
|
|
51
|
-
function
|
|
52
|
-
|
|
80
|
+
async function get(url) {
|
|
81
|
+
const controller = new AbortController();
|
|
82
|
+
const timer = setTimeout(() => controller.abort(), TIMEOUT_MS);
|
|
83
|
+
try {
|
|
84
|
+
const res = await fetch(url, { signal: controller.signal, headers: { "user-agent": "sliderpro-agentic-skills-etch" } });
|
|
85
|
+
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
|
86
|
+
return await res.text();
|
|
87
|
+
} finally {
|
|
88
|
+
clearTimeout(timer);
|
|
89
|
+
}
|
|
53
90
|
}
|
|
54
91
|
|
|
55
|
-
function
|
|
56
|
-
|
|
92
|
+
function bundledManifest() {
|
|
93
|
+
return JSON.parse(readFileSync(join(bundleRoot, "manifest.json"), "utf8"));
|
|
94
|
+
}
|
|
57
95
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
96
|
+
/** Returns { files: [{out, content}] } or null if anything at all went wrong. */
|
|
97
|
+
async function fetchLive(ref) {
|
|
98
|
+
let manifest;
|
|
99
|
+
try {
|
|
100
|
+
manifest = JSON.parse(await get(rawUrl(ref, REPO_MANIFEST)));
|
|
101
|
+
} catch {
|
|
102
|
+
// The manifest may predate this feature on an older ref; the bundled list
|
|
103
|
+
// is still a reasonable guess at what to fetch.
|
|
104
|
+
try {
|
|
105
|
+
manifest = bundledManifest();
|
|
106
|
+
} catch {
|
|
107
|
+
return null;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const entries = [...(manifest.skills || []), ...(manifest.components || [])];
|
|
112
|
+
if (entries.length === 0) return null;
|
|
61
113
|
|
|
62
|
-
|
|
63
|
-
|
|
114
|
+
const files = [];
|
|
115
|
+
for (const entry of entries) {
|
|
116
|
+
let text;
|
|
117
|
+
try {
|
|
118
|
+
text = await get(rawUrl(ref, entry.repo));
|
|
119
|
+
} catch {
|
|
120
|
+
return null;
|
|
64
121
|
}
|
|
122
|
+
if (!looksLikeMarkdown(text)) return null;
|
|
123
|
+
files.push({ out: entry.out, content: applyRewrites(text) });
|
|
124
|
+
}
|
|
125
|
+
return files;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function writeFiles(target, files) {
|
|
129
|
+
for (const file of files) {
|
|
130
|
+
const dest = join(target, file.out);
|
|
131
|
+
mkdirSync(dirname(dest), { recursive: true });
|
|
132
|
+
writeFileSync(dest, file.content);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
65
135
|
|
|
66
|
-
|
|
136
|
+
function copyBundle(target) {
|
|
137
|
+
for (const dir of DIRS) {
|
|
138
|
+
const source = join(bundleRoot, dir);
|
|
139
|
+
if (existsSync(source)) cpSync(source, join(target, dir), { recursive: true });
|
|
67
140
|
}
|
|
68
141
|
}
|
|
69
142
|
|
|
70
|
-
function countComponents() {
|
|
143
|
+
function countComponents(target) {
|
|
71
144
|
try {
|
|
72
|
-
return readdirSync(join(
|
|
145
|
+
return readdirSync(join(target, OUT_COMPONENTS_DIR)).filter((f) => f.endsWith(".md")).length;
|
|
73
146
|
} catch {
|
|
74
147
|
return 0;
|
|
75
148
|
}
|
|
76
149
|
}
|
|
77
150
|
|
|
78
|
-
function main() {
|
|
79
|
-
const { force, help, target } = parseArgs(process.argv.slice(2));
|
|
151
|
+
async function main() {
|
|
152
|
+
const { force, help, offline, ref, target } = parseArgs(process.argv.slice(2));
|
|
80
153
|
|
|
81
154
|
if (help) {
|
|
82
155
|
printHelp();
|
|
83
156
|
return;
|
|
84
157
|
}
|
|
85
158
|
|
|
86
|
-
const conflicts =
|
|
87
|
-
|
|
159
|
+
const conflicts = DIRS.filter((dir) => existsSync(join(target, dir))).map((dir) => `${dir}/`);
|
|
88
160
|
if (conflicts.length > 0 && !force) {
|
|
89
161
|
console.error("Installation blocked, the following already exist:");
|
|
90
|
-
for (const conflict of conflicts) {
|
|
91
|
-
console.error(` ${conflict}`);
|
|
92
|
-
}
|
|
162
|
+
for (const conflict of conflicts) console.error(` ${conflict}`);
|
|
93
163
|
console.error("\nRe-run with --force to overwrite.");
|
|
94
164
|
process.exit(1);
|
|
95
165
|
}
|
|
96
166
|
|
|
97
|
-
|
|
167
|
+
mkdirSync(target, { recursive: true });
|
|
168
|
+
|
|
169
|
+
let source;
|
|
170
|
+
if (offline) {
|
|
171
|
+
copyBundle(target);
|
|
172
|
+
source = "the copy bundled with this package (--offline)";
|
|
173
|
+
} else {
|
|
174
|
+
const files = await fetchLive(ref);
|
|
175
|
+
if (files) {
|
|
176
|
+
writeFiles(target, files);
|
|
177
|
+
source = `${REPO} (${ref})`;
|
|
178
|
+
} else {
|
|
179
|
+
copyBundle(target);
|
|
180
|
+
source = "the copy bundled with this package (network unavailable)";
|
|
181
|
+
}
|
|
182
|
+
}
|
|
98
183
|
|
|
99
184
|
console.log(`Installed Slider Pro AI Connector skills to ${target}`);
|
|
100
|
-
console.log(`
|
|
101
|
-
console.log(`
|
|
102
|
-
console.log(`
|
|
103
|
-
console.log(
|
|
185
|
+
console.log(` source: ${source}`);
|
|
186
|
+
console.log(` ${OUT_SKILLS_DIR}/slider-pro-skills.md`);
|
|
187
|
+
console.log(` ${OUT_SKILLS_DIR}/slider-pro-skills-reference.md`);
|
|
188
|
+
console.log(` ${OUT_COMPONENTS_DIR}/ (${countComponents(target)} component prop docs)`);
|
|
189
|
+
console.log(`\nPoint your AI coding agent at ${OUT_SKILLS_DIR}/slider-pro-skills.md to load it.`);
|
|
104
190
|
console.log('Then tell the agent: "npx @digital-gravy/etch-connector serve"');
|
|
105
191
|
}
|
|
106
192
|
|
|
107
|
-
main()
|
|
193
|
+
main().catch((err) => {
|
|
194
|
+
console.error(err.message || err);
|
|
195
|
+
process.exit(1);
|
|
196
|
+
});
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
// Single source of truth for where the skills come from and how their links are
|
|
2
|
+
// rewritten for an installed project. Both scripts/build.js (which bundles a
|
|
3
|
+
// copy at publish time) and bin/cli.js (which prefers a live copy at install
|
|
4
|
+
// time) import this, so the two paths cannot drift in how they rewrite links.
|
|
5
|
+
|
|
6
|
+
export const REPO = "udoro/SliderPro-Etch-Docs";
|
|
7
|
+
export const DEFAULT_REF = "master";
|
|
8
|
+
export const DOCS_URL = "https://design-with-cracka.gitbook.io/etchsliderpro";
|
|
9
|
+
|
|
10
|
+
// Where things live in the docs repo.
|
|
11
|
+
export const REPO_SKILLS_DIR = "ai-connector/slider-skills";
|
|
12
|
+
export const REPO_COMPONENTS_DIR = "components";
|
|
13
|
+
export const REPO_MANIFEST = "ai-connector/skills-manifest.json";
|
|
14
|
+
|
|
15
|
+
// Where they land in a user's project.
|
|
16
|
+
export const OUT_SKILLS_DIR = "slider-skills";
|
|
17
|
+
export const OUT_COMPONENTS_DIR = "components";
|
|
18
|
+
|
|
19
|
+
// The docs repo nests slider-skills/ two levels below the root
|
|
20
|
+
// (ai-connector/slider-skills/), so it links components/ as ../../components/.
|
|
21
|
+
// Installed, slider-skills/ sits directly under the project root, so that
|
|
22
|
+
// becomes ../components/.
|
|
23
|
+
//
|
|
24
|
+
// The sibling doc pages the skills link to do not ship in the package at all,
|
|
25
|
+
// so point those at the public site rather than leave a link resolving to
|
|
26
|
+
// nothing.
|
|
27
|
+
export const PATH_REWRITE = [
|
|
28
|
+
[/\.\.\/\.\.\/components\//g, "../components/"],
|
|
29
|
+
[/\.\.\/\.\.\/card-stack-templates\.md/g, `${DOCS_URL}/card-stack-templates`],
|
|
30
|
+
[/\.\.\/\.\.\/premade-templates\.md/g, `${DOCS_URL}/premade-templates`],
|
|
31
|
+
[/\.\.\/\.\.\/javascript-api\.md/g, `${DOCS_URL}/javascript-api`],
|
|
32
|
+
[/\.\.\/\.\.\/styling-and-responsive\.md/g, `${DOCS_URL}/styling-and-responsive`],
|
|
33
|
+
[/\.\.\/\.\.\/admin-settings\.md/g, `${DOCS_URL}/admin-settings`],
|
|
34
|
+
[/\.\.\/\.\.\/troubleshooting\.md/g, `${DOCS_URL}/troubleshooting`],
|
|
35
|
+
];
|
|
36
|
+
|
|
37
|
+
export function applyRewrites(content) {
|
|
38
|
+
let out = content;
|
|
39
|
+
for (const [pattern, replacement] of PATH_REWRITE) out = out.replace(pattern, replacement);
|
|
40
|
+
return out;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Only a markdown LINK TARGET pointing outside the package is a defect: it would
|
|
45
|
+
* 404 for every installed user. A bare ../../ path in prose is not, because the
|
|
46
|
+
* skills files deliberately reference optional local files and always say what
|
|
47
|
+
* to do when they are absent.
|
|
48
|
+
*/
|
|
49
|
+
export function findStaleLinks(content) {
|
|
50
|
+
return [...content.matchAll(/\]\((\.\.\/\.\.\/[^)]+)\)/g)].map((m) => m[1]);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export function rawUrl(ref, repoPath) {
|
|
54
|
+
return `https://raw.githubusercontent.com/${REPO}/${ref}/${repoPath}`;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* A fetched file only counts if it is non-empty markdown. A proxy or an error
|
|
59
|
+
* page returning 200 with HTML would otherwise be written out as a skills file.
|
|
60
|
+
*/
|
|
61
|
+
export function looksLikeMarkdown(text) {
|
|
62
|
+
if (!text || text.trim().length < 200) return false;
|
|
63
|
+
const head = text.slice(0, 400).toLowerCase();
|
|
64
|
+
return !head.includes("<!doctype html") && !head.includes("<html");
|
|
65
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sliderpro-agentic-skills-etch",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Installs the Slider Pro for Etch AI Connector skills files and component docs into any project",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
},
|
|
9
9
|
"files": [
|
|
10
10
|
"bin",
|
|
11
|
+
"lib",
|
|
11
12
|
"skills-package"
|
|
12
13
|
],
|
|
13
14
|
"scripts": {
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
{
|
|
2
|
+
"generated": "2026-08-22",
|
|
3
|
+
"skills": [
|
|
4
|
+
{
|
|
5
|
+
"repo": "ai-connector/slider-skills/slider-pro-skills-reference.md",
|
|
6
|
+
"out": "slider-skills/slider-pro-skills-reference.md"
|
|
7
|
+
},
|
|
8
|
+
{
|
|
9
|
+
"repo": "ai-connector/slider-skills/slider-pro-skills.md",
|
|
10
|
+
"out": "slider-skills/slider-pro-skills.md"
|
|
11
|
+
}
|
|
12
|
+
],
|
|
13
|
+
"components": [
|
|
14
|
+
{
|
|
15
|
+
"repo": "components/dwc-slide.md",
|
|
16
|
+
"out": "components/dwc-slide.md"
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
"repo": "components/dwc-slider-nav-button.md",
|
|
20
|
+
"out": "components/dwc-slider-nav-button.md"
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
"repo": "components/dwc-slider-pagination.md",
|
|
24
|
+
"out": "components/dwc-slider-pagination.md"
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
"repo": "components/dwc-slider-play-pause.md",
|
|
28
|
+
"out": "components/dwc-slider-play-pause.md"
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
"repo": "components/dwc-slider-progress.md",
|
|
32
|
+
"out": "components/dwc-slider-progress.md"
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
"repo": "components/dwc-slider-wrapper.md",
|
|
36
|
+
"out": "components/dwc-slider-wrapper.md"
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
"repo": "components/dwc-slider.md",
|
|
40
|
+
"out": "components/dwc-slider.md"
|
|
41
|
+
}
|
|
42
|
+
]
|
|
43
|
+
}
|
|
@@ -242,6 +242,46 @@ Either way the CSS rule itself is `etch.styles.create('.fall-card', '...')`, whi
|
|
|
242
242
|
selector has to match.
|
|
243
243
|
|
|
244
244
|
|
|
245
|
+
### CSS conventions
|
|
246
|
+
|
|
247
|
+
Match the shipped templates. An install that already has premade sliders on it has these
|
|
248
|
+
conventions everywhere, and a build that ignores them reads as foreign.
|
|
249
|
+
|
|
250
|
+
**Naming.** The block is the slider, named after the design:
|
|
251
|
+
|
|
252
|
+
| Thing | Pattern | Example |
|
|
253
|
+
| --- | --- | --- |
|
|
254
|
+
| Wrapper class, slider designs | `.slider-<name>-wrapper` | `.slider-flow-wrapper` |
|
|
255
|
+
| Wrapper class, card stacks | `.slider-wrapper-<name>` | `.slider-wrapper-fall` |
|
|
256
|
+
| Slider class | `.slider-<name>` | `.slider-chronos` |
|
|
257
|
+
| Elements | `.slider-<name>__<element>` | `.slider-team__sync` |
|
|
258
|
+
| Modifiers | `.slider-<name>__<element>--<variant>` | `.slider-afterform__word--form` |
|
|
259
|
+
|
|
260
|
+
The wrapper class carries the word `wrapper` and the slider class carries the word `slider`. Do not
|
|
261
|
+
invent a short prefix of your own.
|
|
262
|
+
|
|
263
|
+
**One style entry per element class.** Never one nested parent entry holding the whole component.
|
|
264
|
+
`etch.styles.create()` per class, each with its own CSS:
|
|
265
|
+
|
|
266
|
+
```js
|
|
267
|
+
const S = {};
|
|
268
|
+
S.wrapper = etch.styles.create('.slider-afterform-wrapper', '/* ... */');
|
|
269
|
+
S.caption = etch.styles.create('.slider-afterform__caption', '/* ... */');
|
|
270
|
+
S.title = etch.styles.create('.slider-afterform__caption-title', '/* ... */');
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
Inside an element's **own** entry, nesting is preferred for media queries, pseudo-elements
|
|
274
|
+
(`&::after`), states (`&:hover`, `&.is-active`), ancestor states
|
|
275
|
+
(`.splide__slide.is-active &`) and child tag selectors (`& img`, `& svg`). Do **not** nest another
|
|
276
|
+
class's rules (`&__title`) inside a different class's entry: that class gets its own entry.
|
|
277
|
+
|
|
278
|
+
**Renaming is not one operation.** A style entry's selector and a block's `class` attribute are
|
|
279
|
+
independent. Renaming the selector rewrites the rendered class only where the class came from a
|
|
280
|
+
**component class prop**, because that prop stores the style id and resolves it at render. On a
|
|
281
|
+
plain element the `class` attribute is literal, so a selector rename leaves it pointing at a
|
|
282
|
+
selector that no longer exists. Rename both, or the element silently loses its CSS.
|
|
283
|
+
|
|
284
|
+
|
|
245
285
|
### Group props and the one-extra-brace rule
|
|
246
286
|
|
|
247
287
|
Most Slider props live in groups (`layout`, `motion`, `slides`, `autoplay`, ...). A group is stored
|
|
@@ -417,6 +457,38 @@ setGroup(thumbId, 'sliderSetup', { sliderRole: 'thumbnails', transitionType: 'Lo
|
|
|
417
457
|
Both inside the same Wrapper and they pair automatically. A Wrapper can hold more than one
|
|
418
458
|
thumbnail slider, which is how Zeon runs a background layer and a strip off the same main.
|
|
419
459
|
|
|
460
|
+
### Animating slide content: no script
|
|
461
|
+
|
|
462
|
+
Splide puts `is-active` on the current `.splide__slide`, which is all you need to animate anything
|
|
463
|
+
inside it. Give the element its resting state, then reveal it from the active slide:
|
|
464
|
+
|
|
465
|
+
```css
|
|
466
|
+
/* in .slider-<name>__figure's own entry */
|
|
467
|
+
opacity: 0;
|
|
468
|
+
transform: translateY(30px);
|
|
469
|
+
transition: opacity 820ms cubic-bezier(0.22, 0.68, 0.24, 1),
|
|
470
|
+
transform 820ms cubic-bezier(0.22, 0.68, 0.24, 1);
|
|
471
|
+
|
|
472
|
+
.splide__slide.is-active & { opacity: 1; transform: translateY(0); }
|
|
473
|
+
```
|
|
474
|
+
|
|
475
|
+
Stagger a caption by putting the same pair on each line with a growing `transition-delay`
|
|
476
|
+
(thumbnail 140ms, status 200ms, title 260ms, body 320ms) so the block assembles rather than
|
|
477
|
+
appearing at once.
|
|
478
|
+
|
|
479
|
+
Two things that bite:
|
|
480
|
+
|
|
481
|
+
* **Keep layout transforms in the animated transform.** An element centred with
|
|
482
|
+
`translateX(-50%)` must animate as `translateX(-50%) translateY(30px)`, or it jumps sideways on
|
|
483
|
+
every transition. Where a breakpoint drops the centring, restate the transform there too.
|
|
484
|
+
* **A slow drift on the backdrop** (`scale(1.06)` to `scale(1)` over several seconds) makes a
|
|
485
|
+
change read as movement rather than a cut. Put it on the image, not the slide, so it does not
|
|
486
|
+
fight the transition.
|
|
487
|
+
|
|
488
|
+
This works with the Fade transition, which crossfades the slides underneath while the contents
|
|
489
|
+
move independently.
|
|
490
|
+
|
|
491
|
+
|
|
420
492
|
### Carousel on mobile, grid on desktop
|
|
421
493
|
|
|
422
494
|
```js
|
|
@@ -661,6 +733,47 @@ rather than debugging a slider that will never start.
|
|
|
661
733
|
|
|
662
734
|
**Do not touch the plugin's own stylesheet.** Style through the Slider Class and CSS variables.
|
|
663
735
|
|
|
736
|
+
**A prop that gates a component's internals must be set explicitly, even to its default.** This is
|
|
737
|
+
the one exception to "never set a prop to its default", and it is invisible when you hit it. A
|
|
738
|
+
component's declared default populates the Etch settings panel; it is **not** written onto an
|
|
739
|
+
instance you create programmatically. When a condition inside the component reads a
|
|
740
|
+
flag prop and the key is absent, the expression does not resolve and the condition returns
|
|
741
|
+
false **whichever operator it uses**. Both branches vanish at once.
|
|
742
|
+
|
|
743
|
+
The worked case: a DWC Slider Nav Button created with `navigationType` alone renders
|
|
744
|
+
`<button>` with no icon. The default arrow sits behind `useCustomArrow isFalsy` and the custom SVG
|
|
745
|
+
behind `useCustomArrow isTruthy`, and neither appears. Writing `useCustomArrow: '{false}'`, which is
|
|
746
|
+
already the default, makes the arrow render. If a component renders structurally but its inner
|
|
747
|
+
content is missing, this is the first thing to check.
|
|
748
|
+
|
|
749
|
+
**Give the slider a builder height when slide content is absolutely positioned.** A slide whose
|
|
750
|
+
children are all `position: absolute` has no intrinsic height, so before the slider initialises it
|
|
751
|
+
collapses to nothing and the layout is unusable in the builder. Guard it:
|
|
752
|
+
|
|
753
|
+
```css
|
|
754
|
+
&.etch-builder-block { min-block-size: 640px; }
|
|
755
|
+
```
|
|
756
|
+
|
|
757
|
+
**Do not anchor against a neighbour that stops shrinking.** A `vw` offset tuned at one width
|
|
758
|
+
silently collides at another, because `min()` and `clamp()` neighbours stop shrinking while the
|
|
759
|
+
`vw` keeps going. Derive the clearance from the neighbour's real footprint instead:
|
|
760
|
+
|
|
761
|
+
```css
|
|
762
|
+
/* wrong: fits at 1771, overlaps the column from 1024 to 1430 */
|
|
763
|
+
right: 17.6vw;
|
|
764
|
+
/* right: reserve exactly what the column occupies */
|
|
765
|
+
right: calc(4vw + min(15vw, 195px) + 2.5vw);
|
|
766
|
+
```
|
|
767
|
+
|
|
768
|
+
Check the mid range explicitly. Between the widest layout and the first breakpoint is where
|
|
769
|
+
side-by-side compositions fail, and it is the range nobody screenshots.
|
|
770
|
+
|
|
771
|
+
**A save can lag the front end.** `etch.saveAsync()` resolves before the change is necessarily
|
|
772
|
+
readable on the published page, so a fetch straight afterwards can return the previous value and
|
|
773
|
+
look like a failed write. Re-read through `etch.blocks.getAttribute` to confirm intent, and treat a
|
|
774
|
+
stale page as latency rather than loss. Reloading the builder tab flushes anything pending.
|
|
775
|
+
|
|
776
|
+
|
|
664
777
|
### Do not
|
|
665
778
|
|
|
666
779
|
* Hardcode component IDs.
|