@port60/template-kit 0.14.2 → 0.15.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/package.json +1 -1
- package/src/commands/packageCmd.mjs +4 -2
- package/src/commands/publish.mjs +2 -1
- package/src/lib/agentsMd.mjs +5 -2
- package/src/lib/artifactFiles.mjs +41 -1
- package/src/vendor/contract/v1/behaviours.json +15 -0
- package/src/vendor/contract/v1/manifest.schema.json +1 -1
- package/src/vendor/validator/behaviors-runtime.js +1 -1
- package/src/vendor/validator/validate.mjs +8 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@port60/template-kit",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.15.0",
|
|
4
4
|
"description": "Build Port60 site templates locally: scaffold, live-preview, validate against the platform contract, and package for studio upload. AI-agent ready \u2014 every scaffold ships AGENTS.md and validate emits machine-readable JSON.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { mkdirSync, rmSync, writeFileSync } from 'node:fs';
|
|
2
2
|
import { resolve, join } from 'node:path';
|
|
3
3
|
import { validateArtifact } from '../vendor/validator/validate.mjs';
|
|
4
|
-
import { loadArtifactDir } from '../lib/artifactFiles.mjs';
|
|
4
|
+
import { loadArtifactDir, skippedNotice } from '../lib/artifactFiles.mjs';
|
|
5
5
|
import { buildZip } from '../lib/zip.mjs';
|
|
6
6
|
|
|
7
7
|
/**
|
|
@@ -9,7 +9,8 @@ import { buildZip } from '../lib/zip.mjs';
|
|
|
9
9
|
* saves the round trip), then zip EXACTLY the contract-shaped file set into
|
|
10
10
|
* `dist/<name>-<version>.zip`, the artifact the studio's upload lane accepts as-is. `dist/` is
|
|
11
11
|
* the build output and nothing else: recreated on every run, so a stale archive from a previous
|
|
12
|
-
* version can never sit beside the fresh one, and gitignored by the scaffold.
|
|
12
|
+
* version can never sit beside the fresh one, and gitignored by the scaffold. Anything the rules
|
|
13
|
+
* left out (an image in assets/, a stray script) is listed under the result, never silently.
|
|
13
14
|
*/
|
|
14
15
|
export async function packageCmd(args) {
|
|
15
16
|
const dir = resolve(args._[0] ?? '.');
|
|
@@ -26,5 +27,6 @@ export async function packageCmd(args) {
|
|
|
26
27
|
const out = join(outDir, `${manifest.name}-${manifest.version}.zip`);
|
|
27
28
|
writeFileSync(out, buildZip(Object.entries(files).map(([path, content]) => ({ path, content }))));
|
|
28
29
|
console.log(`✓ ${out}`);
|
|
30
|
+
for (const line of skippedNotice(dir)) console.log(line);
|
|
29
31
|
console.log(' Upload it from your studio (tenant admin → Studio → your template → Versions).');
|
|
30
32
|
}
|
package/src/commands/publish.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { resolve } from 'node:path';
|
|
2
2
|
import { validateArtifact } from '../vendor/validator/validate.mjs';
|
|
3
|
-
import { loadArtifactDir } from '../lib/artifactFiles.mjs';
|
|
3
|
+
import { loadArtifactDir, skippedNotice } from '../lib/artifactFiles.mjs';
|
|
4
4
|
import { buildZip } from '../lib/zip.mjs';
|
|
5
5
|
import { accessToken, api, apiBase, loadCredentials, requireCredentials } from '../lib/auth.mjs';
|
|
6
6
|
|
|
@@ -44,6 +44,7 @@ export async function publish(args) {
|
|
|
44
44
|
}
|
|
45
45
|
const zip = buildZip(Object.entries(files).map(([path, content]) => ({ path, content })));
|
|
46
46
|
console.log(`✓ ${manifest.name} ${manifest.version} validated, uploading${ci ? ' from CI' : ''}…`);
|
|
47
|
+
for (const line of skippedNotice(dir)) console.log(line);
|
|
47
48
|
|
|
48
49
|
if (ci) {
|
|
49
50
|
await publishFromCi(args, zip);
|
package/src/lib/agentsMd.mjs
CHANGED
|
@@ -34,8 +34,11 @@ platform accepts it; if it fails here, the upload will fail identically.
|
|
|
34
34
|
\`supports.sections\`. Charities compose pages from a **closed catalogue** of section types
|
|
35
35
|
(see \`npm run validate\` output or the docs), you cannot invent new types.
|
|
36
36
|
- \`pages/<page>.liquid\`, optional full-page templates for \`supports.pageTemplates\`.
|
|
37
|
-
- \`assets/theme.css\`, required, your entire look
|
|
38
|
-
|
|
37
|
+
- \`assets/theme.css\`, required, your entire look, and the ONLY stylesheet the platform loads
|
|
38
|
+
(other \`.css\` files under \`assets/\` are packaged but never loaded, so keep everything in
|
|
39
|
+
it). **No images, no fonts, no JS**: \`package\` and \`publish\` leave them out and list what
|
|
40
|
+
they left out; the upload refuses them. Photographs belong in the charity's media library;
|
|
41
|
+
decorative textures go inline in the CSS as data URIs.
|
|
39
42
|
|
|
40
43
|
## The rules the validator enforces (do not fight them)
|
|
41
44
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// The contract-shaped file set of an artifact directory
|
|
1
|
+
// The contract-shaped file set of an artifact directory, the same selection the platform's
|
|
2
2
|
// upload intake allow-lists: manifest, layout, section/page renderers, css assets. Anything else
|
|
3
3
|
// in the directory is not part of an artifact and is neither validated nor packaged.
|
|
4
4
|
import { readFileSync, readdirSync, existsSync } from 'node:fs';
|
|
@@ -26,3 +26,43 @@ export function loadArtifactDir(root) {
|
|
|
26
26
|
}
|
|
27
27
|
return files;
|
|
28
28
|
}
|
|
29
|
+
|
|
30
|
+
// What the artifact rules leave behind. A photograph, a font or a script dropped next to the
|
|
31
|
+
// renderers is the classic surprise: it may even show in a local page, then the zip and the upload
|
|
32
|
+
// carry nothing of it. `package` and `publish` print this list so the author learns it from the
|
|
33
|
+
// kit, not from a broken URL on a live site. Project files that legitimately live beside a
|
|
34
|
+
// template (README, package.json, node_modules, dist, preview content, design notes) are not
|
|
35
|
+
// reported; only the three artifact folders and top-level binaries are.
|
|
36
|
+
const ACCEPTED = { sections: /\.liquid$/, pages: /\.liquid$/, assets: /\.css$/ };
|
|
37
|
+
const TOP_LEVEL_BINARY = /\.(png|jpe?g|gif|webp|avif|svg|ico|woff2?|ttf|otf|eot|mp4|webm|mp3|m4a|js|mjs|cjs|ts)$/i;
|
|
38
|
+
|
|
39
|
+
export function listSkippedFiles(root) {
|
|
40
|
+
const skipped = [];
|
|
41
|
+
const walk = (dir, rel, accept, depth) => {
|
|
42
|
+
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
43
|
+
if (entry.name.startsWith('.')) continue;
|
|
44
|
+
const path = `${rel}/${entry.name}`;
|
|
45
|
+
if (entry.isDirectory()) walk(join(dir, entry.name), path, accept, depth + 1);
|
|
46
|
+
// Only the folder's own files are read; anything nested is left out whatever its name.
|
|
47
|
+
else if (depth > 0 || !accept.test(entry.name)) skipped.push(path);
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
for (const [dir, accept] of Object.entries(ACCEPTED)) {
|
|
51
|
+
if (existsSync(join(root, dir))) walk(join(root, dir), dir, accept, 0);
|
|
52
|
+
}
|
|
53
|
+
for (const entry of readdirSync(root, { withFileTypes: true })) {
|
|
54
|
+
if (entry.isFile() && TOP_LEVEL_BINARY.test(entry.name)) skipped.push(entry.name);
|
|
55
|
+
}
|
|
56
|
+
return skipped.sort();
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** The lines `package`/`publish` print under the result when something was left out; empty otherwise. */
|
|
60
|
+
export function skippedNotice(root) {
|
|
61
|
+
const skipped = listSkippedFiles(root);
|
|
62
|
+
if (skipped.length === 0) return [];
|
|
63
|
+
return [
|
|
64
|
+
` left out (not part of a template): ${skipped.join(', ')}`,
|
|
65
|
+
' Templates ship Liquid and CSS only; photographs belong in the charity\'s media library.',
|
|
66
|
+
' See https://developers.port60.com/guides/publishing/#what-the-zip-contains'
|
|
67
|
+
];
|
|
68
|
+
}
|
|
@@ -124,6 +124,21 @@
|
|
|
124
124
|
"stateClasses": ["is-active"],
|
|
125
125
|
"cssVars": ["--p60-carousel-index"],
|
|
126
126
|
"injects": [".p60-carousel-dots", ".p60-carousel-dot", ".p60-carousel-dot--active"]
|
|
127
|
+
},
|
|
128
|
+
{
|
|
129
|
+
"name": "nav",
|
|
130
|
+
"status": "available",
|
|
131
|
+
"label": "Navigation",
|
|
132
|
+
"description": "Your menu, made robust. You design the menu (markup, every pixel, the breakpoint); the engine supplies the interaction CSS :hover cannot: data-p60-nav on each nav root (several allowed), data-p60-nav-item on each group holding a data-p60-nav-toggle control and a data-p60-nav-menu panel (nesting allowed). Hover-capable pointers get hover intent (a short delay to open, a grace period to close) so crossing the gap between a toggle and its panel never closes it; keyboard, touch and pen toggle on the control (Enter/click, ArrowDown opens and focuses the first link; a mouse click on an already-hovered parent link navigates); Escape closes and returns focus to the toggle; a click outside or focus moving away closes; one item stays open per level and aria-expanded is kept honest. The burger: data-p60-nav-burger on a button whose aria-controls (or the attribute value) names the stacked panel; the engine toggles is-open on both, closes on Escape (returning focus), on a real navigation inside the panel, on an outside click, and when the burger disappears at a wider viewport. State is is-open on the item, the panel and the burger; under .p60-js show a panel only on is-open and keep your :hover/:focus-within rules scoped to html:not(.p60-js) for the no-JS render.",
|
|
133
|
+
"attributes": [
|
|
134
|
+
{ "attr": "data-p60-nav", "value": "none; a nav root, several allowed" },
|
|
135
|
+
{ "attr": "data-p60-nav-item", "value": "none; a group holding one toggle and one menu, nestable" },
|
|
136
|
+
{ "attr": "data-p60-nav-toggle", "value": "none; the item's control (a link or button)" },
|
|
137
|
+
{ "attr": "data-p60-nav-menu", "value": "none; the item's panel" },
|
|
138
|
+
{ "attr": "data-p60-nav-burger", "value": "none, or the id of the panel when aria-controls is absent" }
|
|
139
|
+
],
|
|
140
|
+
"stateClasses": ["is-open"],
|
|
141
|
+
"cssVars": []
|
|
127
142
|
}
|
|
128
143
|
]
|
|
129
144
|
}
|
|
@@ -115,7 +115,7 @@
|
|
|
115
115
|
"type": "array",
|
|
116
116
|
"items": {
|
|
117
117
|
"type": "string",
|
|
118
|
-
"enum": ["reveal", "counter", "progress", "countdown", "accordion", "carousel", "stickyHeader", "stickyCta", "lightbox", "tabs"]
|
|
118
|
+
"enum": ["reveal", "counter", "progress", "countdown", "accordion", "carousel", "stickyHeader", "stickyCta", "lightbox", "tabs", "nav"]
|
|
119
119
|
},
|
|
120
120
|
"uniqueItems": true,
|
|
121
121
|
"description": "Engine-attached behaviours this template's markup opts into via data-p60-* attributes (see the behaviour catalogue). Templates never ship JavaScript; declaring here is what makes the engine wire the markup, and the validator proves declaration and usage agree in both directions."
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
// GENERATED by scripts/build-behaviors-runtime.mjs — do not edit by hand.
|
|
2
|
-
"use strict";var p60Behaviors=(()=>{var L=Object.defineProperty;var w=Object.getOwnPropertyDescriptor;var x=Object.getOwnPropertyNames;var M=Object.prototype.hasOwnProperty;var k=(r,i)=>{for(var t in i)L(r,t,{get:i[t],enumerable:!0})},S=(r,i,t,n)=>{if(i&&typeof i=="object"||typeof i=="function")for(let e of x(i))!M.call(r,e)&&e!==t&&L(r,e,{get:()=>i[e],enumerable:!(n=w(i,e))||n.enumerable});return r};var T=r=>S(L({},"__esModule",{value:!0}),r);var R={};k(R,{initBehaviors:()=>K});var A=()=>window.matchMedia("(prefers-reduced-motion: reduce)").matches;function v(r,i){let t=`data-p60-bound-${i}`;return r.hasAttribute(t)?!0:(r.setAttribute(t,""),!1)}function E(r,i){if(r.length===0)return;if(A()||!("IntersectionObserver"in window)){r.forEach(i);return}let t=new IntersectionObserver(n=>{for(let e of n)e.isIntersecting&&(t.unobserve(e.target),i(e.target))},{rootMargin:"0px 0px -10% 0px",threshold:.15});r.forEach(n=>t.observe(n))}function H(r){let i=Array.from(r.querySelectorAll("[data-p60-reveal-group]")).filter(n=>!v(n,"reveal-group"));for(let n of i)Array.from(n.children).forEach((e,o)=>{e.style.setProperty("--p60-reveal-index",String(o)),e.classList.add("p60-reveal-child")});E(i,n=>n.classList.add("is-revealed"));let t=Array.from(r.querySelectorAll("[data-p60-reveal]")).filter(n=>!v(n,"reveal"));E(t,n=>n.classList.add("is-revealed"))}function q(r){let i=Array.from(r.querySelectorAll("[data-p60-count]")).filter(t=>!v(t,"count"));E(i,t=>{t.classList.add("is-revealed");let n=Number.parseInt(t.getAttribute("data-p60-count")??"",10);if(!Number.isFinite(n)||A())return;let e=Math.min(Number.parseInt(t.getAttribute("data-p60-count-duration")??"1200",10)||1200,4e3),o=new Intl.NumberFormat(document.documentElement.lang||void 0),d=performance.now(),s=a=>{let c=Math.min((a-d)/e,1),u=1-Math.pow(1-c,3);t.textContent=o.format(Math.round(n*u)),c<1&&requestAnimationFrame(s)};requestAnimationFrame(s)})}function N(r){let i=Array.from(r.querySelectorAll("[data-p60-progress]")).filter(t=>!v(t,"progress"));if(!A()){for(let t of i){let n=t.style.width||getComputedStyle(t).width;t.style.transition="none",t.style.width="0px",t.offsetWidth,t.style.transition="width 1.1s cubic-bezier(0.22, 1, 0.36, 1)",t.dataset.p60ProgressTarget=n}E(i,t=>{t.classList.add("is-revealed"),t.style.width=t.dataset.p60ProgressTarget??""})}}function P(r){let i=Array.from(r.querySelectorAll("[data-p60-countdown]")).filter(t=>!v(t,"countdown"));for(let t of i){let n=Date.parse(t.getAttribute("data-p60-until")??"");if(!Number.isFinite(n))continue;let e=t.getAttribute("data-p60-countdown")||"dhm",o=e==="dhms",d=()=>{let a=n-Date.now();if(a<=0){t.classList.add("is-elapsed");let l=t.getAttribute("data-p60-elapsed-text");l&&(t.textContent=l),window.clearInterval(s);return}let c=Math.floor(a/1e3),u=Math.floor(c/86400),f=Math.floor(c%86400/3600),g=Math.floor(c%3600/60),m=c%60,h=[];e.includes("d")&&u>0&&h.push(`${u}d`),h.push(`${f}h`,`${g}m`),o&&h.push(`${m}s`),t.textContent=h.join(" ")},s=window.setInterval(d,o?1e3:15e3);d()}}function C(r){let i=Array.from(r.querySelectorAll("[data-p60-accordion]")).filter(t=>!v(t,"accordion"));for(let t of i){let n=t.getAttribute("data-p60-accordion")!=="multi",e=Array.from(t.querySelectorAll("details"));for(let o of e){o.addEventListener("toggle",()=>{if(o.classList.toggle("is-open",o.open),o.open&&n)for(let a of e)a!==o&&a.open&&(a.open=!1)}),o.open&&o.classList.add("is-open");let s=o.querySelector("summary")?.nextElementSibling;s&&!A()&&o.addEventListener("toggle",()=>{if(!o.open)return;let a=s.scrollHeight;s.style.overflow="hidden",s.animate([{height:"0px",opacity:"0"},{height:`${a}px`,opacity:"1"}],{duration:260,easing:"ease-out"}).addEventListener("finish",()=>{s.style.overflow=""})})}}}function I(r){let i=Array.from(r.querySelectorAll("[data-p60-carousel]")).filter(t=>!v(t,"carousel"));for(let t of i){let n=Array.from(t.querySelectorAll("[data-p60-slide]"));if(n.length<2)continue;let e=Math.max(0,n.findIndex(l=>l.classList.contains("is-active"))),o=!1,d=[],s=document.createElement("nav");s.className="p60-carousel-dots",s.setAttribute("aria-label","Slides"),n.forEach((l,y)=>{let p=document.createElement("button");p.type="button",p.className="p60-carousel-dot",p.setAttribute("aria-label",`Go to slide ${y+1}`),p.addEventListener("click",()=>a(y,!0)),d.push(p),s.appendChild(p)}),t.appendChild(s),t.setAttribute("aria-roledescription","carousel"),t.hasAttribute("tabindex")||t.setAttribute("tabindex","0");let a=(l,y=!1)=>{e=(l+n.length)%n.length,n.forEach((p,b)=>{p.classList.toggle("is-active",b===e),b===e?(p.removeAttribute("aria-hidden"),p.removeAttribute("inert")):(p.setAttribute("aria-hidden","true"),p.setAttribute("inert",""))}),d.forEach((p,b)=>{p.classList.toggle("p60-carousel-dot--active",b===e),p.setAttribute("aria-current",b===e?"true":"false")}),t.style.setProperty("--p60-carousel-index",String(e)),y&&h()};t.querySelectorAll("[data-p60-carousel-prev]").forEach(l=>l.addEventListener("click",()=>a(e-1,!0))),t.querySelectorAll("[data-p60-carousel-next]").forEach(l=>l.addEventListener("click",()=>a(e+1,!0))),t.addEventListener("keydown",l=>{l.key==="ArrowLeft"&&a(e-1,!0),l.key==="ArrowRight"&&a(e+1,!0)});let c=0,u=0;t.addEventListener("pointerdown",l=>{c=l.clientX,u=l.clientY}),t.addEventListener("pointerup",l=>{let y=l.clientX-c,p=l.clientY-u;Math.abs(y)>40&&Math.abs(y)>Math.abs(p)&&a(e+(y<0?1:-1),!0)}),t.addEventListener("mouseenter",()=>{o=!0}),t.addEventListener("mouseleave",()=>{o=!1}),t.addEventListener("focusin",()=>{o=!0}),t.addEventListener("focusout",()=>{o=!1});let f,g=t.getAttribute("data-p60-carousel")==="auto"&&!A(),m=Math.max(Number.parseInt(t.getAttribute("data-p60-interval")??"6000",10)||6e3,3e3),h=()=>{f!==void 0&&window.clearInterval(f),g&&(f=window.setInterval(()=>{!o&&!document.hidden&&a(e+1)},m))};a(e),h()}}function F(r){let i=Array.from(r.querySelectorAll("[data-p60-sticky-header]")).filter(t=>!v(t,"sticky-header"));for(let t of i){let n=t.getAttribute("data-p60-sticky-header")==="auto-hide",e=window.scrollY,o=!1,d=()=>{let s=window.scrollY;t.classList.toggle("is-condensed",s>24),n&&(s>160&&s>e?t.classList.add("is-hidden"):t.classList.remove("is-hidden")),e=s,o=!1};window.addEventListener("scroll",()=>{o||(o=!0,requestAnimationFrame(d))},{passive:!0}),d()}}function D(r){let i=Array.from(r.querySelectorAll("[data-p60-sticky-cta]")).filter(t=>!v(t,"sticky-cta"));for(let t of i){let n=Number.parseInt(t.getAttribute("data-p60-sticky-cta")??"480",10)||480,e=!1,o=()=>{t.classList.toggle("is-stuck",window.scrollY>n),e=!1};window.addEventListener("scroll",()=>{e||(e=!0,requestAnimationFrame(o))},{passive:!0}),o()}}function Y(r){let i=Array.from(r.querySelectorAll("[data-p60-lightbox]")).filter(t=>!v(t,"lightbox"));for(let t of i){let n=Array.from(t.querySelectorAll("a[data-p60-lightbox-item]"));if(n.length===0)continue;let e=null,o=0,d=null,s=()=>{e?.remove(),e=null,t.classList.remove("is-open"),document.removeEventListener("keydown",c),d?.focus()},a=f=>{o=(f+n.length)%n.length;let g=n[o];if(!e)return;let m=e.querySelector(".p60-lightbox-img"),h=e.querySelector(".p60-lightbox-caption");m&&(m.src=g.href);let l=g.getAttribute("data-p60-caption")??g.querySelector("img")?.alt??"";h&&(h.textContent=l),m&&(m.alt=l)},c=f=>{if(f.key==="Escape"&&s(),f.key==="ArrowLeft"&&a(o-1),f.key==="ArrowRight"&&a(o+1),f.key==="Tab"&&e){let g=Array.from(e.querySelectorAll("button")),m=g.indexOf(document.activeElement),h=f.shiftKey?m<=0?g.length-1:m-1:m>=g.length-1?0:m+1;g[h]?.focus(),f.preventDefault()}},u=(f,g)=>{d=g,e=document.createElement("div"),e.className="p60-lightbox",e.setAttribute("role","dialog"),e.setAttribute("aria-modal","true"),A()&&(e.style.transition="none"),e.innerHTML='<figure><img class="p60-lightbox-img" alt=""><figcaption class="p60-lightbox-caption"></figcaption></figure>';let m=(l,y,p)=>{let b=document.createElement("button");return b.type="button",b.className=l,b.setAttribute("aria-label",y),b.addEventListener("click",p),e.appendChild(b),b};n.length>1&&(m("p60-lightbox-prev","Previous image",()=>a(o-1)),m("p60-lightbox-next","Next image",()=>a(o+1)));let h=m("p60-lightbox-close","Close",s);e.addEventListener("click",l=>{l.target===e&&s()}),document.body.appendChild(e),t.classList.add("is-open"),document.addEventListener("keydown",c),a(f),h.focus()};n.forEach((f,g)=>f.addEventListener("click",m=>{m.preventDefault(),u(g,f)}))}}function $(r){let i=Array.from(r.querySelectorAll("[data-p60-tabs]")).filter(t=>!v(t,"tabs"));for(let t of i){let n=Array.from(t.querySelectorAll("[data-p60-tab]")),e=Array.from(t.querySelectorAll("[data-p60-panel]"));if(n.length===0||e.length===0)continue;let o=s=>e.find(a=>a.getAttribute("data-p60-panel")===s.getAttribute("data-p60-tab"));n[0].closest("[data-p60-tabs] > *")?.setAttribute("role","tablist");let d=(s,a=!1)=>{for(let c of n){let u=c===s;c.classList.toggle("is-active",u),c.setAttribute("aria-selected",u?"true":"false"),c.setAttribute("tabindex",u?"0":"-1")}for(let c of e){let u=c===o(s);c.classList.toggle("is-active",u),c.hidden=!u}a&&s.focus()};n.forEach((s,a)=>{s.setAttribute("role","tab"),s.addEventListener("click",c=>{c.preventDefault(),d(s)}),s.addEventListener("keydown",c=>{let u=c.key;u==="ArrowLeft"&&d(n[(a-1+n.length)%n.length],!0),u==="ArrowRight"&&d(n[(a+1)%n.length],!0),u==="Home"&&d(n[0],!0),u==="End"&&d(n[n.length-1],!0)})}),e.forEach(s=>s.setAttribute("role","tabpanel")),d(n.find(s=>s.classList.contains("is-active"))??n[0])}}function K(r=document){document.documentElement.classList.add("p60-js"),H(r),q(r),N(r),P(r),C(r),I(r),F(r),D(r),Y(r),$(r)}return T(R);})();
|
|
2
|
+
"use strict";var p60Behaviors=(()=>{var M=Object.defineProperty;var H=Object.getOwnPropertyDescriptor;var N=Object.getOwnPropertyNames;var P=Object.prototype.hasOwnProperty;var q=(n,s)=>{for(var e in s)M(n,e,{get:s[e],enumerable:!0})},I=(n,s,e,o)=>{if(s&&typeof s=="object"||typeof s=="function")for(let t of N(s))!P.call(n,t)&&t!==e&&M(n,t,{get:()=>s[t],enumerable:!(o=H(s,t))||o.enumerable});return n};var C=n=>I(M({},"__esModule",{value:!0}),n);var W={};q(W,{initBehaviors:()=>G});var L=()=>window.matchMedia("(prefers-reduced-motion: reduce)").matches;function y(n,s){let e=`data-p60-bound-${s}`;return n.hasAttribute(e)?!0:(n.setAttribute(e,""),!1)}function T(n,s){if(n.length===0)return;if(L()||!("IntersectionObserver"in window)){n.forEach(s);return}let e=new IntersectionObserver(o=>{for(let t of o)t.isIntersecting&&(e.unobserve(t.target),s(t.target))},{rootMargin:"0px 0px -10% 0px",threshold:.15});n.forEach(o=>e.observe(o))}function O(n){let s=Array.from(n.querySelectorAll("[data-p60-reveal-group]")).filter(o=>!y(o,"reveal-group"));for(let o of s)Array.from(o.children).forEach((t,a)=>{t.style.setProperty("--p60-reveal-index",String(a)),t.classList.add("p60-reveal-child")});T(s,o=>o.classList.add("is-revealed"));let e=Array.from(n.querySelectorAll("[data-p60-reveal]")).filter(o=>!y(o,"reveal"));T(e,o=>o.classList.add("is-revealed"))}function D(n){let s=Array.from(n.querySelectorAll("[data-p60-count]")).filter(e=>!y(e,"count"));T(s,e=>{e.classList.add("is-revealed");let o=Number.parseInt(e.getAttribute("data-p60-count")??"",10);if(!Number.isFinite(o)||L())return;let t=Math.min(Number.parseInt(e.getAttribute("data-p60-count-duration")??"1200",10)||1200,4e3),a=new Intl.NumberFormat(document.documentElement.lang||void 0),c=performance.now(),r=i=>{let d=Math.min((i-c)/t,1),l=1-Math.pow(1-d,3);e.textContent=a.format(Math.round(o*l)),d<1&&requestAnimationFrame(r)};requestAnimationFrame(r)})}function B(n){let s=Array.from(n.querySelectorAll("[data-p60-progress]")).filter(e=>!y(e,"progress"));if(!L()){for(let e of s){let o=e.style.width||getComputedStyle(e).width;e.style.transition="none",e.style.width="0px",e.offsetWidth,e.style.transition="width 1.1s cubic-bezier(0.22, 1, 0.36, 1)",e.dataset.p60ProgressTarget=o}T(s,e=>{e.classList.add("is-revealed"),e.style.width=e.dataset.p60ProgressTarget??""})}}function F(n){let s=Array.from(n.querySelectorAll("[data-p60-countdown]")).filter(e=>!y(e,"countdown"));for(let e of s){let o=Date.parse(e.getAttribute("data-p60-until")??"");if(!Number.isFinite(o))continue;let t=e.getAttribute("data-p60-countdown")||"dhm",a=t==="dhms",c=()=>{let i=o-Date.now();if(i<=0){e.classList.add("is-elapsed");let u=e.getAttribute("data-p60-elapsed-text");u&&(e.textContent=u),window.clearInterval(r);return}let d=Math.floor(i/1e3),l=Math.floor(d/86400),f=Math.floor(d%86400/3600),g=Math.floor(d%3600/60),m=d%60,v=[];t.includes("d")&&l>0&&v.push(`${l}d`),v.push(`${f}h`,`${g}m`),a&&v.push(`${m}s`),e.textContent=v.join(" ")},r=window.setInterval(c,a?1e3:15e3);c()}}function _(n){let s=Array.from(n.querySelectorAll("[data-p60-accordion]")).filter(e=>!y(e,"accordion"));for(let e of s){let o=e.getAttribute("data-p60-accordion")!=="multi",t=Array.from(e.querySelectorAll("details"));for(let a of t){a.addEventListener("toggle",()=>{if(a.classList.toggle("is-open",a.open),a.open&&o)for(let i of t)i!==a&&i.open&&(i.open=!1)}),a.open&&a.classList.add("is-open");let r=a.querySelector("summary")?.nextElementSibling;r&&!L()&&a.addEventListener("toggle",()=>{if(!a.open)return;let i=r.scrollHeight;r.style.overflow="hidden",r.animate([{height:"0px",opacity:"0"},{height:`${i}px`,opacity:"1"}],{duration:260,easing:"ease-out"}).addEventListener("finish",()=>{r.style.overflow=""})})}}}function R(n){let s=Array.from(n.querySelectorAll("[data-p60-carousel]")).filter(e=>!y(e,"carousel"));for(let e of s){let o=Array.from(e.querySelectorAll("[data-p60-slide]"));if(o.length<2)continue;let t=Math.max(0,o.findIndex(u=>u.classList.contains("is-active"))),a=!1,c=[],r=document.createElement("nav");r.className="p60-carousel-dots",r.setAttribute("aria-label","Slides"),o.forEach((u,b)=>{let p=document.createElement("button");p.type="button",p.className="p60-carousel-dot",p.setAttribute("aria-label",`Go to slide ${b+1}`),p.addEventListener("click",()=>i(b,!0)),c.push(p),r.appendChild(p)}),e.appendChild(r),e.setAttribute("aria-roledescription","carousel"),e.hasAttribute("tabindex")||e.setAttribute("tabindex","0");let i=(u,b=!1)=>{t=(u+o.length)%o.length,o.forEach((p,h)=>{p.classList.toggle("is-active",h===t),h===t?(p.removeAttribute("aria-hidden"),p.removeAttribute("inert")):(p.setAttribute("aria-hidden","true"),p.setAttribute("inert",""))}),c.forEach((p,h)=>{p.classList.toggle("p60-carousel-dot--active",h===t),p.setAttribute("aria-current",h===t?"true":"false")}),e.style.setProperty("--p60-carousel-index",String(t)),b&&v()};e.querySelectorAll("[data-p60-carousel-prev]").forEach(u=>u.addEventListener("click",()=>i(t-1,!0))),e.querySelectorAll("[data-p60-carousel-next]").forEach(u=>u.addEventListener("click",()=>i(t+1,!0))),e.addEventListener("keydown",u=>{u.key==="ArrowLeft"&&i(t-1,!0),u.key==="ArrowRight"&&i(t+1,!0)});let d=0,l=0;e.addEventListener("pointerdown",u=>{d=u.clientX,l=u.clientY}),e.addEventListener("pointerup",u=>{let b=u.clientX-d,p=u.clientY-l;Math.abs(b)>40&&Math.abs(b)>Math.abs(p)&&i(t+(b<0?1:-1),!0)}),e.addEventListener("mouseenter",()=>{a=!0}),e.addEventListener("mouseleave",()=>{a=!1}),e.addEventListener("focusin",()=>{a=!0}),e.addEventListener("focusout",()=>{a=!1});let f,g=e.getAttribute("data-p60-carousel")==="auto"&&!L(),m=Math.max(Number.parseInt(e.getAttribute("data-p60-interval")??"6000",10)||6e3,3e3),v=()=>{f!==void 0&&window.clearInterval(f),g&&(f=window.setInterval(()=>{!a&&!document.hidden&&i(t+1)},m))};i(t),v()}}function Y(n){let s=Array.from(n.querySelectorAll("[data-p60-sticky-header]")).filter(e=>!y(e,"sticky-header"));for(let e of s){let o=e.getAttribute("data-p60-sticky-header")==="auto-hide",t=window.scrollY,a=!1,c=()=>{let r=window.scrollY;e.classList.toggle("is-condensed",r>24),o&&(r>160&&r>t?e.classList.add("is-hidden"):e.classList.remove("is-hidden")),t=r,a=!1};window.addEventListener("scroll",()=>{a||(a=!0,requestAnimationFrame(c))},{passive:!0}),c()}}function $(n){let s=Array.from(n.querySelectorAll("[data-p60-sticky-cta]")).filter(e=>!y(e,"sticky-cta"));for(let e of s){let o=Number.parseInt(e.getAttribute("data-p60-sticky-cta")??"480",10)||480,t=!1,a=()=>{e.classList.toggle("is-stuck",window.scrollY>o),t=!1};window.addEventListener("scroll",()=>{t||(t=!0,requestAnimationFrame(a))},{passive:!0}),a()}}function K(n){let s=Array.from(n.querySelectorAll("[data-p60-lightbox]")).filter(e=>!y(e,"lightbox"));for(let e of s){let o=Array.from(e.querySelectorAll("a[data-p60-lightbox-item]"));if(o.length===0)continue;let t=null,a=0,c=null,r=()=>{t?.remove(),t=null,e.classList.remove("is-open"),document.removeEventListener("keydown",d),c?.focus()},i=f=>{a=(f+o.length)%o.length;let g=o[a];if(!t)return;let m=t.querySelector(".p60-lightbox-img"),v=t.querySelector(".p60-lightbox-caption");m&&(m.src=g.href);let u=g.getAttribute("data-p60-caption")??g.querySelector("img")?.alt??"";v&&(v.textContent=u),m&&(m.alt=u)},d=f=>{if(f.key==="Escape"&&r(),f.key==="ArrowLeft"&&i(a-1),f.key==="ArrowRight"&&i(a+1),f.key==="Tab"&&t){let g=Array.from(t.querySelectorAll("button")),m=g.indexOf(document.activeElement),v=f.shiftKey?m<=0?g.length-1:m-1:m>=g.length-1?0:m+1;g[v]?.focus(),f.preventDefault()}},l=(f,g)=>{c=g,t=document.createElement("div"),t.className="p60-lightbox",t.setAttribute("role","dialog"),t.setAttribute("aria-modal","true"),L()&&(t.style.transition="none"),t.innerHTML='<figure><img class="p60-lightbox-img" alt=""><figcaption class="p60-lightbox-caption"></figcaption></figure>';let m=(u,b,p)=>{let h=document.createElement("button");return h.type="button",h.className=u,h.setAttribute("aria-label",b),h.addEventListener("click",p),t.appendChild(h),h};o.length>1&&(m("p60-lightbox-prev","Previous image",()=>i(a-1)),m("p60-lightbox-next","Next image",()=>i(a+1)));let v=m("p60-lightbox-close","Close",r);t.addEventListener("click",u=>{u.target===t&&r()}),document.body.appendChild(t),e.classList.add("is-open"),document.addEventListener("keydown",d),i(f),v.focus()};o.forEach((f,g)=>f.addEventListener("click",m=>{m.preventDefault(),l(g,f)}))}}function V(n){let s=Array.from(n.querySelectorAll("[data-p60-tabs]")).filter(e=>!y(e,"tabs"));for(let e of s){let o=Array.from(e.querySelectorAll("[data-p60-tab]")),t=Array.from(e.querySelectorAll("[data-p60-panel]"));if(o.length===0||t.length===0)continue;let a=r=>t.find(i=>i.getAttribute("data-p60-panel")===r.getAttribute("data-p60-tab"));o[0].closest("[data-p60-tabs] > *")?.setAttribute("role","tablist");let c=(r,i=!1)=>{for(let d of o){let l=d===r;d.classList.toggle("is-active",l),d.setAttribute("aria-selected",l?"true":"false"),d.setAttribute("tabindex",l?"0":"-1")}for(let d of t){let l=d===a(r);d.classList.toggle("is-active",l),d.hidden=!l}i&&r.focus()};o.forEach((r,i)=>{r.setAttribute("role","tab"),r.addEventListener("click",d=>{d.preventDefault(),c(r)}),r.addEventListener("keydown",d=>{let l=d.key;l==="ArrowLeft"&&c(o[(i-1+o.length)%o.length],!0),l==="ArrowRight"&&c(o[(i+1)%o.length],!0),l==="Home"&&c(o[0],!0),l==="End"&&c(o[o.length-1],!0)})}),t.forEach(r=>r.setAttribute("role","tabpanel")),c(o.find(r=>r.classList.contains("is-active"))??o[0])}}var X=60,j=280,x=()=>window.matchMedia("(hover: hover)").matches,z='a[href], button:not([disabled]), input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex="-1"])',A=new Set,k=!1;function S(n){n.openTimer!==null&&(window.clearTimeout(n.openTimer),n.openTimer=null),n.closeTimer!==null&&(window.clearTimeout(n.closeTimer),n.closeTimer=null)}function E(n,s=!1){S(n);for(let e of Array.from(A))e!==n&&n.item.contains(e.item)&&E(e);n.item.classList.remove("is-open"),n.toggle.setAttribute("aria-expanded","false"),n.openedBy=null,A.delete(n),s&&n.toggle.focus()}function w(n,s){S(n);for(let e of Array.from(A))e!==n&&e.nav===n.nav&&!e.item.contains(n.item)&&E(e);n.item.classList.add("is-open"),n.toggle.setAttribute("aria-expanded","true"),n.openedBy=s,A.add(n)}function U(n){for(let e of Array.from(n.querySelectorAll("[data-p60-nav]"))){let o=Array.from(e.querySelectorAll("[data-p60-nav-item]")).filter(t=>t.closest("[data-p60-nav]")===e&&!y(t,"nav-item"));for(let t of o){let a=l=>l.closest("[data-p60-nav-item]")===t,c=Array.from(t.querySelectorAll("[data-p60-nav-toggle]")).find(a)??null,r=Array.from(t.querySelectorAll("[data-p60-nav-menu]")).find(a)??null;if(!c||!r)continue;let i={nav:e,item:t,toggle:c,menu:r,openedBy:null,lastPointer:"",openTimer:null,closeTimer:null},d=()=>A.has(i);c.setAttribute("aria-expanded","false"),r.id&&!c.hasAttribute("aria-controls")&&c.setAttribute("aria-controls",r.id),t.addEventListener("mouseenter",()=>{x()&&(i.closeTimer!==null&&(window.clearTimeout(i.closeTimer),i.closeTimer=null),!d()&&i.openTimer===null&&(i.openTimer=window.setTimeout(()=>{i.openTimer=null,w(i,"hover")},X)))}),t.addEventListener("mouseleave",()=>{x()&&(i.openTimer!==null&&(window.clearTimeout(i.openTimer),i.openTimer=null),d()&&i.openedBy==="hover"&&i.closeTimer===null&&(i.closeTimer=window.setTimeout(()=>{i.closeTimer=null,E(i)},j)))}),c.addEventListener("pointerdown",l=>{i.lastPointer=l.pointerType??""}),c.addEventListener("click",l=>{let f=l.detail===0||i.lastPointer==="touch"||i.lastPointer==="pen"||!x();if(!d()){l.preventDefault(),l.stopPropagation(),w(i,"toggle");return}!f&&i.openedBy==="hover"||(l.preventDefault(),l.stopPropagation(),E(i))}),c.addEventListener("keydown",l=>{l.key==="ArrowDown"&&(l.preventDefault(),d()||w(i,"toggle"),r.querySelector(z)?.focus())}),t.addEventListener("keydown",l=>{l.key==="Escape"&&d()&&(l.stopPropagation(),E(i,!0))}),t.addEventListener("focusin",l=>{l.target instanceof Node&&r.contains(l.target)&&!d()&&w(i,"focus")}),t.addEventListener("focusout",l=>{let f=l.relatedTarget;f instanceof Node&&!t.contains(f)&&d()&&E(i)})}}let s=Array.from(n.querySelectorAll("[data-p60-nav-burger]")).filter(e=>!y(e,"nav-burger"));for(let e of s){let o=e.getAttribute("aria-controls")||e.getAttribute("data-p60-nav-burger")||"",t=o?document.getElementById(o):null;if(!t)continue;let a=()=>t.classList.contains("is-open"),c=r=>{if(t.classList.toggle("is-open",r),e.classList.toggle("is-open",r),e.setAttribute("aria-expanded",String(r)),!r)for(let i of Array.from(A))t.contains(i.item)&&E(i)};e.setAttribute("aria-expanded","false"),e.addEventListener("click",r=>{r.stopPropagation(),c(!a())}),t.addEventListener("click",r=>{r.target instanceof Element&&r.target.closest("a")&&c(!1)}),document.addEventListener("keydown",r=>{r.key==="Escape"&&a()&&(c(!1),e.focus())}),document.addEventListener("click",r=>{a()&&r.target instanceof Node&&!t.contains(r.target)&&!e.contains(r.target)&&c(!1)}),window.addEventListener("resize",()=>{a()&&e.offsetParent===null&&c(!1)})}k||(k=!0,document.addEventListener("click",e=>{for(let o of Array.from(A))(!(e.target instanceof Node)||!o.item.contains(e.target))&&E(o)}))}function G(n=document){document.documentElement.classList.add("p60-js"),O(n),D(n),B(n),F(n),_(n),R(n),Y(n),$(n),K(n),V(n),U(n)}return C(W);})();
|
|
@@ -140,6 +140,7 @@ export async function validateArtifact(files) {
|
|
|
140
140
|
stickyCta: [/data-p60-sticky-cta\b/, 'data-p60-sticky-cta'],
|
|
141
141
|
lightbox: [/data-p60-lightbox\b/, 'data-p60-lightbox'],
|
|
142
142
|
tabs: [/data-p60-tabs\b/, 'data-p60-tabs'],
|
|
143
|
+
nav: [/data-p60-nav\b/, 'data-p60-nav'],
|
|
143
144
|
};
|
|
144
145
|
for (const name of declaredBehaviours) {
|
|
145
146
|
const primary = PRIMARY_ATTR[name];
|
|
@@ -162,6 +163,13 @@ export async function validateArtifact(files) {
|
|
|
162
163
|
&& (!/data-p60-tab(?!s)\b/.test(liquidSource) || !/data-p60-panel\b/.test(liquidSource))) {
|
|
163
164
|
errors.push("behaviour 'tabs': a data-p60-tabs container needs data-p60-tab controls and data-p60-panel panels");
|
|
164
165
|
}
|
|
166
|
+
if (/data-p60-nav-item\b/.test(liquidSource)
|
|
167
|
+
&& (!/data-p60-nav-toggle\b/.test(liquidSource) || !/data-p60-nav-menu\b/.test(liquidSource))) {
|
|
168
|
+
errors.push("behaviour 'nav': a data-p60-nav-item group needs a data-p60-nav-toggle control and a data-p60-nav-menu panel");
|
|
169
|
+
}
|
|
170
|
+
if (/data-p60-nav-(item|burger)\b/.test(liquidSource) && !/data-p60-nav(?!-)\b/.test(liquidSource)) {
|
|
171
|
+
errors.push("behaviour 'nav': data-p60-nav-item and data-p60-nav-burger need a data-p60-nav root around the menu");
|
|
172
|
+
}
|
|
165
173
|
}
|
|
166
174
|
|
|
167
175
|
// Font knobs: the default family must be a real catalogue entry (the tenant-unset render uses
|