sliderpro-agentic-skills-etch 0.1.0 → 0.1.2

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sliderpro-agentic-skills-etch",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
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": {
@@ -444,19 +444,28 @@ handler inside `ready()`: the slider mounted before your code ran, so it has alr
444
444
  Before configuring an unfamiliar slider, check whether it is a premade template. If it is, use
445
445
  that template's documented knobs rather than inventing new CSS.
446
446
 
447
- | Template | Identify by | Script | Guide |
448
- | --- | --- | --- | --- |
449
- | Slider Zeon | `.slider-zeon`, `.main-slider-zeon` | no | `https://design-with-cracka.gitbook.io/etchsliderpro/premade-templates` |
450
- | Slider Chronos | `.slider-chronos` | no | `https://design-with-cracka.gitbook.io/etchsliderpro/premade-templates` |
451
- | Slider Flow | `.slider-flow` | no | `https://design-with-cracka.gitbook.io/etchsliderpro/premade-templates` |
452
- | Slider Team | `.slider-team` | no | `https://design-with-cracka.gitbook.io/etchsliderpro/premade-templates` |
453
- | Slider Stack | `.slider-stack`, `.slider-stack__card` | no | `https://design-with-cracka.gitbook.io/etchsliderpro/card-stack-templates` |
454
- | Slider Marquee | no class prefix. Identify by the combination: Infinite Scroll on, Edge Fade on, Slide Auto Width on, two wrappers whose rows scroll in opposite directions | no | `https://design-with-cracka.gitbook.io/etchsliderpro/premade-templates` |
455
- | Deck Featured | `.deck-stack-featured`, `.deck-card-featured` | yes | `https://design-with-cracka.gitbook.io/etchsliderpro/card-stack-templates` |
456
- | Deck Testimonial | `.deck-stack-testimonial`, `.deck-card-testimonial` | yes | `https://design-with-cracka.gitbook.io/etchsliderpro/card-stack-templates` |
457
- | Fall | `.fall-stack`, `.fall-card` | yes | `https://design-with-cracka.gitbook.io/etchsliderpro/card-stack-templates` |
458
-
459
- All nine use Sync Custom Element.
447
+ Every template names its Wrapper, so one class on the Wrapper identifies all nine. Check that
448
+ first; the second column is confirmation.
449
+
450
+ | Template | Wrapper class | Also present | Script | Guide |
451
+ | --- | --- | --- | --- | --- |
452
+ | Slider Zeon | `.slider-zeon-wrapper` | `.slider-zeon__bg-thumb`, `.main-slider-zeon__content` | no | `https://design-with-cracka.gitbook.io/etchsliderpro/premade-templates` |
453
+ | Slider Chronos | `.slider-chronos-wrapper` | `.slider-chronos__slide`, `.slider-chronos__year` | no | `https://design-with-cracka.gitbook.io/etchsliderpro/premade-templates` |
454
+ | Slider Flow | `.slider-flow-wrapper` | `.slider-flow__slide`, `.slider-flow__slide-bg-media` | no | `https://design-with-cracka.gitbook.io/etchsliderpro/premade-templates` |
455
+ | Slider Team | `.slider-team-wrapper` | `.slider-team__grid`, `.slider-team__sync` | no | `https://design-with-cracka.gitbook.io/etchsliderpro/premade-templates` |
456
+ | Slider Stack | `.slider-stack-wrapper` | `.slider-stack__card`, `.slider-stack__card-lift` | no | `https://design-with-cracka.gitbook.io/etchsliderpro/card-stack-templates` |
457
+ | Slider Marquee | `.slider-marquee-wrapper` | two rows, Infinite Scroll on, Edge Fade on the Wrapper | no | `https://design-with-cracka.gitbook.io/etchsliderpro/premade-templates` |
458
+ | Deck Featured | `.slider-wrapper-deck-featured` | `.deck-stack-featured`, `.deck-card-featured` | yes | `https://design-with-cracka.gitbook.io/etchsliderpro/card-stack-templates` |
459
+ | Deck Testimonial | `.slider-wrapper-deck-testimonial` | `.deck-stack-testimonial`, `.deck-card-testimonial` | yes | `https://design-with-cracka.gitbook.io/etchsliderpro/card-stack-templates` |
460
+ | Fall | `.slider-wrapper-fall` | `.fall-stack`, `.fall-card` | yes | `https://design-with-cracka.gitbook.io/etchsliderpro/card-stack-templates` |
461
+
462
+ Note the two naming schemes: the six slider templates suffix the name (`.slider-flow-wrapper`),
463
+ the three card stacks infix it (`.slider-wrapper-fall`).
464
+
465
+ Five of the nine sync elements outside the track, and they do not all use the same prop. Team and
466
+ Stack set `sliderSetup.syncCustomElement` on the Slider; the three card stacks set
467
+ `sliderlessSync.customElement` on the Wrapper. Zeon pairs sliders by `sliderSetup.sliderRole`
468
+ instead, and Chronos, Flow and Marquee sync nothing.
460
469
 
