@typed-grid-areas/solid 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.
@@ -0,0 +1,80 @@
1
+ import { ComponentProps, JSX, ValidComponent } from "solid-js";
2
+ import type { GridConfig } from "typed-grid-areas";
3
+ /**
4
+ * Props of a slot component (`<Card.icon>`) and of a grid's root component
5
+ * (`<Card>`): anything the element `as` renders takes (a `div`'s by
6
+ * default), plus `as`.
7
+ */
8
+ export type SlotComponentProps<T extends ValidComponent = "div"> = Omit<ComponentProps<T>, "as"> & {
9
+ /**
10
+ * The element to render as the slot (or grid) instead of a `div`: a tag
11
+ * name (`"label"`), a component, or a render function
12
+ * (`props => <label {...props} />`). A component or render function gets
13
+ * the slot's props, including its `data-area`, and must spread them onto
14
+ * its root element.
15
+ */
16
+ as?: T | SlotRenderFunction;
17
+ };
18
+ /**
19
+ * A render function passed to `as`: it gets the slot's props, including its
20
+ * `data-area`, to spread onto the element it renders. Annotate its `props`
21
+ * (`(props: JSX.LabelHTMLAttributes<HTMLLabelElement>) => …`) for the slot
22
+ * to take that element's attributes rather than a `div`'s.
23
+ */
24
+ export type SlotRenderFunction = (props: JSX.HTMLAttributes<any>) => JSX.Element;
25
+ /** The props of a grid's root component (`<Card>`). */
26
+ export type GridComponentProps<T extends ValidComponent = "div"> = SlotComponentProps<T>;
27
+ /** A slot component (`<Card.icon>`): renders the element that fills one area. */
28
+ export type SlotComponent = <T extends ValidComponent = "div">(props: SlotComponentProps<T>) => JSX.Element;
29
+ /**
30
+ * The component a definition renders as: the grid itself, with a slot
31
+ * component per area as a member, named exactly like the area. An area
32
+ * nested through `nest` is the root component of the nested grid instead,
33
+ * with that grid's slots as its members (`<Card.content.icon>`).
34
+ */
35
+ export type GridComponent<TAreaName extends string, TNest = {}> = (<T extends ValidComponent = "div">(props: GridComponentProps<T>) => JSX.Element) & {
36
+ readonly [K in TAreaName]: K extends keyof TNest ? TNest[K] : SlotComponent;
37
+ };
38
+ /** Any grid component, whatever its areas. */
39
+ type AnyGridComponent = GridComponent<any, any>;
40
+ /** Options of `createGridComponent`. */
41
+ export type GridComponentOptions<TAreaName extends string> = {
42
+ /**
43
+ * Grids nested into areas of this one: `nest: { content: ContentGrid }`
44
+ * makes `<Card.content>` both the `content` slot and the root of
45
+ * `ContentGrid`.
46
+ */
47
+ nest?: {
48
+ readonly [K in TAreaName]?: AnyGridComponent;
49
+ };
50
+ };
51
+ /**
52
+ * Turns a definition into the component it renders as.
53
+ *
54
+ * `<Card>` renders the grid (inside its own size container when the
55
+ * definition has breakpoints) and puts the definition's CSS on the page;
56
+ * `<Card.icon>` renders the slot that fills the `icon` area. Slots accept
57
+ * `class`, `style`, `children`, regular element props, and `as` to render
58
+ * your own element as the slot.
59
+ *
60
+ * Create grid components at module level, once per definition.
61
+ *
62
+ * @example
63
+ * ```tsx
64
+ * const Card = createGridComponent(
65
+ * defineLayout({ areas: ["input?", "content"], layouts: [{ template: "input content" }] }),
66
+ * { nest: { content: ContentGrid } },
67
+ * );
68
+ *
69
+ * <Card>
70
+ * <Card.input as="label" for="notify">…</Card.input>
71
+ * <Card.content>
72
+ * <Card.content.icon>…</Card.content.icon>
73
+ * </Card.content>
74
+ * </Card>
75
+ * ```
76
+ */
77
+ export declare function createGridComponent<TAreaName extends string, const TNest extends GridComponentOptions<TAreaName>["nest"] = {}>(definition: GridConfig<TAreaName>, options?: {
78
+ nest?: TNest;
79
+ }): GridComponent<TAreaName, TNest>;
80
+ export {};
@@ -0,0 +1,132 @@
1
+ import { children, createEffect, mergeProps, sharedConfig, splitProps, } from "solid-js";
2
+ import { Dynamic, isServer, ssr, useAssets } from "solid-js/web";
3
+ import { createSlotCheck, gridMarkup, gridStyleTags, insertGridStyle, isDevelopment, } from "typed-grid-areas/adapter";
4
+ /**
5
+ * What a server render has registered with `useAssets`, by render. The
6
+ * render's list of assets is internal to Solid; it's only used here as a
7
+ * key that's new for each render.
8
+ */
9
+ const registeredStyles = new WeakMap();
10
+ /**
11
+ * Puts a definition's CSS on the page: on the server, its style tag among
12
+ * the render's assets (once per render), which Solid injects before
13
+ * `</head>`; in the browser, its style tag in `<head>`, reusing one the
14
+ * server rendered.
15
+ */
16
+ function emitCss(definition) {
17
+ if (!isServer) {
18
+ insertGridStyle(document, definition);
19
+ return;
20
+ }
21
+ const assets = sharedConfig.context?.assets;
22
+ if (assets === undefined) {
23
+ return;
24
+ }
25
+ const registered = registeredStyles.get(assets) ?? new Set();
26
+ registeredStyles.set(assets, registered);
27
+ const { id } = gridMarkup(definition);
28
+ if (registered.has(id)) {
29
+ return;
30
+ }
31
+ registered.add(id);
32
+ // `ssr` marks the tag's HTML as already rendered, so it isn't escaped.
33
+ useAssets(() => ssr(gridStyleTags([definition])));
34
+ }
35
+ function createRoot(definition) {
36
+ const markup = gridMarkup(definition);
37
+ return function Root(props) {
38
+ emitCss(definition);
39
+ const [local, others] = splitProps(props, ["as", "ref", "children", "data-area"]);
40
+ const { sizeContainer, grid: gridElement } = markup.elements(local["data-area"]);
41
+ const resolved = children(() => local.children);
42
+ let grid;
43
+ const ref = (element) => {
44
+ grid = element;
45
+ local.ref?.(element);
46
+ };
47
+ // In development, check the slots after mount and whenever they change.
48
+ if (!isServer && isDevelopment()) {
49
+ const checkSlots = createSlotCheck(definition, {
50
+ strayChildRemedy: "make it a slot with `as`",
51
+ });
52
+ createEffect(() => {
53
+ resolved();
54
+ if (grid !== undefined) {
55
+ for (const warning of checkSlots(grid)) {
56
+ // eslint-disable-next-line no-console
57
+ console.warn(warning);
58
+ }
59
+ }
60
+ });
61
+ }
62
+ const element = (<Dynamic component={local.as ?? "div"} {...others} {...gridElement.attributes} ref={ref}>
63
+ {resolved()}
64
+ </Dynamic>);
65
+ if (sizeContainer === undefined) {
66
+ return element;
67
+ }
68
+ return (<div {...sizeContainer.attributes} style={sizeContainer.styleText}>
69
+ {element}
70
+ </div>);
71
+ };
72
+ }
73
+ function createSlotComponent(area) {
74
+ return function SlotComponent(props) {
75
+ const [local, others] = splitProps(props, ["as"]);
76
+ return <Dynamic component={local.as ?? "div"} {...others} data-area={area}/>;
77
+ };
78
+ }
79
+ /**
80
+ * Defines `members` on a component. `defineProperty`, because an area may be
81
+ * named like a function's own read-only properties (`name`, `length`).
82
+ */
83
+ function withMembers(component, members) {
84
+ for (const [key, value] of Object.entries(members)) {
85
+ Object.defineProperty(component, key, { value, enumerable: true, configurable: true });
86
+ }
87
+ return component;
88
+ }
89
+ /** The members of a grid component, one per area. */
90
+ function membersOf(component) {
91
+ return Object.fromEntries(Object.entries(component));
92
+ }
93
+ /**
94
+ * Turns a definition into the component it renders as.
95
+ *
96
+ * `<Card>` renders the grid (inside its own size container when the
97
+ * definition has breakpoints) and puts the definition's CSS on the page;
98
+ * `<Card.icon>` renders the slot that fills the `icon` area. Slots accept
99
+ * `class`, `style`, `children`, regular element props, and `as` to render
100
+ * your own element as the slot.
101
+ *
102
+ * Create grid components at module level, once per definition.
103
+ *
104
+ * @example
105
+ * ```tsx
106
+ * const Card = createGridComponent(
107
+ * defineLayout({ areas: ["input?", "content"], layouts: [{ template: "input content" }] }),
108
+ * { nest: { content: ContentGrid } },
109
+ * );
110
+ *
111
+ * <Card>
112
+ * <Card.input as="label" for="notify">…</Card.input>
113
+ * <Card.content>
114
+ * <Card.content.icon>…</Card.content.icon>
115
+ * </Card.content>
116
+ * </Card>
117
+ * ```
118
+ */
119
+ export function createGridComponent(definition, options = {}) {
120
+ const members = {};
121
+ for (const area of definition.areas) {
122
+ const nested = options.nest?.[area];
123
+ if (nested === undefined) {
124
+ members[area] = createSlotComponent(area);
125
+ continue;
126
+ }
127
+ const Nested = nested;
128
+ const NestedRoot = (props) => (<Nested {...mergeProps(props, { "data-area": area })}/>);
129
+ members[area] = withMembers(NestedRoot, membersOf(nested));
130
+ }
131
+ return withMembers(createRoot(definition), members);
132
+ }
@@ -0,0 +1 @@
1
+ export { createGridComponent, type GridComponent, type GridComponentOptions, type GridComponentProps, type SlotComponent, type SlotComponentProps, type SlotRenderFunction, } from "./gridComponent.tsx";
package/dist/index.js ADDED
@@ -0,0 +1,139 @@
1
+ import { Dynamic, createComponent, insert, isServer, mergeProps, spread, ssr, template, useAssets } from "solid-js/web";
2
+ import { children, createEffect, mergeProps as mergeProps$1, sharedConfig, splitProps } from "solid-js";
3
+ import { createSlotCheck, gridMarkup, gridStyleTags, insertGridStyle, isDevelopment } from "typed-grid-areas/adapter";
4
+ // src/gridComponent.tsx
5
+ var _tmpl$ = /*#__PURE__*/ template(`<div>`);
6
+ /**
7
+ * What a server render has registered with `useAssets`, by render. The
8
+ * render's list of assets is internal to Solid; it's only used here as a
9
+ * key that's new for each render.
10
+ */
11
+ var registeredStyles = /* @__PURE__ */ new WeakMap();
12
+ /**
13
+ * Puts a definition's CSS on the page: on the server, its style tag among
14
+ * the render's assets (once per render), which Solid injects before
15
+ * `</head>`; in the browser, its style tag in `<head>`, reusing one the
16
+ * server rendered.
17
+ */
18
+ function emitCss(definition) {
19
+ if (!isServer) {
20
+ insertGridStyle(document, definition);
21
+ return;
22
+ }
23
+ const assets = sharedConfig.context?.assets;
24
+ if (assets === void 0) return;
25
+ const registered = registeredStyles.get(assets) ?? /* @__PURE__ */ new Set();
26
+ registeredStyles.set(assets, registered);
27
+ const { id } = gridMarkup(definition);
28
+ if (registered.has(id)) return;
29
+ registered.add(id);
30
+ useAssets(() => ssr(gridStyleTags([definition])));
31
+ }
32
+ function createRoot(definition) {
33
+ const markup = gridMarkup(definition);
34
+ return function Root(props) {
35
+ emitCss(definition);
36
+ const [local, others] = splitProps(props, [
37
+ "as",
38
+ "ref",
39
+ "children",
40
+ "data-area"
41
+ ]);
42
+ const { sizeContainer, grid: gridElement } = markup.elements(local["data-area"]);
43
+ const resolved = children(() => local.children);
44
+ let grid;
45
+ const ref = (element) => {
46
+ grid = element;
47
+ local.ref?.(element);
48
+ };
49
+ if (!isServer && isDevelopment()) {
50
+ const checkSlots = createSlotCheck(definition, { strayChildRemedy: "make it a slot with `as`" });
51
+ createEffect(() => {
52
+ resolved();
53
+ if (grid !== void 0) for (const warning of checkSlots(grid)) console.warn(warning);
54
+ });
55
+ }
56
+ const element = createComponent(Dynamic, mergeProps({ get component() {
57
+ return local.as ?? "div";
58
+ } }, others, () => gridElement.attributes, {
59
+ ref,
60
+ get children() {
61
+ return resolved();
62
+ }
63
+ }));
64
+ if (sizeContainer === void 0) return element;
65
+ return (() => {
66
+ var _el$ = _tmpl$();
67
+ spread(_el$, mergeProps(() => sizeContainer.attributes, { get style() {
68
+ return sizeContainer.styleText;
69
+ } }), false, true);
70
+ insert(_el$, element);
71
+ return _el$;
72
+ })();
73
+ };
74
+ }
75
+ function createSlotComponent(area) {
76
+ return function SlotComponent(props) {
77
+ const [local, others] = splitProps(props, ["as"]);
78
+ return createComponent(Dynamic, mergeProps({ get component() {
79
+ return local.as ?? "div";
80
+ } }, others, { "data-area": area }));
81
+ };
82
+ }
83
+ /**
84
+ * Defines `members` on a component. `defineProperty`, because an area may be
85
+ * named like a function's own read-only properties (`name`, `length`).
86
+ */
87
+ function withMembers(component, members) {
88
+ for (const [key, value] of Object.entries(members)) Object.defineProperty(component, key, {
89
+ value,
90
+ enumerable: true,
91
+ configurable: true
92
+ });
93
+ return component;
94
+ }
95
+ /** The members of a grid component, one per area. */
96
+ function membersOf(component) {
97
+ return Object.fromEntries(Object.entries(component));
98
+ }
99
+ /**
100
+ * Turns a definition into the component it renders as.
101
+ *
102
+ * `<Card>` renders the grid (inside its own size container when the
103
+ * definition has breakpoints) and puts the definition's CSS on the page;
104
+ * `<Card.icon>` renders the slot that fills the `icon` area. Slots accept
105
+ * `class`, `style`, `children`, regular element props, and `as` to render
106
+ * your own element as the slot.
107
+ *
108
+ * Create grid components at module level, once per definition.
109
+ *
110
+ * @example
111
+ * ```tsx
112
+ * const Card = createGridComponent(
113
+ * defineLayout({ areas: ["input?", "content"], layouts: [{ template: "input content" }] }),
114
+ * { nest: { content: ContentGrid } },
115
+ * );
116
+ *
117
+ * <Card>
118
+ * <Card.input as="label" for="notify">…</Card.input>
119
+ * <Card.content>
120
+ * <Card.content.icon>…</Card.content.icon>
121
+ * </Card.content>
122
+ * </Card>
123
+ * ```
124
+ */
125
+ function createGridComponent(definition, options = {}) {
126
+ const members = {};
127
+ for (const area of definition.areas) {
128
+ const nested = options.nest?.[area];
129
+ if (nested === void 0) {
130
+ members[area] = createSlotComponent(area);
131
+ continue;
132
+ }
133
+ const Nested = nested;
134
+ const NestedRoot = (props) => createComponent(Nested, mergeProps(() => mergeProps$1(props, { "data-area": area })));
135
+ members[area] = withMembers(NestedRoot, membersOf(nested));
136
+ }
137
+ return withMembers(createRoot(definition), members);
138
+ }
139
+ export { createGridComponent };
package/dist/index.jsx ADDED
@@ -0,0 +1 @@
1
+ export { createGridComponent, } from "./gridComponent.jsx";
package/package.json CHANGED
@@ -1 +1,51 @@
1
- {"name":"@typed-grid-areas/solid","version":"0.0.0"}
1
+ {
2
+ "name": "@typed-grid-areas/solid",
3
+ "version": "0.1.0",
4
+ "description": "Solid adapter for typed-grid-areas: render a grid definition as typed slot components.",
5
+ "type": "module",
6
+ "license": "MIT",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/MichaelBuss/typed-grid-areas.git",
10
+ "directory": "packages/solid"
11
+ },
12
+ "homepage": "https://github.com/MichaelBuss/typed-grid-areas#readme",
13
+ "bugs": {
14
+ "url": "https://github.com/MichaelBuss/typed-grid-areas/issues"
15
+ },
16
+ "exports": {
17
+ ".": {
18
+ "types": "./dist/index.d.ts",
19
+ "solid": "./dist/index.jsx",
20
+ "import": "./dist/index.js"
21
+ }
22
+ },
23
+ "files": [
24
+ "dist"
25
+ ],
26
+ "scripts": {
27
+ "build": "vite build && tsc -p tsconfig.build.json",
28
+ "prepack": "npm run build && cp ../../README.md ../../LICENSE .",
29
+ "postpack": "rm README.md LICENSE",
30
+ "test": "vitest run --project unit --project node",
31
+ "test:browser": "vitest run --project browser --project browser-development",
32
+ "typecheck": "tsc --noEmit"
33
+ },
34
+ "peerDependencies": {
35
+ "solid-js": "^1.9.0"
36
+ },
37
+ "dependencies": {
38
+ "typed-grid-areas": "^0.2.0"
39
+ },
40
+ "devDependencies": {
41
+ "@typed-grid-areas/acceptance": "*",
42
+ "@types/node": "^22.20.5",
43
+ "@vitest/browser-playwright": "^5.0.1",
44
+ "playwright": "^1.64.1",
45
+ "solid-js": "^1.9.17",
46
+ "typescript": "^7.0.2",
47
+ "vite": "^8.3.5",
48
+ "vite-plugin-solid": "^2.11.14",
49
+ "vitest": "^5.0.1"
50
+ }
51
+ }