@intent-framework/dom 0.1.0-alpha.1 → 0.1.0-alpha.3

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,65 @@
1
+ # @intent-framework/dom
2
+
3
+ DOM materializer for Intent screens and router.
4
+
5
+ ## Install
6
+
7
+ ```sh
8
+ pnpm add @intent-framework/core@0.1.0-alpha.1 @intent-framework/dom@0.1.0-alpha.1
9
+ ```
10
+
11
+ ```sh
12
+ npm install @intent-framework/core@0.1.0-alpha.1 @intent-framework/dom@0.1.0-alpha.1
13
+ ```
14
+
15
+ ## What it provides
16
+
17
+ - `renderDom()` — materialize a screen into semantic HTML
18
+ - `renderRouter()` — materialize a router into navigable DOM pages
19
+ - Real HTML labels, inputs, buttons, and `aria-live` output
20
+ - Reactive action enablement and blocked reasons
21
+ - Enter key triggers the default action when unambiguous
22
+ - Opt-in screen-name heading via `showScreenName`
23
+ - Opt-in semantic data attributes via `showSemanticIds`
24
+
25
+ ## Minimal example
26
+
27
+ ```ts
28
+ import { screen } from "@intent-framework/core"
29
+ import { renderDom } from "@intent-framework/dom"
30
+
31
+ const InviteMember = screen("InviteMember", $ => {
32
+ const email = $.state.text("email")
33
+
34
+ const emailAsk = $.ask("Email", email)
35
+ .required()
36
+ .validate(value => value.includes("@") ? true : "Enter a valid email")
37
+
38
+ const invite = $.act("Invite member")
39
+ .primary()
40
+ .when(emailAsk.valid, "Enter a valid email first")
41
+
42
+ $.surface("main").contains(emailAsk, invite)
43
+ })
44
+
45
+ const cleanup = renderDom(InviteMember, {
46
+ target: document.getElementById("root")!,
47
+ })
48
+ ```
49
+
50
+ The renderer produces real DOM — labels, inputs, buttons, and an `aria-live` output. No JSX required.
51
+
52
+ ## Where this fits
53
+
54
+ DOM is a renderer for Intent screens. It depends on `@intent-framework/core` and can optionally integrate with `@intent-framework/router` via `renderRouter()`. It is not the source of truth — the screen definition is.
55
+
56
+ ## Learn more
57
+
58
+ - [Root README](../../README.md) — project overview and philosophy
59
+ - [Quickstart](../../docs/Quickstart.md) — step-by-step guide with DOM rendering
60
+ - [Semantic DOM Debugging](../../docs/Semantic-DOM-Debugging.md) — how `showSemanticIds` maps `inspectScreen()` IDs to DOM data attributes
61
+ - [Canonical runnable example](../../examples/canonical-invite) — matches the Quickstart one-to-one
62
+
63
+ ## Status
64
+
65
+ Experimental alpha. Version `0.1.0-alpha.1`. APIs may change. Not recommended for production use.
@@ -13,6 +13,7 @@ export type RenderRouterOptions<TServices extends object = DefaultScreenServices
13
13
  notFound?: ScreenDefinition<TServices> | ((pathname: string) => ScreenDefinition<TServices>);
14
14
  services?: Omit<TServices, "navigate" | "route">;
15
15
  showScreenName?: boolean;
16
+ showSemanticIds?: boolean;
16
17
  };
17
18
  export declare function renderRouter<Routes extends Record<string, {
18
19
  path: string;
package/dist/index.d.ts CHANGED
@@ -3,6 +3,7 @@ export type DomRendererOptions<TServices extends object = DefaultScreenServices>
3
3
  target: HTMLElement;
4
4
  services?: TServices;
5
5
  showScreenName?: boolean;
6
+ showSemanticIds?: boolean;
6
7
  };
7
8
  export { renderRouter } from "./dom-router.js";
8
9
  export type { RouterDomHandle, RenderRouterOptions } from "./dom-router.js";
package/dist/index.js CHANGED
@@ -1,8 +1,8 @@
1
- import { createScreenRuntime } from "@intent-framework/core";
1
+ import { createScreenRuntime, inspectScreen } from "@intent-framework/core";
2
2
 
3
3
  //#region src/dom-router.ts
