@codepress/design-kit 0.7.0 → 0.8.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.
Files changed (3) hide show
  1. package/README.md +5 -1
  2. package/package.json +1 -1
  3. package/src/kit.mjs +54 -7
package/README.md CHANGED
@@ -463,7 +463,11 @@ by id, title, component and source file only.
463
463
  `compose` writes one self-contained page: the frame's markup with slots
464
464
  filled and every `<kit-snippet id="…">` expanded, its CSS inlined, every
465
465
  relative `url()` that names a file in the kit (fonts included) as a data URI,
466
- and `data-kit-sha`/`data-kit-id` on `<html>`. Scripts, event handlers,
466
+ and `data-kit-sha`/`data-kit-id` on `<html>`. Each expanded snippet's
467
+ top-level elements carry `data-kit-snippet="<id>"`, and a
468
+ `<meta name="design-kit-usage">` in the head holds a JSON record of the kit sha,
469
+ the frame, the snippet ids and how many `data-kit-new` elements the page has.
470
+ The record stays in the page: `kit.mjs` makes no network call. Scripts, event handlers,
467
471
  `<link>`s and SVG animation are removed, and URLs pass an allowlist: only
468
472
  relative references, fragments and `data:` survive, read the way the browser
469
473
  reads them. `--theme dark` applies the manifest's theme. Elements marked
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@codepress/design-kit",
3
- "version": "0.7.0",
3
+ "version": "0.8.0",
4
4
  "description": "Capture your Storybook and real app pages as a static design kit, and compose self-contained mockups from it",
5
5
  "type": "module",
6
6
  "bin": {
package/src/kit.mjs CHANGED
@@ -711,7 +711,30 @@ export function inlineAssets(kit, css, cssDir = join(kit.dir, "css")) {
711
711
 
712
712
  // ── compose ─────────────────────────────────────────────────────────────────
713
713
 
714
- function expandSnippets(kit, html, used, budget, depth = 0) {
714
+ /**
715
+ * `data-kit-snippet="<id>"` on each top-level element of a snippet's markup,
716
+ * so the composed page records which real piece every part came from. A
717
+ * nested `<kit-snippet>` at the top is left alone: its own expansion stamps
718
+ * the elements that replace it.
719
+ */
720
+ function stampSnippetRoots(html, id) {
721
+ const roots = elements(html).filter(
722
+ (e) =>
723
+ e.depth === 0 &&
724
+ e.name !== "kit-snippet" &&
725
+ !("data-kit-snippet" in e.attrs),
726
+ );
727
+ let out = "";
728
+ let i = 0;
729
+ for (const root of roots) {
730
+ const at = root.start + 1 + root.name.length;
731
+ out += `${html.slice(i, at)} data-kit-snippet="${escapeAttr(id)}"`;
732
+ i = at;
733
+ }
734
+ return out + html.slice(i);
735
+ }
736
+
737
+ function expandSnippets(kit, html, used, budget, depth = 0, ids = []) {
715
738
  const found = elements(html).filter((e) => e.name === "kit-snippet");
716
739
  if (!found.length) return html;
717
740
  if (depth >= SNIPPET_DEPTH) {
@@ -735,9 +758,17 @@ function expandSnippets(kit, html, used, budget, depth = 0) {
735
758
  );
736
759
  }
737
760
  if (rowSource(row) === "stories") markup = unwrapStoryRoot(markup);
761
+ if (!ids.includes(row.id)) ids.push(row.id);
738
762
  out +=
739
763
  html.slice(i, element.start) +
740
- expandSnippets(kit, markup, used, budget, depth + 1);
764
+ expandSnippets(
765
+ kit,
766
+ stampSnippetRoots(markup, row.id),
767
+ used,
768
+ budget,
769
+ depth + 1,
770
+ ids,
771
+ );
741
772
  i = element.end;
742
773
  }
743
774
  return out + html.slice(i);
@@ -874,10 +905,17 @@ export function compose(
874
905
  }
875
906
  const used = new Set([rowCssFile(kit, row)]);
876
907
  const filled = fillSlots(kit, row, readSnippet(kit, row), slots);
877
- const body = expandSnippets(kit, filled, used, {
878
- left: maxBytes - filled.length,
879
- max: maxBytes,
880
- });
908
+ const snippetIds = [];
909
+ const body = sanitizeMarkup(
910
+ expandSnippets(
911
+ kit,
912
+ filled,
913
+ used,
914
+ { left: maxBytes - filled.length, max: maxBytes },
915
+ 0,
916
+ snippetIds,
917
+ ),
918
+ );
881
919
 
882
920
  // Stories' sheet first, then the route sheets, as before page sources.
883
921
  const storiesCss = cssFile(kit, "stories");
@@ -912,18 +950,27 @@ export function compose(
912
950
  "body",
913
951
  {},
914
952
  );
953
+ // What this page used, for whoever wants to count it later. Recorded in
954
+ // the page only; kit.mjs never sends it anywhere.
955
+ const usage = {
956
+ kit_sha: sha,
957
+ frame,
958
+ snippets: snippetIds,
959
+ new_parts: elements(body).filter((e) => "data-kit-new" in e.attrs).length,
960
+ };
915
961
  const page = `<!doctype html>
916
962
  <html${htmlAttrs}>
917
963
  <head>
918
964
  <meta charset="utf-8">
919
965
  <meta name="viewport" content="width=device-width, initial-scale=1">
966
+ <meta name="design-kit-usage" content="${escapeAttr(JSON.stringify(usage))}">
920
967
  <title>${escapeAttr(row.title ?? frame)}</title>
921
968
  <style>
922
969
  ${sanitizeCss(css)}${showNewCss}
923
970
  </style>
924
971
  </head>
925
972
  <body${bodyAttrs}>
926
- ${sanitizeMarkup(body)}
973
+ ${body}
927
974
  </body>
928
975
  </html>
929
976
  `;