@loom-forge/react 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.
package/README.md ADDED
@@ -0,0 +1,144 @@
1
+ # @loom-forge/react
2
+
3
+ **loom** · a camada de view. Consome a `ResolvedNode` do [`@loom-forge/core`](../core/README.md) e
4
+ instancia componentes React: o registry `type → componente`, os renderers de layout e o
5
+ events-out. Enxuto de propósito — nenhum algoritmo de resolução mora aqui.
6
+
7
+ ```bash
8
+ pnpm add @loom-forge/react react
9
+ ```
10
+
11
+ ```ts
12
+ import {
13
+ createEngine,
14
+ ResolverProvider,
15
+ ResolverView,
16
+ Render,
17
+ useResolver,
18
+ useAction,
19
+ useCapability,
20
+ createComponentRegistry,
21
+ fragmentRenderer,
22
+ sectionRenderer,
23
+ gridFormRenderer,
24
+ defaultLayoutRenderers,
25
+ } from "@loom-forge/react";
26
+ ```
27
+
28
+ ---
29
+
30
+ ## Engine + render
31
+
32
+ ```ts
33
+ createEngine(opts?: CreateEngineOptions): Engine
34
+ ```
35
+
36
+ | Opção | Default | O quê |
37
+ | ------------------------------ | ---------------------- | --------------------------------------------- |
38
+ | `binder` | `identityBinder` | `BindingEngine` (ex.: `createTsliteBinder()`) |
39
+ | `resolver` | `createTypeResolver()` | estratégia de resolução |
40
+ | `planners` / `layoutRenderers` | defaults | mapas `name → planner`/`renderer` |
41
+ | `defaultLayout` | `"fragment"` | layout quando o nó não declara |
42
+ | `actions` | — | `ActionPort` (events-out) |
43
+ | `onActionResult` | warn em falha (dev) | recebe TODO envelope que voltou de `actions` |
44
+ | `capabilities` | — | `CapabilityPort` (escape imperativo) |
45
+ | `unknownComponent` | — | componente p/ type desconhecido sem fallback |
46
+ | `onUnknown` | warn (dev) | callback de diagnóstico |
47
+
48
+ ```ts
49
+ const engine = createEngine()
50
+ .register("Page", PageComp, { layout: "section" }) // popula descriptor (core) + componente (view)
51
+ .register("Field", FieldComp);
52
+
53
+ <ResolverProvider engine={engine}>
54
+ <ResolverView node={definition} data={data} />
55
+ </ResolverProvider>;
56
+ ```
57
+
58
+ - **`engine.register(type, component, descriptor?)`** — popula os DOIS registries de uma vez. O
59
+ `descriptor` aceita `{ defaultProps, schema, layout, meta, childrenProp }` (`childrenProp` = prop que
60
+ recebe os filhos arrumados; default `"children"`).
61
+ - **`<ResolverView node data?>`** — aplica o `bind` (memoizado em `[engine, node, data]`) e renderiza.
62
+ Muda o `data` → re-bind/re-resolve/re-render (loop de SPA).
63
+ - **`<Render node>`** — renderiza uma `ResolvedNode` já resolvida (interno; `renderResolved` é a função).
64
+ - **`engine.updates`** — o destino de uma intenção marcada `local`: `apply(action, params)` é o
65
+ update de um componente vivo, e `pending?(action, flag)` é o pendente da instância, `true` do
66
+ despacho ao resultado de uma ação do host (ADR-018 do repo). Quem os provê é o boundary do
67
+ [`forge-react`](../forge-react/README.md), no engine do corpo; uma intenção marcada nunca vai
68
+ para `actions` (ADR-016 do repo).
69
+ - **`intentRuntimeOf(engine)`** — o `IntentRuntime` do core montado a partir do engine: o binder
70
+ para avaliar os `params` no despacho, `updates.apply`/`pending` quando há boundary, a porta e o
71
+ `onActionResult`. É com ele que cada evento roda `runIntent(binding, emitido, rt)` — a intenção
72
+ e a cadeia `then`/`catch` dela. Exportado para quem monta o próprio boundary.
73
+
74
+ ## Modo de inspeção
75
+
76
+ ```ts
77
+ import { originsAt, elementsOf, boundsOf } from "@loom-forge/react";
78
+
79
+ const engine = createEngine({ inspect: true });
80
+
81
+ element.addEventListener("click", (e) => {
82
+ const path = originsAt(e.target); // [{ component, id }, …] — de fora para dentro
83
+ });
84
+ elementsOf(stage, { component: "Form", id: "email" }); // os elementos que desenham o nó
85
+ boundsOf(wrapper); // onde ele está na tela
86
+ ```
87
+
88
+ Com `inspect`, cada nó com origem sai embrulhado num `<loom-node>` com `display: contents` e a origem
89
+ em atributo. É o que um editor visual usa para focar pelo clique e desenhar o contorno — sem mudar o
90
+ layout (o invólucro não gera caixa) e sem exigir nada do componente registrado. O que focar é decisão
91
+ de quem edita; a origem vem da expansão do `forge` (ADR-017 do repo). Desligado, não há invólucro.
92
+
93
+ ## Hooks
94
+
95
+ | Hook | Retorno | Uso |
96
+ | --------------------- | ------------------------- | ---------------------------------- |
97
+ | `useResolver()` | `Engine` | acesso ao engine ativo |
98
+ | `useAction()` | `ActionPort \| undefined` | dispatch imperativo de ação |
99
+ | `useCapability(name)` | `unknown` | escape imperativo (router/mapa/ws) |
100
+
101
+ ## Component registry + renderers
102
+
103
+ `createComponentRegistry()` → `{ register(type, component, { childrenProp? }), get, has }`.
104
+ Renderers default: `fragmentRenderer()` · `sectionRenderer()` · `gridFormRenderer()` ·
105
+ `defaultLayoutRenderers()`.
106
+
107
+ ---
108
+
109
+ ## `grid-form` responsivo
110
+
111
+ `placement.span` aceita número (fixo, inline, zero-config) OU `{ base, sm, md, lg, xl }` (responsivo).
112
+ O responsivo usa **container queries** e requer:
113
+
114
+ ```ts
115
+ import "@loom-forge/react/grid-form.css";
116
+ ```
117
+
118
+ ```jsonc
119
+ {
120
+ "type": "Form",
121
+ "layout": "grid-form",
122
+ "children": [
123
+ {
124
+ "type": "Field",
125
+ "placement": { "span": { "base": 12, "md": 6, "lg": 4 } },
126
+ },
127
+ ],
128
+ }
129
+ ```
130
+
131
+ Breakpoints (largura do **container**): `sm` 40rem · `md` 48rem · `lg` 64rem · `xl` 80rem.
132
+
133
+ ---
134
+
135
+ ## Os vizinhos
136
+
137
+ Este package renderiza; o resto entra por port.
138
+
139
+ | quero… | package |
140
+ | ---------------------------------------------- | ------------------------------------------------------------------------------------------------- |
141
+ | expressões `{{ }}`, `each`/`when` | [`@loom-forge/tslite`](../tslite/README.md) |
142
+ | disparar ações de verdade no `events` | o próprio runtime do host, na opção `actions` — ver o `ActionPort` no [`core`](../core/README.md) |
143
+ | o layout `grid` (dashboard, react-grid-layout) | [`@loom-forge/grid`](../grid/README.md) |
144
+ | declarar componentes NOVOS em JSON | [`@loom-forge/forge`](../forge/README.md) + [`forge-react`](../forge-react/README.md) |
@@ -0,0 +1,44 @@
1
+ /* @loom-forge/react/grid-form.css
2
+ Estilo do grid-form RESPONSIVO (slots por breakpoint). Só é necessário quando algum
3
+ `placement.span` é um objeto `{ base, sm, md, lg, xl }`; spans numéricos fixos usam style
4
+ inline e NÃO precisam deste arquivo.
5
+
6
+ Container queries (não viewport): cada grid-form se adapta à largura do PRÓPRIO container —
7
+ um form numa sidebar estreita encolhe os slots certo, independente da tela. */
8
+
9
+ .dui-gf {
10
+ container-type: inline-size;
11
+ display: grid;
12
+ grid-template-columns: repeat(var(--gf-cols, 12), minmax(0, 1fr));
13
+ gap: var(--gf-gap, 1rem);
14
+ }
15
+
16
+ /* `grid-column-end` (longhand) → não pisa no `grid-column-start` da quebra de linha. */
17
+ .dui-gf-cell {
18
+ min-width: 0;
19
+ grid-column-end: span var(--gf-span-base);
20
+ }
21
+ .dui-gf-break {
22
+ grid-column-start: 1;
23
+ }
24
+
25
+ @container (min-width: 40rem) {
26
+ .dui-gf-cell {
27
+ grid-column-end: span var(--gf-span-sm);
28
+ }
29
+ }
30
+ @container (min-width: 48rem) {
31
+ .dui-gf-cell {
32
+ grid-column-end: span var(--gf-span-md);
33
+ }
34
+ }
35
+ @container (min-width: 64rem) {
36
+ .dui-gf-cell {
37
+ grid-column-end: span var(--gf-span-lg);
38
+ }
39
+ }
40
+ @container (min-width: 80rem) {
41
+ .dui-gf-cell {
42
+ grid-column-end: span var(--gf-span-xl);
43
+ }
44
+ }
@@ -0,0 +1,182 @@
1
+ import * as react from 'react';
2
+ import { ComponentType, ReactNode } from 'react';
3
+ import { DescriptorRegistry, BindingEngine, Resolver, LayoutPlanner, ActionPort, ActionResult, CapabilityPort, ResolvedNode, IntentRuntime, NodeOrigin, DefNode } from '@loom-forge/core';
4
+
5
+ /** Entrada do registry de componentes (camada de view). `childrenProp` = prop que recebe
6
+ * os filhos arrumados (default `"children"`). */
7
+ interface ComponentEntry {
8
+ component: ComponentType<any>;
9
+ childrenProp?: string;
10
+ }
11
+ interface ComponentRegistry {
12
+ register(type: string, component: ComponentType<any>, opts?: {
13
+ childrenProp?: string;
14
+ }): ComponentRegistry;
15
+ get(type: string): ComponentEntry | undefined;
16
+ has(type: string): boolean;
17
+ }
18
+ /** Registry `type → componente React`. Separado do registry de descriptors (core) — a view
19
+ * mapeia type→componente; o core só conhece o descriptor (headless). */
20
+ declare function createComponentRegistry(): ComponentRegistry;
21
+
22
+ interface LayoutRenderer {
23
+ name: string;
24
+ render(plan: unknown, children: Map<string, ReactNode>): ReactNode;
25
+ }
26
+ declare function fragmentRenderer(): LayoutRenderer;
27
+ declare function sectionRenderer(): LayoutRenderer;
28
+ declare function gridFormRenderer(): LayoutRenderer;
29
+ /** renderers default — pareados com `defaultPlanners()` do core. */
30
+ declare function defaultLayoutRenderers(): Record<string, LayoutRenderer>;
31
+
32
+ /** Descriptor passado no `register` (mistura metadado headless + `childrenProp` de view). */
33
+ interface EngineDescriptor {
34
+ defaultProps?: Record<string, unknown>;
35
+ schema?: unknown;
36
+ layout?: string;
37
+ meta?: Record<string, unknown>;
38
+ /** prop que recebe os filhos arrumados (default `"children"`). */
39
+ childrenProp?: string;
40
+ }
41
+ /** O engine montado: core (descriptors/resolver/binder/planners) + view
42
+ * (componentes/renderers). `register` popula os dois registries de uma vez. */
43
+ interface Engine {
44
+ descriptors: DescriptorRegistry;
45
+ components: ComponentRegistry;
46
+ binder: BindingEngine;
47
+ resolver: Resolver;
48
+ planners: Record<string, LayoutPlanner>;
49
+ layoutRenderers: Record<string, LayoutRenderer>;
50
+ defaultLayout: string;
51
+ /** PORT canônico de efeito: events-out e dispatch imperativo — o runtime do host, sem adapter (ADR-115 do core). */
52
+ actions?: ActionPort;
53
+ /**
54
+ * Onde o RESULTADO de um despacho pousa.
55
+ *
56
+ * O handler é síncrono e devolve `void` — é o contrato do React, que não aguarda handler. O
57
+ * resultado, sync ou async, chega aqui: é isso que impede uma falha de sumir e uma promessa
58
+ * rejeitada virar ruído solto. Ausente, uma falha vira aviso em dev.
59
+ */
60
+ onActionResult?(id: string, result: ActionResult): void;
61
+ /**
62
+ * O destino de uma intenção LOCAL (`binding.local`): o update do componente vivo mais próximo.
63
+ *
64
+ * O compositor não sabe o que é um update — quem provê isto é o boundary de um componente vivo,
65
+ * no engine que ele passa ao próprio corpo. Uma intenção marcada nunca sai pela `actions`: o nome
66
+ * de um update não é id da aplicação.
67
+ */
68
+ updates?: {
69
+ apply(name: string, params?: unknown): void;
70
+ /** o pendente da instância: `true` do despacho ao resultado de uma ação do host (ADR-018).
71
+ * É o boundary que o guarda, porque pendente é fato de uma INSTÂNCIA. */
72
+ pending?(action: string, flag: boolean): void;
73
+ };
74
+ /**
75
+ * MODO DE INSPEÇÃO: cada nó com origem sai embrulhado num `<loom-node>` com `display: contents`,
76
+ * que marca no DOM de onde ele veio sem mudar o layout nem exigir nada do componente. É o que um
77
+ * editor usa para focar pelo clique (`originsAt`) e para achar o que desenhar em volta
78
+ * (`elementsOf`, `boundsOf`). Desligado, não há invólucro nenhum.
79
+ */
80
+ inspect?: boolean;
81
+ /** ESCAPE imperativo: capabilities vivas do host (router, mapa, ws…). */
82
+ capabilities?: CapabilityPort;
83
+ unknownComponent?: ComponentType<{
84
+ node: {
85
+ id: string;
86
+ type: string;
87
+ };
88
+ }>;
89
+ onUnknown?(node: {
90
+ id: string;
91
+ type: string;
92
+ }): void;
93
+ register(type: string, component: ComponentType<any>, descriptor?: EngineDescriptor): Engine;
94
+ }
95
+
96
+ /** Provê o engine pra árvore. */
97
+ declare function ResolverProvider({ engine, children, }: {
98
+ engine: Engine;
99
+ children: ReactNode;
100
+ }): react.JSX.Element;
101
+ /** Acessa o engine ativo (componentes do domínio: dispatch de ações, leitura do registry…). */
102
+ declare function useResolver(): Engine;
103
+ /** O ActionPort ativo — pra componentes dispararem ações imperativamente (canônico de efeito). */
104
+ declare function useAction(): ActionPort | undefined;
105
+ /** ESCAPE imperativo: pega uma capability viva do host (router, mapa, ws…). Use com parcimônia
106
+ * — o caminho canônico é via actions. */
107
+ declare function useCapability(name: string): unknown;
108
+
109
+ /** Renderiza uma `ResolvedNode` (saída do core) usando o registry de componentes + os
110
+ * renderers de layout do engine. */
111
+ declare function Render({ node }: {
112
+ node: ResolvedNode;
113
+ }): ReactNode;
114
+ declare function renderResolved(node: ResolvedNode, engine: Engine): ReactNode;
115
+ /**
116
+ * O que a cadeia de uma intenção precisa de quem a executa, lido do engine: o binder para
117
+ * avaliar os `params` no despacho, o boundary (quando há) para o update local e o pendente, a
118
+ * porta e o `onActionResult` do host.
119
+ */
120
+ declare function intentRuntimeOf(engine: Engine): IntentRuntime;
121
+
122
+ /** a tag do invólucro — um custom element, válido em qualquer lugar da árvore. */
123
+ declare const INSPECT_TAG = "loom-node";
124
+ /**
125
+ * A chave de uma origem num atributo: `componente#id`, cada parte codificada. Codificar é o que
126
+ * garante que nenhuma parte tenha espaço (o separador da lista) nem `#`.
127
+ */
128
+ declare const originKey: (origin: NodeOrigin) => string;
129
+ /**
130
+ * As origens do ponto onde um evento caiu, do nó mais de FORA para o mais de dentro.
131
+ *
132
+ * Sobe pelos invólucros a partir do alvo e junta o que cada um diz — um nó que ocupa uma chamada
133
+ * responde pelos dois. Quem decide QUAL delas focar é o editor (o escopo em edição, um duplo-clique
134
+ * que entra no componente).
135
+ */
136
+ declare function originsAt(target: EventTarget | null): NodeOrigin[];
137
+ /** Os invólucros que desenham um nó — todas as cópias, quando ele se repete num `each`. */
138
+ declare function elementsOf(root: ParentNode, origin: NodeOrigin): Element[];
139
+ /**
140
+ * A caixa de um nó na tela: a do que o invólucro contém, porque ele mesmo não tem caixa.
141
+ * `undefined` quando o nó não ocupa espaço nenhum.
142
+ */
143
+ declare function boundsOf(element: Element): DOMRect | undefined;
144
+
145
+ interface CreateEngineOptions {
146
+ binder?: BindingEngine;
147
+ resolver?: Resolver;
148
+ descriptors?: DescriptorRegistry;
149
+ components?: ComponentRegistry;
150
+ planners?: Record<string, LayoutPlanner>;
151
+ layoutRenderers?: Record<string, LayoutRenderer>;
152
+ defaultLayout?: string;
153
+ actions?: ActionPort;
154
+ /** onde o RESULTADO de um despacho pousa — ver `Engine.onActionResult`. */
155
+ onActionResult?(id: string, result: ActionResult): void;
156
+ /** o MODO DE INSPEÇÃO — ver `Engine.inspect`. */
157
+ inspect?: boolean;
158
+ capabilities?: CapabilityPort;
159
+ unknownComponent?: ComponentType<{
160
+ node: {
161
+ id: string;
162
+ type: string;
163
+ };
164
+ }>;
165
+ onUnknown?(node: {
166
+ id: string;
167
+ type: string;
168
+ }): void;
169
+ }
170
+ /**
171
+ * Monta o engine (core + view). `register(type, component, descriptor?)` popula os DOIS
172
+ * registries (descriptor headless + componente React) de uma vez. Sobrescreva qualquer
173
+ * plugin/port pelas opções.
174
+ */
175
+ declare function createEngine(opts?: CreateEngineOptions): Engine;
176
+ /** Entrada app-facing: resolve a árvore (core, memoizado) e renderiza (view). */
177
+ declare function ResolverView({ node, data, }: {
178
+ node: DefNode;
179
+ data?: unknown;
180
+ }): ReactNode;
181
+
182
+ export { type ComponentEntry, type ComponentRegistry, type CreateEngineOptions, type Engine, type EngineDescriptor, INSPECT_TAG, type LayoutRenderer, Render, ResolverProvider, ResolverView, boundsOf, createComponentRegistry, createEngine, defaultLayoutRenderers, elementsOf, fragmentRenderer, gridFormRenderer, intentRuntimeOf, originKey, originsAt, renderResolved, sectionRenderer, useAction, useCapability, useResolver };
package/dist/index.js ADDED
@@ -0,0 +1,268 @@
1
+ import { createContext, useContext, createElement, useMemo } from 'react';
2
+ import { jsx } from 'react/jsx-runtime';
3
+ import { runIntent, intentParams, defaultPlanners, createTypeResolver, identityBinder, createDescriptorRegistry, resolveTree } from '@loom-forge/core';
4
+
5
+ // src/context.tsx
6
+ var EngineContext = createContext(null);
7
+ function ResolverProvider({
8
+ engine,
9
+ children
10
+ }) {
11
+ return /* @__PURE__ */ jsx(EngineContext.Provider, { value: engine, children });
12
+ }
13
+ function useResolver() {
14
+ const engine = useContext(EngineContext);
15
+ if (!engine)
16
+ throw new Error("useResolver: precisa estar dentro de <ResolverProvider>.");
17
+ return engine;
18
+ }
19
+ function useAction() {
20
+ return useResolver().actions;
21
+ }
22
+ function useCapability(name) {
23
+ return useResolver().capabilities?.get(name);
24
+ }
25
+ var INSPECT_TAG = "loom-node";
26
+ var ATTRIBUTE = "data-loom-origins";
27
+ var originKey = (origin) => `${encodeURIComponent(origin.component)}#${encodeURIComponent(origin.id)}`;
28
+ var originOfKey = (key) => {
29
+ const at = key.indexOf("#");
30
+ if (at < 0) return void 0;
31
+ return {
32
+ component: decodeURIComponent(key.slice(0, at)),
33
+ id: decodeURIComponent(key.slice(at + 1))
34
+ };
35
+ };
36
+ function inspectable(key, origins, element) {
37
+ return createElement(
38
+ INSPECT_TAG,
39
+ {
40
+ key,
41
+ [ATTRIBUTE]: origins.map(originKey).join(" "),
42
+ style: { display: "contents" }
43
+ },
44
+ element
45
+ );
46
+ }
47
+ var SELECTOR = `${INSPECT_TAG}[${ATTRIBUTE}]`;
48
+ function originsAt(target) {
49
+ const start = target instanceof Element ? target : target instanceof Node ? target.parentElement : null;
50
+ const path = [];
51
+ let at = start?.closest(SELECTOR) ?? null;
52
+ while (at) {
53
+ path.unshift(
54
+ (at.getAttribute(ATTRIBUTE) ?? "").split(" ").map(originOfKey).filter((origin) => origin !== void 0)
55
+ );
56
+ at = at.parentElement?.closest(SELECTOR) ?? null;
57
+ }
58
+ return path.flat();
59
+ }
60
+ function elementsOf(root, origin) {
61
+ return [
62
+ ...root.querySelectorAll(
63
+ `${INSPECT_TAG}[${ATTRIBUTE}~="${originKey(origin)}"]`
64
+ )
65
+ ];
66
+ }
67
+ function boundsOf(element) {
68
+ const range = element.ownerDocument.createRange();
69
+ range.selectNodeContents(element);
70
+ const box = range.getBoundingClientRect();
71
+ return box.width === 0 && box.height === 0 ? void 0 : box;
72
+ }
73
+
74
+ // src/render.tsx
75
+ function Render({ node }) {
76
+ const engine = useResolver();
77
+ return renderResolved(node, engine);
78
+ }
79
+ function renderResolved(node, engine) {
80
+ if (node.kind === "drop") return null;
81
+ if (node.kind === "unknown") return fallback(engine, node);
82
+ const isEl = node.el === true;
83
+ const entry = isEl ? void 0 : engine.components.get(node.type);
84
+ if (!isEl && !entry)
85
+ return fallback(engine, { id: node.id, type: node.type });
86
+ const finalProps = { ...node.props };
87
+ if (node.children.length) {
88
+ const byId = /* @__PURE__ */ new Map();
89
+ for (const c of node.children) byId.set(c.id, renderResolved(c, engine));
90
+ const renderer = node.layout ? engine.layoutRenderers[node.layout.name] : void 0;
91
+ const arranged = renderer && node.layout ? renderer.render(node.layout.plan, byId) : node.children.map((c) => byId.get(c.id));
92
+ finalProps[isEl ? "children" : entry.childrenProp ?? "children"] = arranged;
93
+ }
94
+ if (node.events) {
95
+ const rt = intentRuntimeOf(engine);
96
+ for (const [name, binding] of Object.entries(node.events))
97
+ finalProps[name] = (emitted) => runIntent(binding, emitted, rt);
98
+ }
99
+ const element = createElement(isEl ? node.type : entry.component, {
100
+ ...finalProps,
101
+ key: node.id
102
+ });
103
+ return engine.inspect && node.origins?.length ? inspectable(node.id, node.origins, element) : element;
104
+ }
105
+ function warnOrphanLocal(action) {
106
+ const g = globalThis;
107
+ if (g.process?.env?.NODE_ENV !== "production")
108
+ console.warn(
109
+ `[loom] local intent "${action}" has no live component to apply it \u2014 it does not go out through the port, because an update name is not an application id.`
110
+ );
111
+ }
112
+ function fallback(engine, node) {
113
+ if (engine.unknownComponent)
114
+ return createElement(engine.unknownComponent, { node, key: node.id });
115
+ engine.onUnknown?.(node);
116
+ return null;
117
+ }
118
+ function intentRuntimeOf(engine) {
119
+ const updates = engine.updates;
120
+ return {
121
+ params: (binding, event) => intentParams(engine.binder, binding, event),
122
+ ...updates ? { apply: (name, params) => updates.apply(name, params) } : {},
123
+ orphan: warnOrphanLocal,
124
+ ...engine.actions ? { port: engine.actions } : {},
125
+ ...engine.onActionResult ? { onResult: engine.onActionResult.bind(engine) } : {},
126
+ ...updates?.pending ? { pending: (action, flag) => updates.pending(action, flag) } : {}
127
+ };
128
+ }
129
+
130
+ // src/registry.ts
131
+ function createComponentRegistry() {
132
+ const map = /* @__PURE__ */ new Map();
133
+ const registry = {
134
+ register(type, component, opts) {
135
+ map.set(type, { component, childrenProp: opts?.childrenProp });
136
+ return registry;
137
+ },
138
+ get: (type) => map.get(type),
139
+ has: (type) => map.has(type)
140
+ };
141
+ return registry;
142
+ }
143
+ function fragmentRenderer() {
144
+ return {
145
+ name: "fragment",
146
+ render: (plan, children) => plan.order.map((id) => children.get(id))
147
+ };
148
+ }
149
+ function sectionRenderer() {
150
+ return {
151
+ name: "section",
152
+ render(plan, children) {
153
+ const p = plan;
154
+ const style = {
155
+ display: "flex",
156
+ flexDirection: p.direction
157
+ };
158
+ if (p.gap != null) style.gap = p.gap;
159
+ return /* @__PURE__ */ jsx("div", { style, children: p.order.map((id) => children.get(id)) });
160
+ }
161
+ };
162
+ }
163
+ function gridFormRenderer() {
164
+ return {
165
+ name: "grid-form",
166
+ render(plan, children) {
167
+ const p = plan;
168
+ if (p.responsive) {
169
+ const wrap2 = {
170
+ "--gf-cols": p.columns,
171
+ "--gf-gap": typeof p.gap === "number" ? `${p.gap}px` : p.gap
172
+ };
173
+ return /* @__PURE__ */ jsx("div", { className: "dui-gf", style: wrap2, children: p.cells.map((c) => {
174
+ const cell = {
175
+ "--gf-span-base": c.spans.base,
176
+ "--gf-span-sm": c.spans.sm,
177
+ "--gf-span-md": c.spans.md,
178
+ "--gf-span-lg": c.spans.lg,
179
+ "--gf-span-xl": c.spans.xl
180
+ };
181
+ return /* @__PURE__ */ jsx(
182
+ "div",
183
+ {
184
+ className: c.break ? "dui-gf-cell dui-gf-break" : "dui-gf-cell",
185
+ style: cell,
186
+ children: children.get(c.id)
187
+ },
188
+ c.id
189
+ );
190
+ }) });
191
+ }
192
+ const wrap = {
193
+ display: "grid",
194
+ gridTemplateColumns: `repeat(${p.columns}, minmax(0, 1fr))`,
195
+ gap: p.gap
196
+ };
197
+ return /* @__PURE__ */ jsx("div", { style: wrap, children: p.cells.map((c) => {
198
+ const cell = {
199
+ gridColumn: `span ${c.span} / span ${c.span}`
200
+ };
201
+ if (c.break) cell.gridColumnStart = 1;
202
+ return /* @__PURE__ */ jsx("div", { style: cell, children: children.get(c.id) }, c.id);
203
+ }) });
204
+ }
205
+ };
206
+ }
207
+ function defaultLayoutRenderers() {
208
+ return {
209
+ fragment: fragmentRenderer(),
210
+ section: sectionRenderer(),
211
+ "grid-form": gridFormRenderer()
212
+ };
213
+ }
214
+ var warnUnknown = (node) => {
215
+ const g = globalThis;
216
+ if (g.process?.env?.NODE_ENV !== "production")
217
+ console.warn(`[loom] tipo n\xE3o resolvido: "${node.type}" (id="${node.id}")`);
218
+ };
219
+ function createEngine(opts = {}) {
220
+ const engine = {
221
+ descriptors: opts.descriptors ?? createDescriptorRegistry(),
222
+ components: opts.components ?? createComponentRegistry(),
223
+ binder: opts.binder ?? identityBinder,
224
+ resolver: opts.resolver ?? createTypeResolver(),
225
+ planners: opts.planners ?? defaultPlanners(),
226
+ layoutRenderers: opts.layoutRenderers ?? defaultLayoutRenderers(),
227
+ defaultLayout: opts.defaultLayout ?? "fragment",
228
+ actions: opts.actions,
229
+ onActionResult: opts.onActionResult,
230
+ inspect: opts.inspect,
231
+ capabilities: opts.capabilities,
232
+ unknownComponent: opts.unknownComponent,
233
+ onUnknown: opts.onUnknown ?? warnUnknown,
234
+ register(type, component, descriptor) {
235
+ const { childrenProp, ...desc } = descriptor ?? {};
236
+ engine.descriptors.register({ type, ...desc });
237
+ engine.components.register(type, component, { childrenProp });
238
+ return engine;
239
+ }
240
+ };
241
+ return engine;
242
+ }
243
+ function ResolverView({
244
+ node,
245
+ data
246
+ }) {
247
+ const engine = useResolver();
248
+ const resolved = useMemo(
249
+ () => resolveTree(
250
+ node,
251
+ {
252
+ registry: engine.descriptors,
253
+ binder: engine.binder,
254
+ resolver: engine.resolver,
255
+ planners: engine.planners,
256
+ defaultLayout: engine.defaultLayout
257
+ },
258
+ { data }
259
+ // a raiz do escopo: `{{ data.x }}`
260
+ ),
261
+ [engine, node, data]
262
+ );
263
+ return /* @__PURE__ */ jsx(Render, { node: resolved });
264
+ }
265
+
266
+ export { INSPECT_TAG, Render, ResolverProvider, ResolverView, boundsOf, createComponentRegistry, createEngine, defaultLayoutRenderers, elementsOf, fragmentRenderer, gridFormRenderer, intentRuntimeOf, originKey, originsAt, renderResolved, sectionRenderer, useAction, useCapability, useResolver };
267
+ //# sourceMappingURL=index.js.map
268
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/context.tsx","../src/inspect.ts","../src/render.tsx","../src/registry.ts","../src/layout.tsx","../src/engine.tsx"],"names":["createElement","jsx","wrap"],"mappings":"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","file":"index.js","sourcesContent":["import { createContext, useContext, type ReactNode } from \"react\";\nimport type { ActionPort } from \"@loom-forge/core\";\nimport type { Engine } from \"./types\";\n\nconst EngineContext = createContext<Engine | null>(null);\n\n/** Provê o engine pra árvore. */\nexport function ResolverProvider({\n engine,\n children,\n}: {\n engine: Engine;\n children: ReactNode;\n}) {\n return (\n <EngineContext.Provider value={engine}>{children}</EngineContext.Provider>\n );\n}\n\n/** Acessa o engine ativo (componentes do domínio: dispatch de ações, leitura do registry…). */\nexport function useResolver(): Engine {\n const engine = useContext(EngineContext);\n if (!engine)\n throw new Error(\"useResolver: precisa estar dentro de <ResolverProvider>.\");\n return engine;\n}\n\n/** O ActionPort ativo — pra componentes dispararem ações imperativamente (canônico de efeito). */\nexport function useAction(): ActionPort | undefined {\n return useResolver().actions;\n}\n\n/** ESCAPE imperativo: pega uma capability viva do host (router, mapa, ws…). Use com parcimônia\n * — o caminho canônico é via actions. */\nexport function useCapability(name: string): unknown {\n return useResolver().capabilities?.get(name);\n}\n","import { createElement, type ReactElement, type ReactNode } from \"react\";\nimport type { NodeOrigin } from \"@loom-forge/core\";\n\n/* ──────────────────────────────────────────────────────────────────────────────\n INSPEÇÃO — a tela de volta ao documento.\n\n Um editor visual precisa responder duas perguntas sobre o DOM: \"este clique caiu\n em QUE nó?\" e \"onde, na tela, está ESTE nó?\". O componente registrado não ajuda —\n ele recebe props e desenha o que quiser —, e o id da árvore resolvida não é mais\n o que o autor escreveu.\n\n A resposta é um invólucro `display: contents` com a origem em atributo. `contents`\n não gera caixa: o filho continua sendo item do flex, do grid ou do que for, e o\n layout não muda. Os renderers de layout põem cada filho numa caixa própria ou\n direto no contêiner, e nenhum clona o filho — então o invólucro é transparente\n para todos eles.\n ────────────────────────────────────────────────────────────────────────────── */\n\n/** a tag do invólucro — um custom element, válido em qualquer lugar da árvore. */\nexport const INSPECT_TAG = \"loom-node\";\n\nconst ATTRIBUTE = \"data-loom-origins\";\n\n/**\n * A chave de uma origem num atributo: `componente#id`, cada parte codificada. Codificar é o que\n * garante que nenhuma parte tenha espaço (o separador da lista) nem `#`.\n */\nexport const originKey = (origin: NodeOrigin): string =>\n `${encodeURIComponent(origin.component)}#${encodeURIComponent(origin.id)}`;\n\nconst originOfKey = (key: string): NodeOrigin | undefined => {\n const at = key.indexOf(\"#\");\n if (at < 0) return undefined;\n return {\n component: decodeURIComponent(key.slice(0, at)),\n id: decodeURIComponent(key.slice(at + 1)),\n };\n};\n\n/** O nó embrulhado com a origem dele. */\nexport function inspectable(\n key: string,\n origins: readonly NodeOrigin[],\n element: ReactNode,\n): ReactElement {\n return createElement(\n INSPECT_TAG,\n {\n key,\n [ATTRIBUTE]: origins.map(originKey).join(\" \"),\n style: { display: \"contents\" },\n },\n element,\n );\n}\n\nconst SELECTOR = `${INSPECT_TAG}[${ATTRIBUTE}]`;\n\n/**\n * As origens do ponto onde um evento caiu, do nó mais de FORA para o mais de dentro.\n *\n * Sobe pelos invólucros a partir do alvo e junta o que cada um diz — um nó que ocupa uma chamada\n * responde pelos dois. Quem decide QUAL delas focar é o editor (o escopo em edição, um duplo-clique\n * que entra no componente).\n */\nexport function originsAt(target: EventTarget | null): NodeOrigin[] {\n const start =\n target instanceof Element\n ? target\n : target instanceof Node\n ? target.parentElement\n : null;\n const path: NodeOrigin[][] = [];\n let at = start?.closest(SELECTOR) ?? null;\n while (at) {\n path.unshift(\n (at.getAttribute(ATTRIBUTE) ?? \"\")\n .split(\" \")\n .map(originOfKey)\n .filter((origin): origin is NodeOrigin => origin !== undefined),\n );\n at = at.parentElement?.closest(SELECTOR) ?? null;\n }\n return path.flat();\n}\n\n/** Os invólucros que desenham um nó — todas as cópias, quando ele se repete num `each`. */\nexport function elementsOf(root: ParentNode, origin: NodeOrigin): Element[] {\n return [\n ...root.querySelectorAll(\n `${INSPECT_TAG}[${ATTRIBUTE}~=\"${originKey(origin)}\"]`,\n ),\n ];\n}\n\n/**\n * A caixa de um nó na tela: a do que o invólucro contém, porque ele mesmo não tem caixa.\n * `undefined` quando o nó não ocupa espaço nenhum.\n */\nexport function boundsOf(element: Element): DOMRect | undefined {\n const range = element.ownerDocument.createRange();\n range.selectNodeContents(element);\n const box = range.getBoundingClientRect();\n return box.width === 0 && box.height === 0 ? undefined : box;\n}\n","import { createElement, type ReactNode } from \"react\";\nimport {\n intentParams,\n runIntent,\n type IntentRuntime,\n type ResolvedNode,\n} from \"@loom-forge/core\";\nimport { useResolver } from \"./context\";\nimport { inspectable } from \"./inspect\";\nimport type { Engine } from \"./types\";\n\n/** Renderiza uma `ResolvedNode` (saída do core) usando o registry de componentes + os\n * renderers de layout do engine. */\nexport function Render({ node }: { node: ResolvedNode }): ReactNode {\n const engine = useResolver();\n return renderResolved(node, engine);\n}\n\nexport function renderResolved(node: ResolvedNode, engine: Engine): ReactNode {\n if (node.kind === \"drop\") return null;\n if (node.kind === \"unknown\") return fallback(engine, node);\n\n // `el`: `type` é uma TAG intrínseca → createElement(tag, …), sem registry. Senão, resolve\n // o componente React registrado. childrenProp customizável só faz sentido pro registrado.\n const isEl = node.el === true;\n const entry = isEl ? undefined : engine.components.get(node.type);\n if (!isEl && !entry)\n return fallback(engine, { id: node.id, type: node.type });\n\n const finalProps: Record<string, unknown> = { ...node.props };\n\n if (node.children.length) {\n const byId = new Map<string, ReactNode>();\n for (const c of node.children) byId.set(c.id, renderResolved(c, engine));\n\n const renderer = node.layout\n ? engine.layoutRenderers[node.layout.name]\n : undefined;\n const arranged: ReactNode =\n renderer && node.layout\n ? renderer.render(node.layout.plan, byId)\n : node.children.map((c) => byId.get(c.id));\n\n finalProps[isEl ? \"children\" : (entry!.childrenProp ?? \"children\")] =\n arranged;\n }\n\n // events-out: cada intenção declarada vira um callback que EXECUTA a cadeia dela (ADR-018).\n //\n // O que o componente emite é a raiz `event` dos `params` — o autor mapeou o que entra\n // (`{ valor: \"{{ event }}\" }`), e os `params` são avaliados agora, no despacho, pelo binder,\n // sobre o escopo que o `bind` guardou. Sem `params`, o argumento é ignorado: é o caminho de\n // quem só declara gesto.\n //\n // Uma intenção MARCADA local é chamada interna de um componente vivo: vai para `updates`, e\n // nunca para a porta. O `then`/`catch` de um despacho ao host seguem daqui, com `data`/`error`.\n if (node.events) {\n const rt = intentRuntimeOf(engine);\n for (const [name, binding] of Object.entries(node.events))\n finalProps[name] = (emitted: unknown): void =>\n runIntent(binding, emitted, rt);\n }\n\n const element = createElement(isEl ? node.type : entry!.component, {\n ...finalProps,\n key: node.id,\n });\n // no modo de inspeção, o nó diz no DOM de onde veio — sem mudar layout nem o componente\n return engine.inspect && node.origins?.length\n ? inspectable(node.id, node.origins, element)\n : element;\n}\n\n/** Uma intenção local sem componente vivo por perto é árvore montada à mão: diz, e não despacha. */\nfunction warnOrphanLocal(action: string): void {\n const g = globalThis as { process?: { env?: { NODE_ENV?: string } } };\n if (g.process?.env?.NODE_ENV !== \"production\")\n console.warn(\n `[loom] local intent \"${action}\" has no live component to apply it — it does not go out through the port, because an update name is not an application id.`,\n );\n}\n\nfunction fallback(\n engine: Engine,\n node: { id: string; type: string },\n): ReactNode {\n if (engine.unknownComponent)\n return createElement(engine.unknownComponent, { node, key: node.id });\n engine.onUnknown?.(node);\n return null;\n}\n\n/**\n * O que a cadeia de uma intenção precisa de quem a executa, lido do engine: o binder para\n * avaliar os `params` no despacho, o boundary (quando há) para o update local e o pendente, a\n * porta e o `onActionResult` do host.\n */\nexport function intentRuntimeOf(engine: Engine): IntentRuntime {\n const updates = engine.updates;\n return {\n params: (binding, event) => intentParams(engine.binder, binding, event),\n ...(updates\n ? { apply: (name, params) => updates.apply(name, params) }\n : {}),\n orphan: warnOrphanLocal,\n ...(engine.actions ? { port: engine.actions } : {}),\n ...(engine.onActionResult\n ? { onResult: engine.onActionResult.bind(engine) }\n : {}),\n ...(updates?.pending\n ? { pending: (action, flag) => updates.pending!(action, flag) }\n : {}),\n };\n}\n","import type { ComponentType } from \"react\";\n\n/** Entrada do registry de componentes (camada de view). `childrenProp` = prop que recebe\n * os filhos arrumados (default `\"children\"`). */\nexport interface ComponentEntry {\n component: ComponentType<any>;\n childrenProp?: string;\n}\n\nexport interface ComponentRegistry {\n register(\n type: string,\n component: ComponentType<any>,\n opts?: { childrenProp?: string },\n ): ComponentRegistry;\n get(type: string): ComponentEntry | undefined;\n has(type: string): boolean;\n}\n\n/** Registry `type → componente React`. Separado do registry de descriptors (core) — a view\n * mapeia type→componente; o core só conhece o descriptor (headless). */\nexport function createComponentRegistry(): ComponentRegistry {\n const map = new Map<string, ComponentEntry>();\n const registry: ComponentRegistry = {\n register(type, component, opts) {\n map.set(type, { component, childrenProp: opts?.childrenProp });\n return registry;\n },\n get: (type) => map.get(type),\n has: (type) => map.has(type),\n };\n return registry;\n}\n","import type { CSSProperties, ReactNode } from \"react\";\nimport type { FragmentPlan, GridFormPlan, SectionPlan } from \"@loom-forge/core\";\n\n/* ──────────────────────────────────────────────────────────────────────────────\n LAYOUT — metade de VIEW (renderer): transforma o PLANO (do planner headless) em JSX.\n Pareado por `name` com o planner do core. Recebe os filhos já renderizados por id.\n ────────────────────────────────────────────────────────────────────────────── */\n\nexport interface LayoutRenderer {\n name: string;\n render(plan: unknown, children: Map<string, ReactNode>): ReactNode;\n}\n\nexport function fragmentRenderer(): LayoutRenderer {\n return {\n name: \"fragment\",\n render: (plan, children) =>\n (plan as FragmentPlan).order.map((id) => children.get(id)),\n };\n}\n\nexport function sectionRenderer(): LayoutRenderer {\n return {\n name: \"section\",\n render(plan, children) {\n const p = plan as SectionPlan;\n const style: CSSProperties = {\n display: \"flex\",\n flexDirection: p.direction,\n };\n if (p.gap != null) style.gap = p.gap;\n return <div style={style}>{p.order.map((id) => children.get(id))}</div>;\n },\n };\n}\n\nexport function gridFormRenderer(): LayoutRenderer {\n return {\n name: \"grid-form\",\n render(plan, children) {\n const p = plan as GridFormPlan;\n\n // Responsivo: slots variam por breakpoint → classes + CSS vars + container queries.\n // Requer importar `@loom-forge/grid-form.css`.\n if (p.responsive) {\n const wrap = {\n \"--gf-cols\": p.columns,\n \"--gf-gap\": typeof p.gap === \"number\" ? `${p.gap}px` : p.gap,\n } as CSSProperties;\n return (\n <div className=\"dui-gf\" style={wrap}>\n {p.cells.map((c) => {\n const cell = {\n \"--gf-span-base\": c.spans.base,\n \"--gf-span-sm\": c.spans.sm,\n \"--gf-span-md\": c.spans.md,\n \"--gf-span-lg\": c.spans.lg,\n \"--gf-span-xl\": c.spans.xl,\n } as CSSProperties;\n return (\n <div\n key={c.id}\n className={\n c.break ? \"dui-gf-cell dui-gf-break\" : \"dui-gf-cell\"\n }\n style={cell}\n >\n {children.get(c.id)}\n </div>\n );\n })}\n </div>\n );\n }\n\n // Fixo (span numérico): inline puro, zero-config (sem CSS).\n const wrap: CSSProperties = {\n display: \"grid\",\n gridTemplateColumns: `repeat(${p.columns}, minmax(0, 1fr))`,\n gap: p.gap,\n };\n return (\n <div style={wrap}>\n {p.cells.map((c) => {\n const cell: CSSProperties = {\n gridColumn: `span ${c.span} / span ${c.span}`,\n };\n if (c.break) cell.gridColumnStart = 1;\n return (\n <div key={c.id} style={cell}>\n {children.get(c.id)}\n </div>\n );\n })}\n </div>\n );\n },\n };\n}\n\n/** renderers default — pareados com `defaultPlanners()` do core. */\nexport function defaultLayoutRenderers(): Record<string, LayoutRenderer> {\n return {\n fragment: fragmentRenderer(),\n section: sectionRenderer(),\n \"grid-form\": gridFormRenderer(),\n };\n}\n","import { useMemo, type ComponentType, type ReactNode } from \"react\";\nimport {\n createDescriptorRegistry,\n createTypeResolver,\n defaultPlanners,\n identityBinder,\n resolveTree,\n type ActionPort,\n type ActionResult,\n type BindingEngine,\n type CapabilityPort,\n type DefNode,\n type DescriptorRegistry,\n type LayoutPlanner,\n type Resolver,\n} from \"@loom-forge/core\";\nimport { createComponentRegistry, type ComponentRegistry } from \"./registry\";\nimport { defaultLayoutRenderers, type LayoutRenderer } from \"./layout\";\nimport { useResolver } from \"./context\";\nimport { Render } from \"./render\";\nimport type { Engine } from \"./types\";\n\nexport interface CreateEngineOptions {\n binder?: BindingEngine;\n resolver?: Resolver;\n descriptors?: DescriptorRegistry;\n components?: ComponentRegistry;\n planners?: Record<string, LayoutPlanner>;\n layoutRenderers?: Record<string, LayoutRenderer>;\n defaultLayout?: string;\n actions?: ActionPort;\n /** onde o RESULTADO de um despacho pousa — ver `Engine.onActionResult`. */\n onActionResult?(id: string, result: ActionResult): void;\n /** o MODO DE INSPEÇÃO — ver `Engine.inspect`. */\n inspect?: boolean;\n capabilities?: CapabilityPort;\n unknownComponent?: ComponentType<{ node: { id: string; type: string } }>;\n onUnknown?(node: { id: string; type: string }): void;\n}\n\nconst warnUnknown = (node: { id: string; type: string }): void => {\n const g = globalThis as { process?: { env?: { NODE_ENV?: string } } };\n if (g.process?.env?.NODE_ENV !== \"production\")\n console.warn(`[loom] tipo não resolvido: \"${node.type}\" (id=\"${node.id}\")`);\n};\n\n/**\n * Monta o engine (core + view). `register(type, component, descriptor?)` popula os DOIS\n * registries (descriptor headless + componente React) de uma vez. Sobrescreva qualquer\n * plugin/port pelas opções.\n */\nexport function createEngine(opts: CreateEngineOptions = {}): Engine {\n const engine: Engine = {\n descriptors: opts.descriptors ?? createDescriptorRegistry(),\n components: opts.components ?? createComponentRegistry(),\n binder: opts.binder ?? identityBinder,\n resolver: opts.resolver ?? createTypeResolver(),\n planners: opts.planners ?? defaultPlanners(),\n layoutRenderers: opts.layoutRenderers ?? defaultLayoutRenderers(),\n defaultLayout: opts.defaultLayout ?? \"fragment\",\n actions: opts.actions,\n onActionResult: opts.onActionResult,\n inspect: opts.inspect,\n capabilities: opts.capabilities,\n unknownComponent: opts.unknownComponent,\n onUnknown: opts.onUnknown ?? warnUnknown,\n register(type, component, descriptor) {\n const { childrenProp, ...desc } = descriptor ?? {};\n engine.descriptors.register({ type, ...desc });\n engine.components.register(type, component, { childrenProp });\n return engine;\n },\n };\n return engine;\n}\n\n/** Entrada app-facing: resolve a árvore (core, memoizado) e renderiza (view). */\nexport function ResolverView({\n node,\n data,\n}: {\n node: DefNode;\n data?: unknown;\n}): ReactNode {\n const engine = useResolver();\n const resolved = useMemo(\n () =>\n resolveTree(\n node,\n {\n registry: engine.descriptors,\n binder: engine.binder,\n resolver: engine.resolver,\n planners: engine.planners,\n defaultLayout: engine.defaultLayout,\n },\n { data }, // a raiz do escopo: `{{ data.x }}`\n ),\n [engine, node, data],\n );\n return <Render node={resolved} />;\n}\n"]}
package/package.json ADDED
@@ -0,0 +1,65 @@
1
+ {
2
+ "name": "@loom-forge/react",
3
+ "version": "0.1.0",
4
+ "type": "module",
5
+ "description": "loom · a view React: consome a ResolvedNode do core e renderiza com o registry de componentes + os renderers de layout (fragment/section/grid-form). Events-out via ActionPort.",
6
+ "keywords": [
7
+ "react",
8
+ "json",
9
+ "dsl",
10
+ "renderer",
11
+ "layout"
12
+ ],
13
+ "license": "MIT",
14
+ "author": {
15
+ "name": "Anderson D. Rosa",
16
+ "url": "https://github.com/andersondrosa"
17
+ },
18
+ "repository": {
19
+ "type": "git",
20
+ "url": "https://github.com/andersondrosa/loom-forge.git",
21
+ "directory": "packages/react"
22
+ },
23
+ "homepage": "https://github.com/andersondrosa/loom-forge/tree/main/packages/react#readme",
24
+ "bugs": {
25
+ "url": "https://github.com/andersondrosa/loom-forge/issues"
26
+ },
27
+ "publishConfig": {
28
+ "access": "public"
29
+ },
30
+ "sideEffects": [
31
+ "**/*.css"
32
+ ],
33
+ "exports": {
34
+ ".": {
35
+ "types": "./dist/index.d.ts",
36
+ "import": "./dist/index.js"
37
+ },
38
+ "./grid-form.css": "./dist/grid-form.css"
39
+ },
40
+ "files": [
41
+ "dist"
42
+ ],
43
+ "dependencies": {
44
+ "@loom-forge/core": "0.1.0"
45
+ },
46
+ "peerDependencies": {
47
+ "react": "^18 || ^19"
48
+ },
49
+ "devDependencies": {
50
+ "@types/react": "^19.0.0",
51
+ "@types/react-dom": "^19.0.0",
52
+ "react": "^19.0.0",
53
+ "react-dom": "^19.0.0",
54
+ "tsup": "^8.0.0",
55
+ "typescript": "^5.9.3"
56
+ },
57
+ "scripts": {
58
+ "build": "tsup && cp src/grid-form.css dist/grid-form.css",
59
+ "dev": "tsup --watch",
60
+ "test": "vitest run",
61
+ "test:watch": "vitest",
62
+ "typecheck": "tsc --noEmit",
63
+ "clean": "rm -rf dist"
64
+ }
65
+ }