@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.
- package/dist/canvas/accessibility.d.ts +118 -11
- package/dist/canvas/accessibility.js +470 -108
- package/dist/canvas/accessibility.js.map +1 -1
- package/dist/canvas/dispatch.d.ts +24 -0
- package/dist/canvas/dispatch.js +62 -0
- package/dist/canvas/dispatch.js.map +1 -0
- package/dist/canvas/editing.d.ts +178 -0
- package/dist/canvas/editing.js +590 -0
- package/dist/canvas/editing.js.map +1 -0
- package/dist/canvas/events.d.ts +28 -23
- package/dist/canvas/events.js +76 -128
- package/dist/canvas/events.js.map +1 -1
- package/dist/canvas/focus.d.ts +78 -0
- package/dist/canvas/focus.js +182 -0
- package/dist/canvas/focus.js.map +1 -0
- package/dist/canvas/index.d.ts +3 -1
- package/dist/canvas/index.js +3 -1
- package/dist/canvas/index.js.map +1 -1
- package/dist/canvas/paint.d.ts +16 -3
- package/dist/canvas/paint.js +283 -41
- package/dist/canvas/paint.js.map +1 -1
- package/dist/canvas/renderer.d.ts +20 -1
- package/dist/canvas/renderer.js +181 -50
- package/dist/canvas/renderer.js.map +1 -1
- package/dist/canvas/selection.js +31 -177
- package/dist/canvas/selection.js.map +1 -1
- package/dist/canvas/text-geometry.d.ts +72 -0
- package/dist/canvas/text-geometry.js +244 -0
- package/dist/canvas/text-geometry.js.map +1 -0
- package/dist/canvas/text.js +42 -9
- package/dist/canvas/text.js.map +1 -1
- package/dist/canvas/types.d.ts +7 -1
- package/dist/canvas/variants.d.ts +4 -1
- package/dist/canvas/variants.js +22 -3
- package/dist/canvas/variants.js.map +1 -1
- package/dist/dom/a11y-styles.d.ts +20 -0
- package/dist/dom/a11y-styles.js +61 -0
- package/dist/dom/a11y-styles.js.map +1 -0
- package/dist/dom/applicators/aria.d.ts +19 -0
- package/dist/dom/applicators/aria.js +36 -0
- package/dist/dom/applicators/aria.js.map +1 -0
- package/dist/dom/applicators/events.d.ts +7 -0
- package/dist/dom/applicators/events.js +35 -8
- package/dist/dom/applicators/events.js.map +1 -1
- package/dist/dom/applicators/index.js +4 -0
- package/dist/dom/applicators/index.js.map +1 -1
- package/dist/dom/components/hypenapp.d.ts +25 -1
- package/dist/dom/components/hypenapp.js +213 -245
- package/dist/dom/components/hypenapp.js.map +1 -1
- package/dist/dom/components/index.js +7 -0
- package/dist/dom/components/index.js.map +1 -1
- package/dist/dom/components/spinner.js +24 -10
- package/dist/dom/components/spinner.js.map +1 -1
- package/dist/dom/components/tabs.d.ts +27 -0
- package/dist/dom/components/tabs.js +69 -0
- package/dist/dom/components/tabs.js.map +1 -0
- package/dist/dom/components/visuallyhidden.d.ts +9 -0
- package/dist/dom/components/visuallyhidden.js +26 -0
- package/dist/dom/components/visuallyhidden.js.map +1 -0
- package/dist/dom/operability.d.ts +100 -0
- package/dist/dom/operability.js +298 -0
- package/dist/dom/operability.js.map +1 -0
- package/dist/dom/renderer.d.ts +98 -5
- package/dist/dom/renderer.js +398 -63
- package/dist/dom/renderer.js.map +1 -1
- package/dist/dom/route-focus.d.ts +42 -0
- package/dist/dom/route-focus.js +88 -0
- package/dist/dom/route-focus.js.map +1 -0
- package/dist/dom/semantics.d.ts +35 -0
- package/dist/dom/semantics.js +184 -0
- package/dist/dom/semantics.js.map +1 -0
- package/package.json +2 -2
- package/src/canvas/QUICKSTART.md +4 -4
- package/src/canvas/README.md +55 -29
- package/src/canvas/accessibility.ts +507 -115
- package/src/canvas/dispatch.ts +78 -0
- package/src/canvas/editing.ts +697 -0
- package/src/canvas/events.ts +89 -142
- package/src/canvas/focus.ts +216 -0
- package/src/canvas/index.ts +8 -1
- package/src/canvas/paint.ts +339 -42
- package/src/canvas/renderer.ts +218 -70
- package/src/canvas/selection.ts +52 -210
- package/src/canvas/text-geometry.ts +311 -0
- package/src/canvas/text.ts +43 -9
- package/src/canvas/types.ts +14 -1
- package/src/canvas/variants.ts +24 -5
- package/src/dom/a11y-styles.ts +65 -0
- package/src/dom/applicators/aria.ts +41 -0
- package/src/dom/applicators/events.ts +38 -7
- package/src/dom/applicators/index.ts +5 -0
- package/src/dom/components/hypenapp.ts +244 -272
- package/src/dom/components/index.ts +7 -0
- package/src/dom/components/spinner.ts +31 -13
- package/src/dom/components/tabs.ts +76 -0
- package/src/dom/components/visuallyhidden.ts +30 -0
- package/src/dom/operability.ts +312 -0
- package/src/dom/renderer.ts +455 -63
- package/src/dom/route-focus.ts +98 -0
- package/src/dom/semantics.ts +199 -0
- package/dist/canvas/input.d.ts +0 -76
- package/dist/canvas/input.js +0 -207
- package/dist/canvas/input.js.map +0 -1
- package/src/canvas/input.ts +0 -251
package/src/canvas/input.ts
DELETED
|
@@ -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
|
-
|