@typed-grid-areas/solid 0.0.0 → 0.1.1

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,143 @@
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, } 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. Written out,
48
+ // not `isDevelopment()`, so production builds drop the check.
49
+ try {
50
+ if (process.env.NODE_ENV === "development") {
51
+ if (!isServer) {
52
+ const checkSlots = createSlotCheck(definition, {
53
+ strayChildRemedy: "make it a slot with `as`",
54
+ });
55
+ createEffect(() => {
56
+ resolved();
57
+ if (grid !== undefined) {
58
+ for (const warning of checkSlots(grid)) {
59
+ // eslint-disable-next-line no-console
60
+ console.warn(warning);
61
+ }
62
+ }
63
+ });
64
+ }
65
+ }
66
+ }
67
+ catch (error) {
68
+ // Without a bundler, `process` may not exist: then this isn't development.
69
+ if (!(error instanceof ReferenceError)) {
70
+ throw error;
71
+ }
72
+ }
73
+ const element = (<Dynamic component={local.as ?? "div"} {...others} {...gridElement.attributes} ref={ref}>
74
+ {resolved()}
75
+ </Dynamic>);
76
+ if (sizeContainer === undefined) {
77
+ return element;
78
+ }
79
+ return (<div {...sizeContainer.attributes} style={sizeContainer.styleText}>
80
+ {element}
81
+ </div>);
82
+ };
83
+ }
84
+ function createSlotComponent(area) {
85
+ return function SlotComponent(props) {
86
+ const [local, others] = splitProps(props, ["as"]);
87
+ return <Dynamic component={local.as ?? "div"} {...others} data-area={area}/>;
88
+ };
89
+ }
90
+ /**
91
+ * Defines `members` on a component. `defineProperty`, because an area may be
92
+ * named like a function's own read-only properties (`name`, `length`).
93
+ */
94
+ function withMembers(component, members) {
95
+ for (const [key, value] of Object.entries(members)) {
96
+ Object.defineProperty(component, key, { value, enumerable: true, configurable: true });
97
+ }
98
+ return component;
99
+ }
100
+ /** The members of a grid component, one per area. */
101
+ function membersOf(component) {
102
+ return Object.fromEntries(Object.entries(component));
103
+ }
104
+ /**
105
+ * Turns a definition into the component it renders as.
106
+ *
107
+ * `<Card>` renders the grid (inside its own size container when the
108
+ * definition has breakpoints) and puts the definition's CSS on the page;
109
+ * `<Card.icon>` renders the slot that fills the `icon` area. Slots accept
110
+ * `class`, `style`, `children`, regular element props, and `as` to render
111
+ * your own element as the slot.
112
+ *
113
+ * Create grid components at module level, once per definition.
114
+ *
115
+ * @example
116
+ * ```tsx
117
+ * const Card = createGridComponent(
118
+ * defineLayout({ areas: ["input?", "content"], layouts: [{ template: "input content" }] }),
119
+ * { nest: { content: ContentGrid } },
120
+ * );
121
+ *
122
+ * <Card>
123
+ * <Card.input as="label" for="notify">…</Card.input>
124
+ * <Card.content>
125
+ * <Card.content.icon>…</Card.content.icon>
126
+ * </Card.content>
127
+ * </Card>
128
+ * ```
129
+ */
130
+ export function createGridComponent(definition, options = {}) {
131
+ const members = {};
132
+ for (const area of definition.areas) {
133
+ const nested = options.nest?.[area];
134
+ if (nested === undefined) {
135
+ members[area] = createSlotComponent(area);
136
+ continue;
137
+ }
138
+ const Nested = nested;
139
+ const NestedRoot = (props) => (<Nested {...mergeProps(props, { "data-area": area })}/>);
140
+ members[area] = withMembers(NestedRoot, membersOf(nested));
141
+ }
142
+ return withMembers(createRoot(definition), members);
143
+ }
@@ -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,145 @@
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 } 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
+ try {
50
+ if (process.env.NODE_ENV === "development") {
51
+ if (!isServer) {
52
+ const checkSlots = createSlotCheck(definition, { strayChildRemedy: "make it a slot with `as`" });
53
+ createEffect(() => {
54
+ resolved();
55
+ if (grid !== void 0) for (const warning of checkSlots(grid)) console.warn(warning);
56
+ });
57
+ }
58
+ }
59
+ } catch (error) {
60
+ if (!(error instanceof ReferenceError)) throw error;
61
+ }
62
+ const element = createComponent(Dynamic, mergeProps({ get component() {
63
+ return local.as ?? "div";
64
+ } }, others, () => gridElement.attributes, {
65
+ ref,
66
+ get children() {
67
+ return resolved();
68
+ }
69
+ }));
70
+ if (sizeContainer === void 0) return element;
71
+ return (() => {
72
+ var _el$ = _tmpl$();
73
+ spread(_el$, mergeProps(() => sizeContainer.attributes, { get style() {
74
+ return sizeContainer.styleText;
75
+ } }), false, true);
76
+ insert(_el$, element);
77
+ return _el$;
78
+ })();
79
+ };
80
+ }
81
+ function createSlotComponent(area) {
82
+ return function SlotComponent(props) {
83
+ const [local, others] = splitProps(props, ["as"]);
84
+ return createComponent(Dynamic, mergeProps({ get component() {
85
+ return local.as ?? "div";
86
+ } }, others, { "data-area": area }));
87
+ };
88
+ }
89
+ /**
90
+ * Defines `members` on a component. `defineProperty`, because an area may be
91
+ * named like a function's own read-only properties (`name`, `length`).
92
+ */
93
+ function withMembers(component, members) {
94
+ for (const [key, value] of Object.entries(members)) Object.defineProperty(component, key, {
95
+ value,
96
+ enumerable: true,
97
+ configurable: true
98
+ });
99
+ return component;
100
+ }
101
+ /** The members of a grid component, one per area. */
102
+ function membersOf(component) {
103
+ return Object.fromEntries(Object.entries(component));
104
+ }
105
+ /**
106
+ * Turns a definition into the component it renders as.
107
+ *
108
+ * `<Card>` renders the grid (inside its own size container when the
109
+ * definition has breakpoints) and puts the definition's CSS on the page;
110
+ * `<Card.icon>` renders the slot that fills the `icon` area. Slots accept
111
+ * `class`, `style`, `children`, regular element props, and `as` to render
112
+ * your own element as the slot.
113
+ *
114
+ * Create grid components at module level, once per definition.
115
+ *
116
+ * @example
117
+ * ```tsx
118
+ * const Card = createGridComponent(
119
+ * defineLayout({ areas: ["input?", "content"], layouts: [{ template: "input content" }] }),
120
+ * { nest: { content: ContentGrid } },
121
+ * );
122
+ *
123
+ * <Card>
124
+ * <Card.input as="label" for="notify">…</Card.input>
125
+ * <Card.content>
126
+ * <Card.content.icon>…</Card.content.icon>
127
+ * </Card.content>
128
+ * </Card>
129
+ * ```
130
+ */
131
+ function createGridComponent(definition, options = {}) {
132
+ const members = {};
133
+ for (const area of definition.areas) {
134
+ const nested = options.nest?.[area];
135
+ if (nested === void 0) {
136
+ members[area] = createSlotComponent(area);
137
+ continue;
138
+ }
139
+ const Nested = nested;
140
+ const NestedRoot = (props) => createComponent(Nested, mergeProps(() => mergeProps$1(props, { "data-area": area })));
141
+ members[area] = withMembers(NestedRoot, membersOf(nested));
142
+ }
143
+ return withMembers(createRoot(definition), members);
144
+ }
145
+ export { createGridComponent };
package/dist/index.jsx ADDED
@@ -0,0 +1 @@
1
+ export { createGridComponent, } from "./gridComponent.jsx";
package/package.json CHANGED
@@ -1 +1,58 @@
1
- {"name":"@typed-grid-areas/solid","version":"0.0.0"}
1
+ {
2
+ "name": "@typed-grid-areas/solid",
3
+ "version": "0.1.1",
4
+ "description": "Solid adapter for typed-grid-areas: render a grid definition as typed slot components.",
5
+ "keywords": [
6
+ "css-grid",
7
+ "grid-template-areas",
8
+ "layout",
9
+ "typescript",
10
+ "solid"
11
+ ],
12
+ "type": "module",
13
+ "license": "MIT",
14
+ "repository": {
15
+ "type": "git",
16
+ "url": "git+https://github.com/MichaelBuss/typed-grid-areas.git",
17
+ "directory": "packages/solid"
18
+ },
19
+ "homepage": "https://michaelbuss.github.io/typed-grid-areas/",
20
+ "bugs": {
21
+ "url": "https://github.com/MichaelBuss/typed-grid-areas/issues"
22
+ },
23
+ "exports": {
24
+ ".": {
25
+ "types": "./dist/index.d.ts",
26
+ "solid": "./dist/index.jsx",
27
+ "import": "./dist/index.js"
28
+ }
29
+ },
30
+ "files": [
31
+ "dist"
32
+ ],
33
+ "scripts": {
34
+ "build": "vite build && tsc -p tsconfig.build.json",
35
+ "prepack": "npm run build && cp ../../README.md ../../LICENSE .",
36
+ "postpack": "rm README.md LICENSE",
37
+ "test": "vitest run --project unit --project node",
38
+ "test:browser": "vitest run --project browser --project browser-development",
39
+ "typecheck": "tsc --noEmit"
40
+ },
41
+ "peerDependencies": {
42
+ "solid-js": "^1.9.0"
43
+ },
44
+ "dependencies": {
45
+ "typed-grid-areas": "^0.3.0"
46
+ },
47
+ "devDependencies": {
48
+ "@typed-grid-areas/acceptance": "*",
49
+ "@types/node": "^22.20.5",
50
+ "@vitest/browser-playwright": "^5.0.1",
51
+ "playwright": "^1.64.1",
52
+ "solid-js": "^1.9.17",
53
+ "typescript": "^7.0.2",
54
+ "vite": "^8.3.5",
55
+ "vite-plugin-solid": "^2.11.14",
56
+ "vitest": "^5.0.1"
57
+ }
58
+ }