461
470
  The three that carry a script expose named constants at the top of it. On the two Deck templates,
462
471
  `MAX_TIERS` is the deepest ring the CSS defines and `NAV_RINGS` is how many rings stay clickable.
@@ -142,25 +142,106 @@ etch.blocks.create(json, parentId?, index?) // returns new id
142
142
  etch.components.list() // [{ id, name }, ...]
143
143
  etch.components.getJson(id) // .properties and .blocks
144
144
  etch.styles.list() // [{ id, selector, type, collection, css }]
145
+ etch.styles.create(selector, css) // returns a STYLE ID; pass it to a class prop
146
+ etch.blocks.addClass(blockId, name) // plain elements only; throws on a component
147
+ etch.blocks.getJson(id).script // { code }, the JavaScript code block
145
148
  await etch.saveAsync()
146
149
  ```
147
150
 
148
151
  ### Component instances are the unit of work
149
152
 
150
- A Slider Pro build is a tree of `etch/component` blocks, each carrying `ref` (the component id) and
151
- a flat `attributes` map of prop values:
153
+ A Slider Pro build is a tree of `etch/component` blocks, each naming a component and carrying a
154
+ flat `attributes` map of prop values. **Two different shapes describe the same tree, and mixing
155
+ them up is the first thing that goes wrong in a session.**
156
+
157
+ **The live shape**, which `etch.blocks.getTree()` returns and `etch.blocks.create()` accepts:
152
158
 
153
159
  ```json
154
160
  {
155
- "blockName": "etch/component",
156
- "attrs": {
157
- "metadata": { "name": "- Flow" },
158
- "ref": 6455,
159
- "attributes": { "wrapperHeight": "350px", "spaceBetweenSliders": "50px" }
160
- }
161
+ "type": "etch/component",
162
+ "version": 1,
163
+ "context": {},
164
+ "children": [],
165
+ "componentId": 6455,
166
+ "attributes": { "wrapperHeight": "350px", "spaceBetweenSliders": "50px" }
161
167
  }
