@hypen-space/web 0.5.2 → 0.5.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.
Files changed (104) hide show
  1. package/dist/canvas/accessibility.d.ts +118 -11
  2. package/dist/canvas/accessibility.js +470 -108
  3. package/dist/canvas/accessibility.js.map +1 -1
  4. package/dist/canvas/dispatch.d.ts +24 -0
  5. package/dist/canvas/dispatch.js +62 -0
  6. package/dist/canvas/dispatch.js.map +1 -0
  7. package/dist/canvas/editing.d.ts +178 -0
  8. package/dist/canvas/editing.js +590 -0
  9. package/dist/canvas/editing.js.map +1 -0
  10. package/dist/canvas/events.d.ts +28 -23
  11. package/dist/canvas/events.js +76 -128
  12. package/dist/canvas/events.js.map +1 -1
  13. package/dist/canvas/focus.d.ts +78 -0
  14. package/dist/canvas/focus.js +182 -0
  15. package/dist/canvas/focus.js.map +1 -0
  16. package/dist/canvas/index.d.ts +3 -1
  17. package/dist/canvas/index.js +3 -1
  18. package/dist/canvas/index.js.map +1 -1
  19. package/dist/canvas/paint.d.ts +16 -3
  20. package/dist/canvas/paint.js +283 -41
  21. package/dist/canvas/paint.js.map +1 -1
  22. package/dist/canvas/renderer.d.ts +20 -1
  23. package/dist/canvas/renderer.js +181 -50
  24. package/dist/canvas/renderer.js.map +1 -1
  25. package/dist/canvas/selection.js +31 -177
  26. package/dist/canvas/selection.js.map +1 -1
  27. package/dist/canvas/text-geometry.d.ts +72 -0
  28. package/dist/canvas/text-geometry.js +244 -0
  29. package/dist/canvas/text-geometry.js.map +1 -0
  30. package/dist/canvas/text.js +42 -9
  31. package/dist/canvas/text.js.map +1 -1
  32. package/dist/canvas/types.d.ts +7 -1
  33. package/dist/canvas/variants.d.ts +4 -1
  34. package/dist/canvas/variants.js +22 -3
  35. package/dist/canvas/variants.js.map +1 -1
  36. package/dist/dom/a11y-styles.d.ts +20 -0
  37. package/dist/dom/a11y-styles.js +61 -0
  38. package/dist/dom/a11y-styles.js.map +1 -0
  39. package/dist/dom/applicators/aria.d.ts +19 -0
  40. package/dist/dom/applicators/aria.js +36 -0
  41. package/dist/dom/applicators/aria.js.map +1 -0
  42. package/dist/dom/applicators/events.d.ts +7 -0
  43. package/dist/dom/applicators/events.js +35 -8
  44. package/dist/dom/applicators/events.js.map +1 -1
  45. package/dist/dom/applicators/index.js +4 -0
  46. package/dist/dom/applicators/index.js.map +1 -1
  47. package/dist/dom/components/hypenapp.d.ts +25 -1
  48. package/dist/dom/components/hypenapp.js +213 -245
  49. package/dist/dom/components/hypenapp.js.map +1 -1
  50. package/dist/dom/components/index.js +7 -0
  51. package/dist/dom/components/index.js.map +1 -1
  52. package/dist/dom/components/spinner.js +24 -10
  53. package/dist/dom/components/spinner.js.map +1 -1
  54. package/dist/dom/components/tabs.d.ts +27 -0
  55. package/dist/dom/components/tabs.js +69 -0
  56. package/dist/dom/components/tabs.js.map +1 -0
  57. package/dist/dom/components/visuallyhidden.d.ts +9 -0
  58. package/dist/dom/components/visuallyhidden.js +26 -0
  59. package/dist/dom/components/visuallyhidden.js.map +1 -0
  60. package/dist/dom/operability.d.ts +100 -0
  61. package/dist/dom/operability.js +298 -0
  62. package/dist/dom/operability.js.map +1 -0
  63. package/dist/dom/renderer.d.ts +98 -5
  64. package/dist/dom/renderer.js +398 -63
  65. package/dist/dom/renderer.js.map +1 -1
  66. package/dist/dom/route-focus.d.ts +42 -0
  67. package/dist/dom/route-focus.js +88 -0
  68. package/dist/dom/route-focus.js.map +1 -0
  69. package/dist/dom/semantics.d.ts +35 -0
  70. package/dist/dom/semantics.js +184 -0
  71. package/dist/dom/semantics.js.map +1 -0
  72. package/package.json +2 -2
  73. package/src/canvas/QUICKSTART.md +4 -4
  74. package/src/canvas/README.md +55 -29
  75. package/src/canvas/accessibility.ts +507 -115
  76. package/src/canvas/dispatch.ts +78 -0
  77. package/src/canvas/editing.ts +697 -0
  78. package/src/canvas/events.ts +89 -142
  79. package/src/canvas/focus.ts +216 -0
  80. package/src/canvas/index.ts +8 -1
  81. package/src/canvas/paint.ts +339 -42
  82. package/src/canvas/renderer.ts +218 -70
  83. package/src/canvas/selection.ts +52 -210
  84. package/src/canvas/text-geometry.ts +311 -0
  85. package/src/canvas/text.ts +43 -9
  86. package/src/canvas/types.ts +14 -1
  87. package/src/canvas/variants.ts +24 -5
  88. package/src/dom/a11y-styles.ts +65 -0
  89. package/src/dom/applicators/aria.ts +41 -0
  90. package/src/dom/applicators/events.ts +38 -7
  91. package/src/dom/applicators/index.ts +5 -0
  92. package/src/dom/components/hypenapp.ts +244 -272
  93. package/src/dom/components/index.ts +7 -0
  94. package/src/dom/components/spinner.ts +31 -13
  95. package/src/dom/components/tabs.ts +76 -0
  96. package/src/dom/components/visuallyhidden.ts +30 -0
  97. package/src/dom/operability.ts +312 -0
  98. package/src/dom/renderer.ts +455 -63
  99. package/src/dom/route-focus.ts +98 -0
  100. package/src/dom/semantics.ts +199 -0
  101. package/dist/canvas/input.d.ts +0 -76
  102. package/dist/canvas/input.js +0 -207
  103. package/dist/canvas/input.js.map +0 -1
  104. package/src/canvas/input.ts +0 -251
