@hypen-space/web 0.4.980 → 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/README.md +20 -13
- 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 +90 -103
- 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 -2
- package/dist/canvas/index.js +3 -2
- package/dist/canvas/index.js.map +1 -1
- package/dist/canvas/layout.js +68 -98
- package/dist/canvas/layout.js.map +1 -1
- package/dist/canvas/paint.d.ts +16 -3
- package/dist/canvas/paint.js +449 -110
- package/dist/canvas/paint.js.map +1 -1
- package/dist/canvas/renderer.d.ts +28 -4
- package/dist/canvas/renderer.js +201 -81
- 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 +44 -18
- package/dist/canvas/text.js.map +1 -1
- package/dist/canvas/types.d.ts +10 -1
- package/dist/canvas/utils.d.ts +0 -23
- package/dist/canvas/utils.js +3 -67
- package/dist/canvas/utils.js.map +1 -1
- package/dist/canvas/variants.d.ts +52 -0
- package/dist/canvas/variants.js +156 -0
- package/dist/canvas/variants.js.map +1 -0
- 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 +36 -8
- package/dist/dom/applicators/events.js.map +1 -1
- package/dist/dom/applicators/font.d.ts +11 -6
- package/dist/dom/applicators/font.js +16 -27
- package/dist/dom/applicators/font.js.map +1 -1
- package/dist/dom/applicators/index.js +51 -45
- package/dist/dom/applicators/index.js.map +1 -1
- package/dist/dom/canvas/index.js +2 -2
- package/dist/dom/canvas/index.js.map +1 -1
- package/dist/dom/components/app.js +2 -2
- package/dist/dom/components/app.js.map +1 -1
- package/dist/dom/components/audio.js +2 -2
- package/dist/dom/components/audio.js.map +1 -1
- package/dist/dom/components/avatar.js +3 -4
- package/dist/dom/components/avatar.js.map +1 -1
- package/dist/dom/components/badge.js +3 -4
- package/dist/dom/components/badge.js.map +1 -1
- package/dist/dom/components/button.js +2 -2
- package/dist/dom/components/button.js.map +1 -1
- package/dist/dom/components/card.js +2 -2
- package/dist/dom/components/card.js.map +1 -1
- package/dist/dom/components/center.js +2 -2
- package/dist/dom/components/center.js.map +1 -1
- package/dist/dom/components/checkbox.js +4 -5
- package/dist/dom/components/checkbox.js.map +1 -1
- package/dist/dom/components/column.js +2 -2
- package/dist/dom/components/column.js.map +1 -1
- package/dist/dom/components/container.js +2 -2
- package/dist/dom/components/container.js.map +1 -1
- package/dist/dom/components/divider.js +2 -2
- package/dist/dom/components/divider.js.map +1 -1
- package/dist/dom/components/grid.js +10 -10
- package/dist/dom/components/grid.js.map +1 -1
- package/dist/dom/components/heading.js +4 -6
- package/dist/dom/components/heading.js.map +1 -1
- package/dist/dom/components/hypenapp.d.ts +25 -1
- package/dist/dom/components/hypenapp.js +215 -247
- package/dist/dom/components/hypenapp.js.map +1 -1
- package/dist/dom/components/icon.js +4 -5
- package/dist/dom/components/icon.js.map +1 -1
- package/dist/dom/components/image.js +2 -2
- package/dist/dom/components/image.js.map +1 -1
- package/dist/dom/components/index.d.ts +2 -2
- package/dist/dom/components/index.js +9 -2
- package/dist/dom/components/index.js.map +1 -1
- package/dist/dom/components/input.js +2 -2
- package/dist/dom/components/input.js.map +1 -1
- package/dist/dom/components/link.js +2 -2
- package/dist/dom/components/link.js.map +1 -1
- package/dist/dom/components/list.js +2 -2
- package/dist/dom/components/list.js.map +1 -1
- package/dist/dom/components/paragraph.js +3 -4
- package/dist/dom/components/paragraph.js.map +1 -1
- package/dist/dom/components/progressbar.js +3 -3
- package/dist/dom/components/progressbar.js.map +1 -1
- package/dist/dom/components/route.js +2 -2
- package/dist/dom/components/route.js.map +1 -1
- package/dist/dom/components/router.js +2 -2
- package/dist/dom/components/router.js.map +1 -1
- package/dist/dom/components/row.js +10 -10
- package/dist/dom/components/row.js.map +1 -1
- package/dist/dom/components/select.js +3 -4
- package/dist/dom/components/select.js.map +1 -1
- package/dist/dom/components/slider.js +2 -2
- package/dist/dom/components/slider.js.map +1 -1
- package/dist/dom/components/spacer.js +2 -2
- package/dist/dom/components/spacer.js.map +1 -1
- package/dist/dom/components/spinner.js +27 -13
- package/dist/dom/components/spinner.js.map +1 -1
- package/dist/dom/components/stack.js +10 -10
- package/dist/dom/components/stack.js.map +1 -1
- package/dist/dom/components/switch.js +5 -6
- package/dist/dom/components/switch.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/text.js +4 -7
- package/dist/dom/components/text.js.map +1 -1
- package/dist/dom/components/textarea.js +2 -2
- package/dist/dom/components/textarea.js.map +1 -1
- package/dist/dom/components/video.js +2 -2
- package/dist/dom/components/video.js.map +1 -1
- 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/debug.js +6 -8
- package/dist/dom/debug.js.map +1 -1
- package/dist/dom/index.d.ts +0 -1
- package/dist/dom/index.js +0 -1
- package/dist/dom/index.js.map +1 -1
- 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 +400 -66
- 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/dist/variants.d.ts +90 -0
- package/dist/variants.js +206 -0
- package/dist/variants.js.map +1 -0
- package/package.json +2 -2
- package/src/canvas/QUICKSTART.md +16 -11
- package/src/canvas/README.md +63 -34
- 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 +104 -115
- package/src/canvas/focus.ts +216 -0
- package/src/canvas/index.ts +8 -9
- package/src/canvas/layout.ts +68 -103
- package/src/canvas/paint.ts +509 -109
- package/src/canvas/renderer.ts +239 -99
- package/src/canvas/selection.ts +52 -210
- package/src/canvas/text-geometry.ts +311 -0
- package/src/canvas/text.ts +45 -18
- package/src/canvas/types.ts +28 -1
- package/src/canvas/utils.ts +3 -69
- package/src/canvas/variants.ts +172 -0
- package/src/dom/README.md +8 -7
- package/src/dom/a11y-styles.ts +65 -0
- package/src/dom/applicators/aria.ts +41 -0
- package/src/dom/applicators/events.ts +39 -7
- package/src/dom/applicators/font.ts +16 -29
- package/src/dom/applicators/index.ts +61 -46
- package/src/dom/canvas/index.ts +2 -2
- package/src/dom/components/app.ts +2 -2
- package/src/dom/components/audio.ts +2 -2
- package/src/dom/components/avatar.ts +3 -4
- package/src/dom/components/badge.ts +3 -4
- package/src/dom/components/button.ts +2 -2
- package/src/dom/components/card.ts +2 -2
- package/src/dom/components/center.ts +2 -2
- package/src/dom/components/checkbox.ts +4 -5
- package/src/dom/components/column.ts +2 -2
- package/src/dom/components/container.ts +2 -2
- package/src/dom/components/divider.ts +2 -2
- package/src/dom/components/grid.ts +10 -10
- package/src/dom/components/heading.ts +4 -6
- package/src/dom/components/hypenapp.ts +246 -274
- package/src/dom/components/icon.ts +4 -5
- package/src/dom/components/image.ts +2 -2
- package/src/dom/components/index.ts +10 -3
- package/src/dom/components/input.ts +2 -2
- package/src/dom/components/link.ts +2 -2
- package/src/dom/components/list.ts +2 -2
- package/src/dom/components/paragraph.ts +3 -4
- package/src/dom/components/progressbar.ts +3 -3
- package/src/dom/components/route.ts +2 -2
- package/src/dom/components/router.ts +2 -2
- package/src/dom/components/row.ts +10 -10
- package/src/dom/components/select.ts +3 -4
- package/src/dom/components/slider.ts +2 -2
- package/src/dom/components/spacer.ts +2 -2
- package/src/dom/components/spinner.ts +34 -16
- package/src/dom/components/stack.ts +10 -10
- package/src/dom/components/switch.ts +5 -6
- package/src/dom/components/tabs.ts +76 -0
- package/src/dom/components/text.ts +4 -7
- package/src/dom/components/textarea.ts +2 -2
- package/src/dom/components/video.ts +2 -2
- package/src/dom/components/visuallyhidden.ts +30 -0
- package/src/dom/debug.ts +6 -8
- package/src/dom/index.ts +0 -8
- package/src/dom/operability.ts +312 -0
- package/src/dom/renderer.ts +457 -66
- package/src/dom/route-focus.ts +98 -0
- package/src/dom/semantics.ts +199 -0
- package/src/variants.ts +249 -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/dist/client.d.ts +0 -36
- package/dist/client.js +0 -47
- package/dist/client.js.map +0 -1
- package/src/canvas/input.ts +0 -251
- package/src/client.ts +0 -88
package/dist/canvas/input.js
DELETED
|
@@ -1,207 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Input Overlay System
|
|
3
|
-
*
|
|
4
|
-
* Handle text input using DOM overlay elements
|
|
5
|
-
*/
|
|
6
|
-
import { getAbsoluteBounds, cssLengthToPx } from "./utils.js";
|
|
7
|
-
/**
|
|
8
|
-
* Input Overlay Manager
|
|
9
|
-
*/
|
|
10
|
-
export class InputOverlay {
|
|
11
|
-
container;
|
|
12
|
-
overlay = null;
|
|
13
|
-
focusedNode = null;
|
|
14
|
-
onChangeCallback = null;
|
|
15
|
-
bindPath = null;
|
|
16
|
-
engine = null;
|
|
17
|
-
constructor(container) {
|
|
18
|
-
this.container = container || {};
|
|
19
|
-
}
|
|
20
|
-
/**
|
|
21
|
-
* Show input overlay for a node.
|
|
22
|
-
*
|
|
23
|
-
* Type comparison is case-insensitive — the engine emits `"Input"` /
|
|
24
|
-
* `"Textarea"`, but the overlay branch was looking for lowercase
|
|
25
|
-
* `"textarea"` and always created an `<input>` (so multi-line typing
|
|
26
|
-
* couldn't enter newlines and the visible caret was the wrong height).
|
|
27
|
-
*/
|
|
28
|
-
showInput(node, canvasBounds, onChange, engine) {
|
|
29
|
-
// Skip if document is not available (non-browser environment)
|
|
30
|
-
if (typeof document === "undefined")
|
|
31
|
-
return;
|
|
32
|
-
this.hideInput();
|
|
33
|
-
// Store bind path and engine for two-way binding
|
|
34
|
-
this.bindPath = node.props.bind || null;
|
|
35
|
-
this.engine = engine || null;
|
|
36
|
-
const bounds = getAbsoluteBounds(node);
|
|
37
|
-
if (!bounds)
|
|
38
|
-
return;
|
|
39
|
-
const isMultiline = node.type.toLowerCase() === "textarea";
|
|
40
|
-
this.overlay = isMultiline
|
|
41
|
-
? document.createElement("textarea")
|
|
42
|
-
: document.createElement("input");
|
|
43
|
-
// Style overlay
|
|
44
|
-
this.styleOverlay(node, bounds, canvasBounds);
|
|
45
|
-
// Set initial value
|
|
46
|
-
const value = node.props.value || "";
|
|
47
|
-
this.overlay.value = value;
|
|
48
|
-
// Setup event handlers
|
|
49
|
-
this.onChangeCallback = onChange;
|
|
50
|
-
this.overlay.addEventListener("input", this.onInput.bind(this));
|
|
51
|
-
this.overlay.addEventListener("blur", this.onBlur.bind(this));
|
|
52
|
-
this.overlay.addEventListener("keydown", this.onKeyDown.bind(this));
|
|
53
|
-
// Mount on document.body so absolute positioning is relative to the
|
|
54
|
-
// viewport (`canvasBounds.left/top` are page coords too) — no
|
|
55
|
-
// dependency on the canvas's wrapper having `position: relative`.
|
|
56
|
-
document.body.appendChild(this.overlay);
|
|
57
|
-
this.overlay.focus();
|
|
58
|
-
this.focusedNode = node;
|
|
59
|
-
}
|
|
60
|
-
/**
|
|
61
|
-
* Hide input overlay
|
|
62
|
-
*/
|
|
63
|
-
hideInput() {
|
|
64
|
-
if (this.overlay) {
|
|
65
|
-
this.overlay.remove();
|
|
66
|
-
this.overlay = null;
|
|
67
|
-
}
|
|
68
|
-
this.focusedNode = null;
|
|
69
|
-
this.onChangeCallback = null;
|
|
70
|
-
this.bindPath = null;
|
|
71
|
-
this.engine = null;
|
|
72
|
-
}
|
|
73
|
-
/**
|
|
74
|
-
* Update overlay position
|
|
75
|
-
*/
|
|
76
|
-
updatePosition(node, canvasBounds) {
|
|
77
|
-
if (!this.overlay || node !== this.focusedNode)
|
|
78
|
-
return;
|
|
79
|
-
const bounds = getAbsoluteBounds(node);
|
|
80
|
-
if (!bounds)
|
|
81
|
-
return;
|
|
82
|
-
this.positionOverlay(bounds, canvasBounds);
|
|
83
|
-
}
|
|
84
|
-
/**
|
|
85
|
-
* Style overlay to match canvas node
|
|
86
|
-
*/
|
|
87
|
-
styleOverlay(node, bounds, canvasBounds) {
|
|
88
|
-
if (!this.overlay)
|
|
89
|
-
return;
|
|
90
|
-
const props = node.props;
|
|
91
|
-
// Position
|
|
92
|
-
this.positionOverlay(bounds, canvasBounds);
|
|
93
|
-
// Font and text styling — go through `cssLengthToPx` so values that
|
|
94
|
-
// arrive in `rem` ("0.875rem" for `text-sm`) round to real pixels
|
|
95
|
-
// instead of `parseFloat`'s bare 0.875 (which made the caret disappear).
|
|
96
|
-
const fontSize = cssLengthToPx(props.fontSize) ?? 16;
|
|
97
|
-
const fontWeight = props.fontWeight || "normal";
|
|
98
|
-
const fontFamily = props.fontFamily || "system-ui, sans-serif";
|
|
99
|
-
const color = props.color || "#000000";
|
|
100
|
-
const padTop = cssLengthToPx(props.paddingTop ?? props.padding) ?? 0;
|
|
101
|
-
const padRight = cssLengthToPx(props.paddingRight ?? props.padding) ?? 0;
|
|
102
|
-
const padBottom = cssLengthToPx(props.paddingBottom ?? props.padding) ?? 0;
|
|
103
|
-
const padLeft = cssLengthToPx(props.paddingLeft ?? props.padding) ?? 0;
|
|
104
|
-
const borderRadius = cssLengthToPx(props.borderRadius) ?? 4;
|
|
105
|
-
Object.assign(this.overlay.style, {
|
|
106
|
-
fontSize: `${fontSize}px`,
|
|
107
|
-
fontWeight: String(fontWeight),
|
|
108
|
-
fontFamily: fontFamily,
|
|
109
|
-
color: color,
|
|
110
|
-
border: "none",
|
|
111
|
-
outline: "none",
|
|
112
|
-
backgroundColor: props.backgroundColor || "#ffffff",
|
|
113
|
-
padding: `${padTop}px ${padRight}px ${padBottom}px ${padLeft}px`,
|
|
114
|
-
borderRadius: `${borderRadius}px`,
|
|
115
|
-
boxSizing: "border-box",
|
|
116
|
-
resize: "none",
|
|
117
|
-
// Keep the overlay above the canvas; the Bun-served stage has no
|
|
118
|
-
// stacking context of its own so a plain z-index works.
|
|
119
|
-
zIndex: "1000",
|
|
120
|
-
});
|
|
121
|
-
// Placeholder
|
|
122
|
-
if (props.placeholder) {
|
|
123
|
-
this.overlay.placeholder = props.placeholder;
|
|
124
|
-
}
|
|
125
|
-
// Input type
|
|
126
|
-
if (this.overlay instanceof HTMLInputElement && props.type) {
|
|
127
|
-
this.overlay.type = props.type;
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
/**
|
|
131
|
-
* Position overlay over canvas node.
|
|
132
|
-
*
|
|
133
|
-
* `bounds` is in canvas-local CSS pixels (the layout coordinate space
|
|
134
|
-
* the renderer paints into after `ctx.scale(dpr, dpr)`). The overlay is
|
|
135
|
-
* mounted on `document.body`, so we add `canvasBounds.left/top`
|
|
136
|
-
* (page coords from `getBoundingClientRect`) to land at the right
|
|
137
|
-
* absolute position regardless of where the canvas sits on the page.
|
|
138
|
-
* Adding `window.scrollX/Y` keeps the overlay pinned to the canvas
|
|
139
|
-
* even when the page scrolls between focus and reposition.
|
|
140
|
-
*/
|
|
141
|
-
positionOverlay(bounds, canvasBounds) {
|
|
142
|
-
if (!this.overlay)
|
|
143
|
-
return;
|
|
144
|
-
const sx = typeof window !== "undefined" ? window.scrollX : 0;
|
|
145
|
-
const sy = typeof window !== "undefined" ? window.scrollY : 0;
|
|
146
|
-
Object.assign(this.overlay.style, {
|
|
147
|
-
position: "absolute",
|
|
148
|
-
left: `${canvasBounds.left + sx + bounds.x}px`,
|
|
149
|
-
top: `${canvasBounds.top + sy + bounds.y}px`,
|
|
150
|
-
width: `${bounds.width}px`,
|
|
151
|
-
height: `${bounds.height}px`,
|
|
152
|
-
});
|
|
153
|
-
}
|
|
154
|
-
/**
|
|
155
|
-
* Handle input event
|
|
156
|
-
*/
|
|
157
|
-
onInput(e) {
|
|
158
|
-
if (!this.overlay || !this.onChangeCallback)
|
|
159
|
-
return;
|
|
160
|
-
const value = this.overlay.value;
|
|
161
|
-
this.onChangeCallback(value);
|
|
162
|
-
// Dispatch __hypen_bind for two-way binding
|
|
163
|
-
if (this.bindPath && this.engine) {
|
|
164
|
-
this.engine.dispatchAction("__hypen_bind", {
|
|
165
|
-
path: this.bindPath,
|
|
166
|
-
value,
|
|
167
|
-
});
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
/**
|
|
171
|
-
* Handle blur event
|
|
172
|
-
*/
|
|
173
|
-
onBlur() {
|
|
174
|
-
// Hide overlay when focus is lost
|
|
175
|
-
this.hideInput();
|
|
176
|
-
}
|
|
177
|
-
/**
|
|
178
|
-
* Handle keyboard events
|
|
179
|
-
*/
|
|
180
|
-
onKeyDown(e) {
|
|
181
|
-
if (!this.overlay)
|
|
182
|
-
return;
|
|
183
|
-
// Enter key submits (unless multiline)
|
|
184
|
-
if (e.key === "Enter" && this.overlay instanceof HTMLInputElement) {
|
|
185
|
-
e.preventDefault();
|
|
186
|
-
this.overlay.blur();
|
|
187
|
-
}
|
|
188
|
-
// Escape cancels
|
|
189
|
-
if (e.key === "Escape") {
|
|
190
|
-
e.preventDefault();
|
|
191
|
-
this.overlay.blur();
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
/**
|
|
195
|
-
* Check if input is currently shown
|
|
196
|
-
*/
|
|
197
|
-
isShown() {
|
|
198
|
-
return this.overlay !== null;
|
|
199
|
-
}
|
|
200
|
-
/**
|
|
201
|
-
* Get current focused node
|
|
202
|
-
*/
|
|
203
|
-
getFocusedNode() {
|
|
204
|
-
return this.focusedNode;
|
|
205
|
-
}
|
|
206
|
-
}
|
|
207
|
-
//# sourceMappingURL=input.js.map
|
package/dist/canvas/input.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"input.js","sourceRoot":"","sources":["../../src/canvas/input.ts"],"names":[],"mappings":"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"}
|
package/dist/client.d.ts
DELETED
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* createHypenClient — single-call wiring for renderer + engine.
|
|
3
|
-
*
|
|
4
|
-
* Replaces the boilerplate:
|
|
5
|
-
* const renderer = new DOMRenderer(container, engine);
|
|
6
|
-
* engine.onPatches((patches) => renderer.applyPatches(patches));
|
|
7
|
-
*
|
|
8
|
-
* Works with either a local Engine (`setRenderCallback`) or RemoteEngine
|
|
9
|
-
* (`onPatches`) — they expose different APIs but the same intent.
|
|
10
|
-
*/
|
|
11
|
-
import type { Patch } from "@hypen-space/core/types";
|
|
12
|
-
import { DOMRenderer } from "./dom/renderer.js";
|
|
13
|
-
import type { DebugConfig } from "./dom/debug.js";
|
|
14
|
-
import { CanvasRenderer } from "./canvas/renderer.js";
|
|
15
|
-
import type { CanvasRendererOptions } from "./canvas/types.js";
|
|
16
|
-
interface PatchSourceLike {
|
|
17
|
-
dispatchAction(name: string, payload?: unknown): void;
|
|
18
|
-
onPatches?(callback: (patches: Patch[]) => void): unknown;
|
|
19
|
-
setRenderCallback?(callback: (patches: Patch[]) => void): void;
|
|
20
|
-
}
|
|
21
|
-
export interface DomClientOptions {
|
|
22
|
-
debug?: Partial<DebugConfig>;
|
|
23
|
-
}
|
|
24
|
-
export interface DomClient {
|
|
25
|
-
renderer: DOMRenderer;
|
|
26
|
-
destroy(): void;
|
|
27
|
-
}
|
|
28
|
-
export declare function createDomClient(container: HTMLElement, engine: PatchSourceLike, options?: DomClientOptions): DomClient;
|
|
29
|
-
export interface CanvasClientOptions extends Partial<CanvasRendererOptions> {
|
|
30
|
-
}
|
|
31
|
-
export interface CanvasClient {
|
|
32
|
-
renderer: CanvasRenderer;
|
|
33
|
-
destroy(): void;
|
|
34
|
-
}
|
|
35
|
-
export declare function createCanvasClient(canvas: HTMLCanvasElement, engine: PatchSourceLike, options?: CanvasClientOptions): CanvasClient;
|
|
36
|
-
export {};
|
package/dist/client.js
DELETED
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* createHypenClient — single-call wiring for renderer + engine.
|
|
3
|
-
*
|
|
4
|
-
* Replaces the boilerplate:
|
|
5
|
-
* const renderer = new DOMRenderer(container, engine);
|
|
6
|
-
* engine.onPatches((patches) => renderer.applyPatches(patches));
|
|
7
|
-
*
|
|
8
|
-
* Works with either a local Engine (`setRenderCallback`) or RemoteEngine
|
|
9
|
-
* (`onPatches`) — they expose different APIs but the same intent.
|
|
10
|
-
*/
|
|
11
|
-
import { DOMRenderer } from "./dom/renderer.js";
|
|
12
|
-
import { CanvasRenderer } from "./canvas/renderer.js";
|
|
13
|
-
function attachPatchListener(engine, callback) {
|
|
14
|
-
if (typeof engine.onPatches === "function") {
|
|
15
|
-
engine.onPatches(callback);
|
|
16
|
-
return;
|
|
17
|
-
}
|
|
18
|
-
if (typeof engine.setRenderCallback === "function") {
|
|
19
|
-
engine.setRenderCallback(callback);
|
|
20
|
-
return;
|
|
21
|
-
}
|
|
22
|
-
throw new Error("createHypenClient: engine has neither onPatches() nor setRenderCallback() — cannot subscribe to patches.");
|
|
23
|
-
}
|
|
24
|
-
export function createDomClient(container, engine, options) {
|
|
25
|
-
const renderer = new DOMRenderer(container, engine, options?.debug);
|
|
26
|
-
attachPatchListener(engine, (patches) => renderer.applyPatches(patches));
|
|
27
|
-
return {
|
|
28
|
-
renderer,
|
|
29
|
-
destroy() {
|
|
30
|
-
// DOMRenderer has no destroy yet; clearing the container is the
|
|
31
|
-
// closest approximation. Patch subscriptions live on the engine —
|
|
32
|
-
// the caller controls engine lifetime, so we don't tear them down.
|
|
33
|
-
container.innerHTML = "";
|
|
34
|
-
},
|
|
35
|
-
};
|
|
36
|
-
}
|
|
37
|
-
export function createCanvasClient(canvas, engine, options) {
|
|
38
|
-
const renderer = new CanvasRenderer(canvas, engine, options);
|
|
39
|
-
attachPatchListener(engine, (patches) => renderer.applyPatches(patches));
|
|
40
|
-
return {
|
|
41
|
-
renderer,
|
|
42
|
-
destroy() {
|
|
43
|
-
renderer.destroy();
|
|
44
|
-
},
|
|
45
|
-
};
|
|
46
|
-
}
|
|
47
|
-
//# sourceMappingURL=client.js.map
|
package/dist/client.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAGH,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAEhD,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAStD,SAAS,mBAAmB,CAC1B,MAAuB,EACvB,QAAoC;IAEpC,IAAI,OAAO,MAAM,CAAC,SAAS,KAAK,UAAU,EAAE,CAAC;QAC3C,MAAM,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAC;QAC3B,OAAO;IACT,CAAC;IACD,IAAI,OAAO,MAAM,CAAC,iBAAiB,KAAK,UAAU,EAAE,CAAC;QACnD,MAAM,CAAC,iBAAiB,CAAC,QAAQ,CAAC,CAAC;QACnC,OAAO;IACT,CAAC;IACD,MAAM,IAAI,KAAK,CACb,0GAA0G,CAC3G,CAAC;AACJ,CAAC;AAWD,MAAM,UAAU,eAAe,CAC7B,SAAsB,EACtB,MAAuB,EACvB,OAA0B;IAE1B,MAAM,QAAQ,GAAG,IAAI,WAAW,CAAC,SAAS,EAAE,MAAa,EAAE,OAAO,EAAE,KAAK,CAAC,CAAC;IAC3E,mBAAmB,CAAC,MAAM,EAAE,CAAC,OAAO,EAAE,EAAE,CAAC,QAAQ,CAAC,YAAY,CAAC,OAAO,CAAC,CAAC,CAAC;IACzE,OAAO;QACL,QAAQ;QACR,OAAO;YACL,gEAAgE;YAChE,kEAAkE;YAClE,mEAAmE;YACnE,SAAS,CAAC,SAAS,GAAG,EAAE,CAAC;QAC3B,CAAC;KACF,CAAC;AACJ,CAAC;AASD,MAAM,UAAU,kBAAkB,CAChC,MAAyB,EACzB,MAAuB,EACvB,OAA6B;IAE7B,MAAM,QAAQ,GAAG,IAAI,cAAc,CAAC,MAAM,EAAE,MAAa,EAAE,OAAO,CAAC,CAAC;IACpE,mBAAmB,CAAC,MAAM,EAAE,CAAC,OAAO,EAAE,EAAE,CAAC,QAAQ,CAAC,YAAY,CAAC,OAAO,CAAC,CAAC,CAAC;IACzE,OAAO;QACL,QAAQ;QACR,OAAO;YACL,QAAQ,CAAC,OAAO,EAAE,CAAC;QACrB,CAAC;KACF,CAAC;AACJ,CAAC"}
|
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
|
-
|
package/src/client.ts
DELETED
|
@@ -1,88 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* createHypenClient — single-call wiring for renderer + engine.
|
|
3
|
-
*
|
|
4
|
-
* Replaces the boilerplate:
|
|
5
|
-
* const renderer = new DOMRenderer(container, engine);
|
|
6
|
-
* engine.onPatches((patches) => renderer.applyPatches(patches));
|
|
7
|
-
*
|
|
8
|
-
* Works with either a local Engine (`setRenderCallback`) or RemoteEngine
|
|
9
|
-
* (`onPatches`) — they expose different APIs but the same intent.
|
|
10
|
-
*/
|
|
11
|
-
|
|
12
|
-
import type { Patch } from "@hypen-space/core/types";
|
|
13
|
-
import { DOMRenderer } from "./dom/renderer.js";
|
|
14
|
-
import type { DebugConfig } from "./dom/debug.js";
|
|
15
|
-
import { CanvasRenderer } from "./canvas/renderer.js";
|
|
16
|
-
import type { CanvasRendererOptions } from "./canvas/types.js";
|
|
17
|
-
|
|
18
|
-
interface PatchSourceLike {
|
|
19
|
-
dispatchAction(name: string, payload?: unknown): void;
|
|
20
|
-
onPatches?(callback: (patches: Patch[]) => void): unknown;
|
|
21
|
-
setRenderCallback?(callback: (patches: Patch[]) => void): void;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
function attachPatchListener(
|
|
25
|
-
engine: PatchSourceLike,
|
|
26
|
-
callback: (patches: Patch[]) => void,
|
|
27
|
-
): void {
|
|
28
|
-
if (typeof engine.onPatches === "function") {
|
|
29
|
-
engine.onPatches(callback);
|
|
30
|
-
return;
|
|
31
|
-
}
|
|
32
|
-
if (typeof engine.setRenderCallback === "function") {
|
|
33
|
-
engine.setRenderCallback(callback);
|
|
34
|
-
return;
|
|
35
|
-
}
|
|
36
|
-
throw new Error(
|
|
37
|
-
"createHypenClient: engine has neither onPatches() nor setRenderCallback() — cannot subscribe to patches.",
|
|
38
|
-
);
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
export interface DomClientOptions {
|
|
42
|
-
debug?: Partial<DebugConfig>;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
export interface DomClient {
|
|
46
|
-
renderer: DOMRenderer;
|
|
47
|
-
destroy(): void;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
export function createDomClient(
|
|
51
|
-
container: HTMLElement,
|
|
52
|
-
engine: PatchSourceLike,
|
|
53
|
-
options?: DomClientOptions,
|
|
54
|
-
): DomClient {
|
|
55
|
-
const renderer = new DOMRenderer(container, engine as any, options?.debug);
|
|
56
|
-
attachPatchListener(engine, (patches) => renderer.applyPatches(patches));
|
|
57
|
-
return {
|
|
58
|
-
renderer,
|
|
59
|
-
destroy() {
|
|
60
|
-
// DOMRenderer has no destroy yet; clearing the container is the
|
|
61
|
-
// closest approximation. Patch subscriptions live on the engine —
|
|
62
|
-
// the caller controls engine lifetime, so we don't tear them down.
|
|
63
|
-
container.innerHTML = "";
|
|
64
|
-
},
|
|
65
|
-
};
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
export interface CanvasClientOptions extends Partial<CanvasRendererOptions> {}
|
|
69
|
-
|
|
70
|
-
export interface CanvasClient {
|
|
71
|
-
renderer: CanvasRenderer;
|
|
72
|
-
destroy(): void;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
export function createCanvasClient(
|
|
76
|
-
canvas: HTMLCanvasElement,
|
|
77
|
-
engine: PatchSourceLike,
|
|
78
|
-
options?: CanvasClientOptions,
|
|
79
|
-
): CanvasClient {
|
|
80
|
-
const renderer = new CanvasRenderer(canvas, engine as any, options);
|
|
81
|
-
attachPatchListener(engine, (patches) => renderer.applyPatches(patches));
|
|
82
|
-
return {
|
|
83
|
-
renderer,
|
|
84
|
-
destroy() {
|
|
85
|
-
renderer.destroy();
|
|
86
|
-
},
|
|
87
|
-
};
|
|
88
|
-
}
|