@homepages/template-kit 0.0.0 → 0.1.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 (90) hide show
  1. package/CHANGELOG.md +182 -0
  2. package/README.md +71 -14
  3. package/dist/base.css +85 -0
  4. package/dist/browser.d.ts +26 -0
  5. package/dist/browser.js +55 -0
  6. package/dist/cli.js +1 -1
  7. package/dist/contracts/image-descriptor.d.ts +17 -0
  8. package/dist/contracts/markers.d.ts +20 -0
  9. package/dist/contracts/markers.js +21 -0
  10. package/dist/contracts/slot-catalog.d.ts +237 -0
  11. package/dist/contracts/slot-catalog.js +247 -0
  12. package/dist/contracts/transforms.d.ts +108 -0
  13. package/dist/contracts/transforms.js +117 -0
  14. package/dist/design-system/breakpoints.d.ts +5 -0
  15. package/dist/design-system/breakpoints.js +14 -0
  16. package/dist/design-system/theme.d.ts +307 -0
  17. package/dist/design-system/theme.js +95 -0
  18. package/dist/eslint/is-client-file.js +79 -0
  19. package/dist/eslint/rules/image-bare-needs-reason.js +38 -0
  20. package/dist/eslint/rules/no-client-directive-in-contract.js +26 -0
  21. package/dist/eslint/rules/no-client-runtime-in-server.js +99 -0
  22. package/dist/eslint/rules/no-css-import-from-render-path.js +23 -0
  23. package/dist/eslint/rules/no-hex.js +111 -0
  24. package/dist/eslint/rules/no-inline-style.js +23 -0
  25. package/dist/eslint/rules/no-nondeterminism.js +44 -0
  26. package/dist/eslint/rules/no-raw-element.js +32 -0
  27. package/dist/eslint/rules/props-from-schema.js +32 -0
  28. package/dist/eslint/rules/serializable-island-props.js +108 -0
  29. package/dist/eslint/rules/slot-marker-literal.js +33 -0
  30. package/dist/eslint.d.ts +4 -8
  31. package/dist/eslint.js +75 -11
  32. package/dist/fixtures/sample-property.d.ts +67 -0
  33. package/dist/fixtures/sample-property.js +707 -0
  34. package/dist/fixtures.d.ts +2 -0
  35. package/dist/fixtures.js +3 -0
  36. package/dist/index.d.ts +37 -13
  37. package/dist/index.js +34 -2
  38. package/dist/island-runtime.d.ts +30 -0
  39. package/dist/island-runtime.js +73 -0
  40. package/dist/islands/contract.d.ts +56 -0
  41. package/dist/islands/contract.js +71 -0
  42. package/dist/islands/marker.d.ts +16 -0
  43. package/dist/islands/marker.js +18 -0
  44. package/dist/package.js +5 -0
  45. package/dist/primitives/Image.d.ts +49 -0
  46. package/dist/primitives/Image.js +95 -0
  47. package/dist/primitives/Section.d.ts +22 -0
  48. package/dist/primitives/Section.js +26 -0
  49. package/dist/primitives/Slot.d.ts +50 -0
  50. package/dist/primitives/Slot.js +39 -0
  51. package/dist/primitives/SlotGroup.d.ts +38 -0
  52. package/dist/primitives/SlotGroup.js +24 -0
  53. package/dist/primitives/SlotItem.d.ts +43 -0
  54. package/dist/primitives/SlotItem.js +38 -0
  55. package/dist/primitives/index.d.ts +7 -0
  56. package/dist/primitives/picture-sources.d.ts +22 -0
  57. package/dist/primitives/picture-sources.js +52 -0
  58. package/dist/schema/fill-spec.d.ts +692 -0
  59. package/dist/schema/fill-spec.js +98 -0
  60. package/dist/schema/fixture-schema.d.ts +143 -0
  61. package/dist/schema/fixture-schema.js +113 -0
  62. package/dist/schema/index.d.ts +16 -0
  63. package/dist/schema/manifest.d.ts +527 -0
  64. package/dist/schema/manifest.js +73 -0
  65. package/dist/schema/property-facts.d.ts +44 -0
  66. package/dist/schema/resolve-variants.d.ts +11 -0
  67. package/dist/schema/resolve-variants.js +15 -0
  68. package/dist/schema/runtime-values.d.ts +45 -0
  69. package/dist/schema/section-nav.d.ts +7 -0
  70. package/dist/schema/section-props.d.ts +69 -0
  71. package/dist/schema/section-schema.d.ts +533 -0
  72. package/dist/schema/section-schema.js +72 -0
  73. package/dist/schema/slot-types.d.ts +117 -0
  74. package/dist/schema/slot-types.js +181 -0
  75. package/dist/schema/source.d.ts +13 -0
  76. package/dist/schema/source.js +12 -0
  77. package/dist/schema/synthesize-nullable.d.ts +18 -0
  78. package/dist/schema/synthesize-nullable.js +47 -0
  79. package/dist/styles.css +131 -9
  80. package/docs/INDEX.md +6 -4
  81. package/docs/eslint.md +90 -0
  82. package/docs/islands.md +150 -0
  83. package/docs/llms.txt +30 -3
  84. package/docs/primitives.md +214 -0
  85. package/docs/schema-system.md +240 -0
  86. package/docs/theme-and-css.md +179 -0
  87. package/package.json +30 -6
  88. package/tsconfig.base.json +16 -0
  89. package/tsconfig.json +2 -13
  90. package/dist/src-CrdHXijV.js +0 -23