@@ -1,251 +0,0 @@
1
- /**
2
- * Input Overlay System
3
- *
4
- * Handle text input using DOM overlay elements
5
- */
6
-
7
- import type { VirtualNode, Rectangle } from "./types.js";
8
- import { getAbsoluteBounds, cssLengthToPx } from "./utils.js";
9
-
10
- /** Minimal engine interface for dispatching bind actions */
11
- interface BindEngine {
12
- dispatchAction(name: string, payload?: unknown): void;
13
- }
14
-
15
- /**
16
- * Input Overlay Manager
17
- */
18
- export class InputOverlay {
19
- private container: HTMLElement;
20
- private overlay: HTMLInputElement | HTMLTextAreaElement | null = null;
21
- private focusedNode: VirtualNode | null = null;
22
- private onChangeCallback: ((value: string) => void) | null = null;
23
- private bindPath: string | null = null;
24
- private engine: BindEngine | null = null;
25
-
26
- constructor(container: HTMLElement | null) {
27
- this.container = container || ({} as HTMLElement);
28
- }
29
-
30
- /**
31
- * Show input overlay for a node.
32
- *
33
- * Type comparison is case-insensitive — the engine emits `"Input"` /
34
- * `"Textarea"`, but the overlay branch was looking for lowercase
35
- * `"textarea"` and always created an `<input>` (so multi-line typing
36
- * couldn't enter newlines and the visible caret was the wrong height).
37
- */
38
- showInput(
39
- node: VirtualNode,
40
- canvasBounds: DOMRect,
41
- onChange: (value: string) => void,
42
- engine?: BindEngine | null
43
- ): void {
44
- // Skip if document is not available (non-browser environment)
45
- if (typeof document === "undefined") return;
46
-
47
- this.hideInput();
48
-
49
- // Store bind path and engine for two-way binding
50
- this.bindPath = (node.props.bind as string) || null;
51
- this.engine = engine || null;
52
-
53
- const bounds = getAbsoluteBounds(node);
54
- if (!bounds) return;
55
-
56
- const isMultiline = node.type.toLowerCase() === "textarea";
57
- this.overlay = isMultiline
58
- ? document.createElement("textarea")
59
- : document.createElement("input");
60
-
61
- // Style overlay
62
- this.styleOverlay(node, bounds, canvasBounds);
63
-
64
- // Set initial value
65
- const value = node.props.value || "";
66
- this.overlay.value = value;
67
-
68
- // Setup event handlers
69
- this.onChangeCallback = onChange;
70
- this.overlay.addEventListener("input", this.onInput.bind(this));
71
- this.overlay.addEventListener("blur", this.onBlur.bind(this));
72
- this.overlay.addEventListener("keydown", this.onKeyDown.bind(this) as EventListener);
73
-
74
- // Mount on document.body so absolute positioning is relative to the
75
- // viewport (`canvasBounds.left/top` are page coords too) — no
76
- // dependency on the canvas's wrapper having `position: relative`.
77
- document.body.appendChild(this.overlay);
78
- this.overlay.focus();
79
-
80
- this.focusedNode = node;
81
- }
82
-
83
- /**
84
- * Hide input overlay
85
- */
86
- hideInput(): void {
87
- if (this.overlay) {
88
- this.overlay.remove();
89
- this.overlay = null;
90
- }
91
- this.focusedNode = null;
92
- this.onChangeCallback = null;
93
- this.bindPath = null;
94
- this.engine = null;
95
- }
96
-
97
- /**
98
- * Update overlay position
99
- */
100
- updatePosition(node: VirtualNode, canvasBounds: DOMRect): void {
101
- if (!this.overlay || node !== this.focusedNode) return;
102
-
103
- const bounds = getAbsoluteBounds(node);
104
- if (!bounds) return;
105
-
106
- this.positionOverlay(bounds, canvasBounds);
107
- }
108
-
109
- /**
110
- * Style overlay to match canvas node
111
- */
112
- private styleOverlay(
113
- node: VirtualNode,
114
- bounds: Rectangle,
115
- canvasBounds: DOMRect
116
- ): void {
117
- if (!this.overlay) return;
118
-
119
- const props = node.props;
120
-
121
- // Position
122
- this.positionOverlay(bounds, canvasBounds);
123
-
124
- // Font and text styling — go through `cssLengthToPx` so values that
125
- // arrive in `rem` ("0.875rem" for `text-sm`) round to real pixels
126
- // instead of `parseFloat`'s bare 0.875 (which made the caret disappear).
127
- const fontSize = cssLengthToPx(props.fontSize) ?? 16;
128
- const fontWeight = props.fontWeight || "normal";
129
- const fontFamily = props.fontFamily || "system-ui, sans-serif";
130
- const color = props.color || "#000000";
131
-
132
- const padTop = cssLengthToPx(props.paddingTop ?? props.padding) ?? 0;
133
- const padRight = cssLengthToPx(props.paddingRight ?? props.padding) ?? 0;
134
- const padBottom = cssLengthToPx(props.paddingBottom ?? props.padding) ?? 0;
135
- const padLeft = cssLengthToPx(props.paddingLeft ?? props.padding) ?? 0;
136
- const borderRadius = cssLengthToPx(props.borderRadius) ?? 4;
137
-
138
- Object.assign(this.overlay.style, {
139
- fontSize: `${fontSize}px`,
140
- fontWeight: String(fontWeight),
141
- fontFamily: fontFamily,
142
- color: color,
143
- border: "none",
144
- outline: "none",
145
- backgroundColor: props.backgroundColor || "#ffffff",
146
- padding: `${padTop}px ${padRight}px ${padBottom}px ${padLeft}px`,
147
- borderRadius: `${borderRadius}px`,
148
- boxSizing: "border-box",
149
- resize: "none",
150
- // Keep the overlay above the canvas; the Bun-served stage has no
151
- // stacking context of its own so a plain z-index works.
152
- zIndex: "1000",
153
- });
154
-
155
- // Placeholder
156
- if (props.placeholder) {
157
- this.overlay.placeholder = props.placeholder;
158
- }
159
-
160
- // Input type
161
- if (this.overlay instanceof HTMLInputElement && props.type) {
162
- this.overlay.type = props.type;
163
- }
164
- }
165
-
166
- /**
167
- * Position overlay over canvas node.
168
- *
169
- * `bounds` is in canvas-local CSS pixels (the layout coordinate space
170
- * the renderer paints into after `ctx.scale(dpr, dpr)`). The overlay is
171
- * mounted on `document.body`, so we add `canvasBounds.left/top`
172
- * (page coords from `getBoundingClientRect`) to land at the right
173
- * absolute position regardless of where the canvas sits on the page.
174
- * Adding `window.scrollX/Y` keeps the overlay pinned to the canvas
175
- * even when the page scrolls between focus and reposition.
176
- */
177
- private positionOverlay(bounds: Rectangle, canvasBounds: DOMRect): void {
178
- if (!this.overlay) return;
179
-
180
- const sx = typeof window !== "undefined" ? window.scrollX : 0;
181
- const sy = typeof window !== "undefined" ? window.scrollY : 0;
182
-
183
- Object.assign(this.overlay.style, {
184
- position: "absolute",
185
- left: `${canvasBounds.left + sx + bounds.x}px`,
186
- top: `${canvasBounds.top + sy + bounds.y}px`,
187
- width: `${bounds.width}px`,
188
- height: `${bounds.height}px`,
189
- });
190
- }
191
-
192
- /**
193
- * Handle input event
194
- */
195
- private onInput(e: Event): void {
196
- if (!this.overlay || !this.onChangeCallback) return;
197
-
198
- const value = this.overlay.value;
199
- this.onChangeCallback(value);
200
-
201
- // Dispatch __hypen_bind for two-way binding
202
- if (this.bindPath && this.engine) {
203
- this.engine.dispatchAction("__hypen_bind", {
204
- path: this.bindPath,
205
- value,
206
- });
207
- }
208
- }
209
-
210
- /**
211
- * Handle blur event
212
- */
213
- private onBlur(): void {
214
- // Hide overlay when focus is lost
215
- this.hideInput();
216
- }
217
-
218
- /**
219
- * Handle keyboard events
220
- */
221
- private onKeyDown(e: KeyboardEvent): void {
222
- if (!this.overlay) return;
223
-
224
- // Enter key submits (unless multiline)
225
- if (e.key === "Enter" && this.overlay instanceof HTMLInputElement) {
226
- e.preventDefault();
227
- this.overlay.blur();
228
- }
229
-
230
- // Escape cancels
231
- if (e.key === "Escape") {
232
- e.preventDefault();
233
- this.overlay.blur();
234
- }
235
- }
236
-
237
- /**
238
- * Check if input is currently shown
239
- */
240
- isShown(): boolean {
241
- return this.overlay !== null;
242
- }
243
-
244
- /**
245
- * Get current focused node
246
- */
247
- getFocusedNode(): VirtualNode | null {
248
- return this.focusedNode;
249
- }
250
- }
251
-