162
168
  ```
163
169
 
170
+ `version`, `context` and `children` are required. Block ids are strings (`"ot2wnef"`), not numbers.
171
+
172
+ **The export shape**, which you only ever see inside a premade template `.json` file, uses
173
+ `blockName`, nests under `attrs`, and calls the component id `ref`:
174
+
175
+ ```json
176
+ { "blockName": "etch/component", "attrs": { "ref": 6455, "attributes": {} }, "innerBlocks": [] }
177
+ ```
178
+
179
+ Never pass the export shape to `create()`. It is rejected with
180
+ `Invalid block JSON: expected string, received undefined` at `path: ["type"]`. To put a template on
181
+ the page, hand its whole `gutenbergBlock` to `etch.blocks.pasteAsync()` instead.
182
+
183
+ ### Children go in a slot, never directly on the component
184
+
185
+ A component's children must be wrapped in an `etch/slot-content` node naming the slot they fill.
186
+ Putting a Slider straight inside a Wrapper's `children` renders nothing.
187
+
188
+ ```json
189
+ { "type": "etch/slot-content", "version": 1, "context": {}, "children": [], "slotName": "Slides" }
190
+ ```
191
+
192
+ | Component | Slots |
193
+ | --- | --- |
194
+ | DWC Slider Wrapper | `Sliders_and_Controls` |
195
+ | DWC Slider | `Top__Controls`, `Slides`, `Bottom__Controls` |
196
+ | DWC Slide | `Content` |
197
+ | DWC Slider Pagination | `PaginationButtons` |
198
+ | DWC Slider Nav Button | `Nav_Btn_Content` |
199
+ | DWC Slider Progress, DWC Slider Play-Pause | none |
200
+
201
+ Controls are siblings of the Slider inside `Sliders_and_Controls`, or children of the Slider's own
202
+ `Top__Controls` / `Bottom__Controls`. Both work, because controls find their own slider.
203
+
204
+ ### Classes: two different mechanisms
205
+
206
+ Adding a class to a plain element and adding one to a component are **not the same operation**, and
207
+ the component path is the one that matters here, because everything Slider Pro ships is a component.
208
+
209
+ **On a component**, `addClass` throws `Block "<id>" is not an HTML block.` Classes go through the
210
+ component's own class-typed prop, and the value is the **style id**, not the class name:
211
+
212
+ ```js
213
+ const styleId = etch.styles.create('.flow-demo', 'border-radius: 1rem;'); // returns an id
214
+ etch.blocks.setAttribute(wrapperId, 'customClass', styleId);
215
+ ```
216
+
217
+ Etch resolves the id to the selector name when it renders, so the page gets
218
+ `class="dwc-slider-wrapper flow-demo"`. The id never appears in the output. Store the id, not the
219
+ name, so the class survives being renamed in the Etch UI.
220
+
221
+ The class-typed props, one per component:
222
+
223
+ | Component | Prop |
224
+ | --- | --- |
225
+ | DWC Slider Wrapper | `customClass` |
226
+ | DWC Slider | `sliderClass` |
227
+ | DWC Slide, Progress, Play-Pause, Pagination | `class` |
228
+ | DWC Slider Nav Button | `buttonClass`, `buttonWrapperClass` |
229
+
230
+ **On a plain element**, use `addClass` with the bare class name, no dot and no id:
231
+
232
+ ```js
233
+ const id = etch.blocks.create(el('article', {}, []));
234
+ etch.blocks.addClass(id, 'fall-card');
235
+ ```
236
+
237
+ Passing a style id here does not resolve. It is treated as a literal name and sanitised, so
238
+ `30xsolr` silently becomes the class `xsolr`. Creating the element with `attributes: { class: '...' }`
239
+ works too and is fine when you are building a subtree in one call.
240
+
241
+ Either way the CSS rule itself is `etch.styles.create('.fall-card', '...')`, which is what the
242
+ selector has to match.
243
+
244
+
164
245
  ### Group props and the one-extra-brace rule
165
246
 
166
247
  Most Slider props live in groups (`layout`, `motion`, `slides`, `autoplay`, ...). A group is stored
@@ -305,9 +386,9 @@ unit. `props.pauseSlidersOnHover` on the Wrapper pauses both rows together.
305
386
 
306
387
  Infinite Scroll needs the Auto-Scroll extension enabled in admin settings, which is the default.
307
388
 
308
- ### Synced elements (Zeon, Chronos, Team)
389
+ ### Synced elements (Team, Stack)
309
390
 
310
- The pattern behind the full-bleed hero, the timeline and the staff showcase is one prop.
391
+ Driving arbitrary elements on the page from the slider is one prop.
311
392
 
312
393
  ```js