@@ -0,0 +1,39 @@
1
+ import { ATTR_SLOT_ID, ATTR_SLOT_TEXT_LEAF } from "../contracts/markers.js";
2
+ import { Children, cloneElement, isValidElement } from "react";
3
+ import { jsx } from "react/jsx-runtime";
4
+
5
+ //#region src/primitives/Slot.tsx
6
+ /**
7
+ * Slot — the canonical wrapper for an editable slot's DOM boundary.
8
+ *
9
+ * Wrap each editable slot's outer DOM in `<Slot id="…">`. It emits `data-slot-id`,
10
+ * which is how the editor resolves a click in the preview back to the slot the user
11
+ * meant. A slot with no marker in the DOM cannot be selected or edited.
12
+ *
13
+ * A primitive with a natural single root for its slot type takes a `slotId` prop and
14
+ * emits the marker on its own root instead — `<Image slotId="hero_image">` needs no
15
+ * `<Slot>` wrapper. `<Slot>` is for everything else: text slots, source-bound scalars
16
+ * rendered as raw spans, and any slot whose markup is a hand-assembled composition.
17
+ */
18
+ function Slot({ id, as, textLeaf, children, ...rest }) {
19
+ const Tag = as ?? "div";
20
+ const content = textLeaf ? markSingleTextLeaf(children) : children;
21
+ return /* @__PURE__ */ jsx(Tag, {
22
+ [ATTR_SLOT_ID]: id,
23
+ ...rest,
24
+ children: content
25
+ });
26
+ }
27
+ function markSingleTextLeaf(children) {
28
+ if (Children.count(children) !== 1) return children;
29
+ const only = Children.toArray(children)[0];
30
+ if (!isValidElement(only)) return children;
31
+ const element = only;
32
+ return cloneElement(element, {
33
+ ...element.props,
34
+ [ATTR_SLOT_TEXT_LEAF]: ""
35
+ });
36
+ }
37
+
38
+ //#endregion
39
+ export { Slot };
@@ -0,0 +1,38 @@
1
+ import { ElementType, HTMLAttributes, ReactNode } from "react";
2
+ //#region src/primitives/SlotGroup.d.ts
3
+ type SlotGroupProps = HTMLAttributes<HTMLElement> & {
4
+ /**
5
+ * Must equal the `id` of a `meta.groups[]` entry in the section schema.
6
+ *
7
+ * Emitted as `data-slot-group` on the rendered container, so the editor treats
8
+ * the group as one selectable unit: selection chrome outlines THIS container
9
+ * rather than the individual members, and a click anywhere inside it —
10
+ * including the gaps between members — resolves to the group.
11
+ */
12
+ id: string;
13
+ /**
14
+ * Element to render. Defaults to `"div"`. Override when the group's surrounding
15
+ * semantic role needs something else.
16
+ */
17
+ as?: ElementType;
18
+ /**
19
+ * The member slots. `SlotGroup` MUST be an ANCESTOR of every member's
20
+ * `data-slot-id` element (it wraps their shared container) — the editor resolves
21
+ * a member back to its group with `closest("[data-slot-group]")`, so a marker
22
+ * that isn't an ancestor would never be found.
23
+ */
24
+ children?: ReactNode;
25
+ };
26
+ /**
27
+ * SlotGroup — the wrapper for a set of slots the schema groups into one editor card.
28
+ *
29
+ * Grouping is authored in two places that must agree. `meta.groups[]` declares the
30
+ * group (`{ id, label, members }`), which collapses its members into a single card in
31
+ * the editor sidebar. The renderer wraps those members' shared container in
32
+ * `<SlotGroup id="<the group's id>">`, which makes the canvas outline and select them
33
+ * as the one unit that matches that one card. Same id on both sides, or the card and
34
+ * the canvas disagree about what the user just clicked.
35
+ */
36
+ declare function SlotGroup({ id, as, children, ...rest }: SlotGroupProps): ReactNode;
37
+ //#endregion
38
+ export { SlotGroup };
@@ -0,0 +1,24 @@
1
+ import { ATTR_SLOT_GROUP } from "../contracts/markers.js";
2
+ import { jsx } from "react/jsx-runtime";
3
+
4
+ //#region src/primitives/SlotGroup.tsx
5
+ /**
6
+ * SlotGroup — the wrapper for a set of slots the schema groups into one editor card.
7
+ *
8
+ * Grouping is authored in two places that must agree. `meta.groups[]` declares the
9
+ * group (`{ id, label, members }`), which collapses its members into a single card in
10
+ * the editor sidebar. The renderer wraps those members' shared container in
11
+ * `<SlotGroup id="<the group's id>">`, which makes the canvas outline and select them
12
+ * as the one unit that matches that one card. Same id on both sides, or the card and
13
+ * the canvas disagree about what the user just clicked.
14
+ */
15
+ function SlotGroup({ id, as, children, ...rest }) {
16
+ return /* @__PURE__ */ jsx(as ?? "div", {
17
+ [ATTR_SLOT_GROUP]: id,
18
+ ...rest,
19
+ children
20
+ });
21
+ }
22
+
23
+ //#endregion
24
+ export { SlotGroup };
@@ -0,0 +1,43 @@
1
+ import { ElementType, HTMLAttributes, ReactNode } from "react";
2
+ //#region src/primitives/SlotItem.d.ts
3
+ type SlotItemProps = HTMLAttributes<HTMLElement> & {
4
+ /**
5
+ * Zero-based position of this item within its collection slot — the item's
6
+ * index in the array the renderer is mapping over. Emitted as
7
+ * `data-slot-item`.
8
+ */
9
+ index: number;
10
+ /**
11
+ * Element to render. Defaults to `"div"`. Override when the item's surrounding
12
+ * semantic role needs something else (`li`, `article`, …).
13
+ */
14
+ as?: ElementType;
15
+ children?: ReactNode;
16
+ };
17
+ /**
18
+ * SlotItem — the per-item handle inside a collection slot (`object_list`,
19
+ * `image_collection`).
20
+ *
21
+ * A collection slot's `<Slot id="…">` marks the whole array; `<SlotItem index={i}>`
22
+ * marks one row of it. The editor needs BOTH: it resolves the slot from the ancestor
23
+ * `data-slot-id` and then the specific item from the nearest `data-slot-item`, which
24
+ * is what makes per-item selection, per-item image cropping, and per-item live
25
+ * surfaces work. Without it a collection is editable only as a whole.
26
+ *
27
+ * The index must be the item's position in the slot's array as rendered. The editor
28
+ * writes edits back by that index, so a `SlotItem` whose index doesn't match its
29
+ * position edits the wrong row.
30
+ *
31
+ * ```tsx
32
+ * <Slot id="amenities">
33
+ * {items.map((item, i) => (
34
+ * <SlotItem key={item.id} index={i} className="border-b">
35
+ * …
36
+ * </SlotItem>
37
+ * ))}
38
+ * </Slot>
39
+ * ```
40
+ */
41
+ declare function SlotItem({ index, as, children, ...rest }: SlotItemProps): ReactNode;
42
+ //#endregion
43
+ export { SlotItem };
@@ -0,0 +1,38 @@
1
+ import { ATTR_SLOT_ITEM } from "../contracts/markers.js";
2
+ import { jsx } from "react/jsx-runtime";
3
+
4
+ //#region src/primitives/SlotItem.tsx
5
+ /**
6
+ * SlotItem — the per-item handle inside a collection slot (`object_list`,
7
+ * `image_collection`).
8
+ *
9
+ * A collection slot's `<Slot id="…">` marks the whole array; `<SlotItem index={i}>`
10
+ * marks one row of it. The editor needs BOTH: it resolves the slot from the ancestor
11
+ * `data-slot-id` and then the specific item from the nearest `data-slot-item`, which
12
+ * is what makes per-item selection, per-item image cropping, and per-item live
13
+ * surfaces work. Without it a collection is editable only as a whole.
14
+ *
15
+ * The index must be the item's position in the slot's array as rendered. The editor
16
+ * writes edits back by that index, so a `SlotItem` whose index doesn't match its
17
+ * position edits the wrong row.
18
+ *
19
+ * ```tsx
20
+ * <Slot id="amenities">
21
+ * {items.map((item, i) => (
22
+ * <SlotItem key={item.id} index={i} className="border-b">
23
+ * …
24
+ * </SlotItem>
25
+ * ))}
26
+ * </Slot>
27
+ * ```
28
+ */
29
+ function SlotItem({ index, as, children, ...rest }) {
30
+ return /* @__PURE__ */ jsx(as ?? "div", {
31
+ [ATTR_SLOT_ITEM]: index,
32
+ ...rest,
33
+ children
34
+ });
35
+ }
36
+
37
+ //#endregion
38
+ export { SlotItem };
@@ -0,0 +1,7 @@
1
+ import { Image } from "./Image.js";
2
+ import { PicturePlan, PictureSourceSpec } from "./picture-sources.js";
3
+ import { Section } from "./Section.js";
4
+ import { Slot } from "./Slot.js";
5
+ import { SlotGroup } from "./SlotGroup.js";
6
+ import { SlotItem } from "./SlotItem.js";
7
+ export { Image, type PicturePlan, type PictureSourceSpec, Section, Slot, SlotGroup, SlotItem };
@@ -0,0 +1,22 @@
1
+ //#region src/primitives/picture-sources.d.ts
2
+ type PictureSourceSpec = {
3
+ type?: string;
4
+ media?: string;
5
+ srcSet: string;
6
+ sizes?: string;
7
+ };
8
+ type PicturePlan = {
9
+ /**
10
+ * Ordered <source> specs: mobile (media-gated) first, then desktop. May be empty
11
+ * even with a valid plan (a single-format descriptor puts everything on the <img>);
12
+ * `sources: []` AND no `imgSrcSet` is the "no responsive — caller degrades" signal.
13
+ */
14
+ sources: PictureSourceSpec[];
15
+ /** srcset for the trailing <img> (the desktop base raster). */
16
+ imgSrcSet?: string;
17
+ imgSizes?: string;
18
+ width?: number;
19
+ height?: number;
20
+ };
21
+ //#endregion
22
+ export { PicturePlan, PictureSourceSpec };
@@ -0,0 +1,52 @@
1
+ //#region src/primitives/picture-sources.ts
2
+ function capSrcset(srcset, maxRungWidth) {
3
+ const entries = srcset.split(",").map((s) => s.trim()).filter(Boolean).map((cand) => {
4
+ const m = cand.match(/(\d+)w\s*$/);
5
+ return {
6
+ cand,
7
+ w: m ? Number(m[1]) : Number.POSITIVE_INFINITY
8
+ };
9
+ }).sort((a, b) => a.w - b.w);
10
+ if (entries.length === 0) return srcset;
11
+ if (maxRungWidth == null) return entries.map((event) => event.cand).join(", ");
12
+ const kept = entries.filter((event) => event.w <= maxRungWidth);
13
+ return (kept.length > 0 ? kept : [entries[0]]).map((event) => event.cand).join(", ");
14
+ }
15
+ function buildPictureSources(args) {
16
+ const { responsive, responsiveMobile, sizes, maxRungWidth, mobileBreakpoint } = args;
17
+ if (!responsive || responsive.sources.length === 0) return { sources: [] };
18
+ const sources = [];
19
+ if (responsiveMobile && responsiveMobile.sources.length > 0 && mobileBreakpoint) {
20
+ const media = `(max-width: ${mobileBreakpoint - 1}px)`;
21
+ const m = responsiveMobile.sources;
22
+ const mBase = m[m.length - 1];
23
+ for (const s of m.slice(0, -1)) sources.push({
24
+ type: s.type,
25
+ media,
26
+ srcSet: capSrcset(s.srcset, maxRungWidth),
27
+ sizes
28
+ });
29
+ sources.push({
30
+ media,
31
+ srcSet: capSrcset(mBase.srcset, maxRungWidth),
32
+ sizes
33
+ });
34
+ }
35
+ const d = responsive.sources;
36
+ const dBase = d[d.length - 1];
37
+ for (const s of d.slice(0, -1)) sources.push({
38
+ type: s.type,
39
+ srcSet: capSrcset(s.srcset, maxRungWidth),
40
+ sizes
41
+ });
42
+ return {
43
+ sources,
44
+ imgSrcSet: capSrcset(dBase.srcset, maxRungWidth),
45
+ imgSizes: sizes,
46
+ width: responsive.width,
47
+ height: responsive.height
48
+ };
49
+ }
50
+
51
+ //#endregion
52
+ export { buildPictureSources };