4
4
  function renderRouter(router, options) {
5
- const { showScreenName } = options;
5
+ const { showScreenName, showSemanticIds } = options;
6
6
  const win = options.window ?? window;
7
7
  let currentCleanup;
8
8
  const navigate = (name, ...args) => {
@@ -30,7 +30,8 @@ function renderRouter(router, options) {
30
30
  currentCleanup = renderDom(match.screen, {
31
31
  target: options.target,
32
32
  services: mergedServices,
33
- showScreenName
33
+ showScreenName,
34
+ showSemanticIds
34
35
  });
35
36
  return;
36
37
  }
@@ -39,7 +40,8 @@ function renderRouter(router, options) {
39
40
  currentCleanup = renderDom(screen, {
40
41
  target: options.target,
41
42
  services: mergedServices,
42
- showScreenName
43
+ showScreenName,
44
+ showSemanticIds
43
45
  });
44
46
  } else options.target.textContent = "Not found";
45
47
  }
@@ -77,9 +79,9 @@ function findDefaultAction(acts) {
77
79
  return void 0;
78
80
  }
79
81
  function renderDom(screenDef, options) {
80
- const { target, services, showScreenName } = options;
82
+ const { target, services, showScreenName, showSemanticIds } = options;
81
83
  target.innerHTML = "";
82
- const root = buildDom(screenDef, showScreenName);
84
+ const root = buildDom(screenDef, showScreenName, showSemanticIds);
83
85
  target.appendChild(root);
84
86
  const runtime = createScreenRuntime(screenDef, { services });
85
87
  runtime.start();
@@ -169,10 +171,19 @@ function renderDom(screenDef, options) {
169
171
  runtime.dispose();
170
172
  };
171
173
  }
172
- function buildDom(screenDef, showScreenName) {
174
+ function buildDom(screenDef, showScreenName, showSemanticIds) {
175
+ let inspected;
176
+ let askSemanticIds;
177
+ let actSemanticIds;
178
+ if (showSemanticIds) {
179
+ inspected = inspectScreen(screenDef);
180
+ askSemanticIds = new Map(inspected.asks.map((a) => [a.id, a.semanticId]));
181
+ actSemanticIds = new Map(inspected.acts.map((a) => [a.id, a.semanticId]));
182
+ }
173
183
  const surface = screenDef.surfaces[0];
174
184
  const main = document.createElement("main");
175
185
  if (surface) main.id = surface.id;
186
+ if (showSemanticIds && inspected) main.setAttribute("data-intent-screen", inspected.semanticId);
176
187
  if (showScreenName) {
177
188
  const heading = document.createElement("h1");
178
189
  heading.textContent = screenDef.name;
@@ -187,10 +198,18 @@ function buildDom(screenDef, showScreenName) {
187
198
  const label = document.createElement("label");
188
199
  label.textContent = ask.label;
189
200
  label.htmlFor = ask.id;
201
+ if (showSemanticIds && askSemanticIds) {
202
+ const sid = askSemanticIds.get(ask.id);
203
+ if (sid) label.setAttribute("data-intent-ask", sid);
204
+ }
190
205
  container.appendChild(label);
191
206
  const input = createInputForAsk(ask);
192
207
  input.id = ask.id;
193
208
  input.name = ask.id;
209
+ if (showSemanticIds && askSemanticIds) {
210
+ const sid = askSemanticIds.get(ask.id);
211
+ if (sid) input.setAttribute("data-intent-ask", sid);
212
+ }
194
213
  if (ask.required) input.required = true;
195
214
  if (ask.kind === "contact" && ask.contactKind) input.setAttribute("autocomplete", ask.contactKind);
196
215
  input.addEventListener("input", () => {
@@ -225,6 +244,10 @@ function buildDom(screenDef, showScreenName) {
225
244
  button.id = act.id;
226
245
  button.type = "button";
227
246
  button.textContent = act.label;
247
+ if (showSemanticIds && actSemanticIds) {
248
+ const sid = actSemanticIds.get(act.id);
249
+ if (sid) button.setAttribute("data-intent-action", sid);
250
+ }
228
251
  if (act.primary) button.className = "primary";
229
252
  if (!act.enabled.current) {
230
253
  button.disabled = true;
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "publishConfig": {
4
4
  "access": "public"
5
5
  },
6
- "version": "0.1.0-alpha.1",
6
+ "version": "0.1.0-alpha.3",
7
7
  "description": "DOM materializer for Intent screens and router",
8
8
  "license": "MIT",
9
9
  "repository": {
@@ -26,8 +26,8 @@
26
26
  "dist"
27
27
  ],
28
28
  "dependencies": {
29
- "@intent-framework/core": "0.1.0-alpha.1",
30
- "@intent-framework/router": "0.1.0-alpha.1"
29
+ "@intent-framework/core": "0.1.0-alpha.2",
30
+ "@intent-framework/router": "0.1.0-alpha.2"
31
31
  },
32
32
  "devDependencies": {
33
33
  "jsdom": "^29.1.1",