313
394
  setGroup(sliderId, 'sliderSetup', { syncCustomElement: '.timeline-node',
@@ -319,8 +400,23 @@ moves, and with nav on, clicking one jumps the slider to it. **You write the CSS
319
400
  states.** The elements do not need to be inside the slider and do not need to match the slide
320
401
  count.
321
402
 
403
+ The selector takes a comma-separated list, so one slider can drive several groups at once. Team
404
+ uses `'.slider-team__sync, .slider-team__sync-heading'` to move a portrait and a heading together.
405
+
322
406
  Grep `## 3. Sync Custom Element` in the reference for multiple selectors and the overlap caveat.
323
407
 
408
+ ### Main and thumbnails in one wrapper (Zeon)
409
+
410
+ A full-bleed hero with a thumbnail strip needs no sync prop at all, just roles:
411
+
412
+ ```js
413
+ setGroup(mainId, 'sliderSetup', { sliderRole: 'main', transitionType: 'Fade' });
414
+ setGroup(thumbId, 'sliderSetup', { sliderRole: 'thumbnails', transitionType: 'Loop' });
415
+ ```
416
+
417
+ Both inside the same Wrapper and they pair automatically. A Wrapper can hold more than one
418
+ thumbnail slider, which is how Zeon runs a background layer and a strip off the same main.
419
+
324
420
  ### Carousel on mobile, grid on desktop
325
421
 
326
422
  ```js
@@ -345,25 +441,89 @@ setGroup(wrapperId, 'sliderlessSync', {
345
441
 
346
442
  CSS handles the front card and its two neighbours from `is-active` / `is-prev` / `is-next`. What
347
443
  CSS cannot do is know that a given card is *three* back, so the rows behind, the z-index ladder and
348
- the click targets need a script. That is case 1 of the native-first gate. The shipped script does
349
- exactly three things:
444
+ the click targets need a script. That is case 1 of the native-first gate.
445
+
446
+ The contract between that script and the CSS is one attribute, **`data-pos`**, which the script
447
+ derives from `is-active` and writes on every card. It is a **signed** offset from the active card,
448
+ so the CSS can fan the left and right sides in opposite directions:
449
+
450
+ ```css
451
+ .deck-stack-featured &:is([data-pos='2'], [data-pos='-2']) { /* ring 2, both sides */ }
452
+ ```
453
+
454
+ The stack container carries **`data-tiers`**, how many rings are actually in play. It is computed,
455
+ not fixed: a looping deck needs spare cards to hide the wrap behind, so the script reduces the
456
+ tiers when there are too few cards.
457
+
458
+ Writing an unsigned rank, or naming the attribute anything else, produces a flat pile: the rules
459
+ above simply never match. The shipped script does this:
350
460
 
351
461
  ```js
352
462
  // 1. make a custom property interpolable, so a blur can animate (case 2)
353
463
  CSS.registerProperty({ name: '--stack-focus', syntax: '<number>',
354
464
  inherits: true, initialValue: '0' });
355
465
 
356
- // 2. per card: distance from the active one, capped at the deepest tier the CSS defines
357
- var rank = Math.abs(distanceFromActive);
358
- card.setAttribute('data-tier', Math.min(rank, MAX_TIERS));
466
+ // 2. how many rings this deck can afford, given looping and the card count
467
+ var mayLoop = wrap.dataset.loop === 'true' && total >= MIN_LOOP;
468
+ stack.dataset.tiers = mayLoop ? Math.min(MAX_TIERS, Math.floor((total - 3) / 2)) : MAX_TIERS;
359
469
 
360
- // 3. hit-testing: stack order by rank, and clicks off beyond the clickable rings
470
+ // 3. per card: SIGNED offset from the active card. On a looping deck the offset
471
+ // wraps, so the far end of the list reads as the near side, not as distance 8.
472
+ var d = n - active;
473
+ if (mayLoop) {
474
+ if (d > total / 2) d -= total;
475
+ if (d < -total / 2) d += total;
476
+ }
477
+ card.dataset.pos = d;
478
+
479
+ // 4. hit-testing: stack order by distance, and clicks off beyond the clickable rings
480
+ var rank = Math.abs(d);
361
481
  card.style.zIndex = String(total - rank);
362
482
  lift.style.pointerEvents = rank <= NAV_RINGS ? 'auto' : 'none';
363
483
  ```
364
484
 
365
- Step 3 is not optional. Stacked cards are coplanar, so the browser hit-tests them by DOM order and
366
- a click near the front card can land on one three positions away.
485
+ Do not clamp `data-pos` to the tier count. Cards past the deepest ring should match no rule and
486
+ stay stacked at the back; clamping piles them onto the last visible ring instead.
487
+
488
+ Both decks also set `--i` on each line of card content so the CSS can stagger it:
489
+
490
+ ```js
491
+ card.querySelectorAll('.deck-card-featured__content > *')
492
+ .forEach(function (line, j) { line.style.setProperty('--i', j); });
493
+ ```
494
+
495
+ Step 4 is Deck only. **Fall's script is much smaller**: it sets `--i` and a signed `data-pos`, and
496
+ nothing else. Fall never loops, so there is no wrap to fold and no z-index ladder to maintain,
497
+ because the cards fall past each other rather than fanning around a front card.
498
+
499
+ Recompute on every change: watch the stack with a `MutationObserver` filtered to `class`, since
500
+ `is-active` moving is the only signal you get.
501
+
502
+ ### Where a script goes
503
+
504
+ **Not in a `<script>` element.** Etch silently drops one from the render, so the page looks right
505
+ in the builder and ships with no behaviour at all.
506
+
507
+ Every block has an optional `script` field (`EtchBlockScript`), which is Etch's JavaScript code
508
+ block. Put the code on the **component instance that owns the markup**, which for a card stack is
509
+ the Wrapper:
510
+
511
+ ```js
512
+ const wrapperId = etch.blocks.create({
513
+ type: 'etch/component', version: 1, context: {}, children: [ /* ... */ ],
514
+ componentId: C['DWC Slider Wrapper'], attributes: {},
515
+ script: { code: "(function () { /* ... */ })();" }
516
+ });
517
+ ```
518
+
519
+ `code` is **plain source** over the API. The base64 you see in a premade template `.json` is only
520
+ how the export serialises it; do not encode it yourself.
521
+
522
+ Etch renders it as `<script type="module" defer>`, so it runs after parsing and top-level `await`
523
+ is available. Read it back with `etch.blocks.getJson(id).script`.
524
+
525
+ Component scripts are also the reason a card deck keeps working: the bridge leaves Sync Without
526
+ Slider wrappers unreconstructed precisely so an author's script keeps observing the original nodes.
367
527
 
368
528
  Card counts for a looping deck: three are always visible (front plus two), and a loop needs a
369
529
  hidden slot at each end, so **five is the minimum** and nine gives the full three-row fan. Fall
@@ -392,19 +552,30 @@ function setGroup(id, key, patch) {
392
552
  etch.blocks.setAttribute(id, key, '{' + JSON.stringify(next) + '}');
393
553
  }
394
554
 
395
- function component(ref, attributes, children) {
396
- return {
397
- blockName: 'etch/component',
398
- attrs: { ref: ref, attributes: attributes || {} },
399
- innerBlocks: children || []
400
- };
555
+ function node(extra, children) {
556
+ return Object.assign({ version: 1, context: {}, children: children || [] }, extra);
401
557
  }
402
558
 
403
- function findByRef(nodes, ref, out) {
559
+ function component(componentId, attributes, children) {
560
+ return node({ type: 'etch/component', componentId: componentId,
561
+ attributes: attributes || {} }, children);
562
+ }
563
+
564
+ function slot(slotName, children) {
565
+ return node({ type: 'etch/slot-content', slotName: slotName }, children);
566
+ }
567
+
568
+ function el(tag, attributes, children) {
569
+ return node({ type: 'etch/element', tag: tag, attributes: attributes || {} }, children);
570
+ }
571
+
572
+ function text(t) { return node({ type: 'etch/text', text: t }); }
573
+
574
+ function findByRef(nodes, componentId, out) {
404
575
  out = out || [];
405
576
  for (const n of nodes || []) {
406
- if (n.componentId === ref || (n.attrs && n.attrs.ref === ref)) out.push(n);
407
- findByRef(n.children || n.innerBlocks || [], ref, out);
577
+ if (n.componentId === componentId) out.push(n);
578
+ findByRef(n.children, componentId, out);
408
579
  }
409
580
  return out;
410
581
  }
@@ -415,18 +586,27 @@ function findByRef(nodes, ref, out) {
415
586
  ```js
416
587
  const C = comps();
417
588
  const slides = [];
418
- for (let i = 0; i < 5; i++) slides.push(component(C['DWC Slide'], {}));
589
+ for (let i = 0; i < 5; i++) {
590
+ slides.push(component(C['DWC Slide'], {}, [
591
+ slot('Content', [ el('div', {}, [ text('Slide ' + (i + 1)) ]) ])
592
+ ]));
593
+ }
419
594
 
420
595
  const id = etch.blocks.create(
421
596
  component(C['DWC Slider Wrapper'], {}, [
422
- component(C['DWC Slider'], {}, slides),
423
- component(C['DWC Slider Nav Button'], {}),
424
- component(C['DWC Slider Pagination'], {})
597
+ slot('Sliders_and_Controls', [
598
+ component(C['DWC Slider'], {}, [ slot('Slides', slides) ]),
599
+ component(C['DWC Slider Nav Button'], {}),
600
+ component(C['DWC Slider Pagination'], {})
601
+ ])
425
602
  ])
426
603
  );
427
604
  return id;
428
605
  ```
429
606
 
607
+ An empty Slide has no content and so no height. Give slides something to size before wondering why
608
+ the slider is a flat line.
609
+
430
610
  Build **one** first, screenshot it, then scale to the full count. Do not generate twenty slides
431
611
  before you have seen one render.
432
612