@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
|
@@ -21,6 +21,8 @@ import {
|
|
|
21
21
|
unregisterEvent,
|
|
22
22
|
getKeyTarget,
|
|
23
23
|
setKeyTarget,
|
|
24
|
+
getMeta,
|
|
25
|
+
setMeta,
|
|
24
26
|
} from "../element-data.js";
|
|
25
27
|
|
|
26
28
|
const log = frameworkLoggers.events;
|
|
@@ -197,6 +199,7 @@ function createEventHandler(
|
|
|
197
199
|
eventType: string,
|
|
198
200
|
options: EventHandlerOptions = {}
|
|
199
201
|
): ApplicatorHandler {
|
|
202
|
+
const actionMetaKey = `event:${eventType}`;
|
|
200
203
|
return (element: HTMLElement, value: unknown) => {
|
|
201
204
|
const { actionName, payload: customPayload } = extractActionDetails(value);
|
|
202
205
|
|
|
@@ -205,14 +208,18 @@ function createEventHandler(
|
|
|
205
208
|
return;
|
|
206
209
|
}
|
|
207
210
|
|
|
211
|
+
// Store the current action on the element; the persistent listener reads
|
|
212
|
+
// it per event, so re-applying with a different action (e.g. a keyed-
|
|
213
|
+
// reused node) replaces the dispatch target instead of stacking listeners.
|
|
214
|
+
setMeta(element, actionMetaKey, { actionName, customPayload });
|
|
215
|
+
|
|
208
216
|
const disposables = getElementDisposables(element);
|
|
209
217
|
|
|
210
218
|
// Track that we've registered this event type
|
|
211
219
|
// The disposable stack handles cleanup automatically
|
|
212
|
-
const eventKey =
|
|
220
|
+
const eventKey = eventType;
|
|
213
221
|
if (getRegisteredEvents(element).has(eventKey)) {
|
|
214
|
-
//
|
|
215
|
-
// This can happen during re-renders
|
|
222
|
+
// Listener already installed — the meta update above retargets it
|
|
216
223
|
return;
|
|
217
224
|
}
|
|
218
225
|
registerEvent(element, eventKey);
|
|
@@ -222,6 +229,12 @@ function createEventHandler(
|
|
|
222
229
|
|
|
223
230
|
// Create the event listener
|
|
224
231
|
const listener = (event: Event) => {
|
|
232
|
+
const current = getMeta<{ actionName: string; customPayload: Record<string, unknown> }>(
|
|
233
|
+
element,
|
|
234
|
+
actionMetaKey,
|
|
235
|
+
);
|
|
236
|
+
if (!current) return;
|
|
237
|
+
|
|
225
238
|
// Handle throttling
|
|
226
239
|
if (options.throttleMs && throttleTimer) {
|
|
227
240
|
return;
|
|
@@ -240,8 +253,8 @@ function createEventHandler(
|
|
|
240
253
|
|
|
241
254
|
// Build payload
|
|
242
255
|
const payload =
|
|
243
|
-
Object.keys(customPayload).length > 0
|
|
244
|
-
? { ...customPayload }
|
|
256
|
+
Object.keys(current.customPayload).length > 0
|
|
257
|
+
? { ...current.customPayload }
|
|
245
258
|
: options.extractPayload
|
|
246
259
|
? options.extractPayload(event, element)
|
|
247
260
|
: extractEventData(event, element);
|
|
@@ -250,9 +263,9 @@ function createEventHandler(
|
|
|
250
263
|
const engine = getEngine(element);
|
|
251
264
|
if (engine) {
|
|
252
265
|
try {
|
|
253
|
-
engine.dispatchAction(actionName, payload);
|
|
266
|
+
engine.dispatchAction(current.actionName, payload);
|
|
254
267
|
} catch (err) {
|
|
255
|
-
log.error(`Error dispatching action "${actionName}":`, err);
|
|
268
|
+
log.error(`Error dispatching action "${current.actionName}":`, err);
|
|
256
269
|
}
|
|
257
270
|
}
|
|
258
271
|
};
|
|
@@ -274,6 +287,24 @@ function createEventHandler(
|
|
|
274
287
|
};
|
|
275
288
|
}
|
|
276
289
|
|
|
290
|
+
/**
|
|
291
|
+
* Dispatch the action encoded in an applicator value (e.g. `"@actions.save"`)
|
|
292
|
+
* to the element's engine. Shared by click wiring and keyboard activation so
|
|
293
|
+
* both routes dispatch identically. No-op if the value carries no action or
|
|
294
|
+
* the element has no engine.
|
|
295
|
+
*/
|
|
296
|
+
export function triggerElementAction(element: HTMLElement, value: unknown): void {
|
|
297
|
+
const { actionName, payload } = extractActionDetails(value);
|
|
298
|
+
if (!actionName) return;
|
|
299
|
+
const engine = getEngine(element);
|
|
300
|
+
if (!engine) return;
|
|
301
|
+
try {
|
|
302
|
+
engine.dispatchAction(actionName, payload);
|
|
303
|
+
} catch (err) {
|
|
304
|
+
log.error(`Error dispatching action "${actionName}":`, err);
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
|
|
277
308
|
/**
|
|
278
309
|
* Create a keyboard event handler that filters by key
|
|
279
310
|
*/
|
|
@@ -496,6 +527,7 @@ export const eventHandlers: Record<string, ApplicatorHandler> = {
|
|
|
496
527
|
// Mouse hover events
|
|
497
528
|
onMouseEnter: createEventHandler("mouseenter", { extractPayload: mousePayload }),
|
|
498
529
|
onMouseLeave: createEventHandler("mouseleave", { extractPayload: mousePayload }),
|
|
530
|
+
onHover: createEventHandler("mouseenter", { extractPayload: mousePayload }), // Alias for onMouseEnter
|
|
499
531
|
|
|
500
532
|
// Two-way binding for .bind(@state.x)
|
|
501
533
|
bind: ((element: HTMLElement, value: unknown) => {
|
|
@@ -5,17 +5,8 @@
|
|
|
5
5
|
import type { ApplicatorHandler } from "./types.js";
|
|
6
6
|
import { toCssLength } from "./size.js";
|
|
7
7
|
|
|
8
|
-
// Track loaded Google Fonts
|
|
9
|
-
const loadedGoogleFonts = new
|
|
10
|
-
|
|
11
|
-
function getLoadedSet(doc: Document): Set<string> {
|
|
12
|
-
let set = loadedGoogleFonts.get(doc);
|
|
13
|
-
if (!set) {
|
|
14
|
-
set = new Set<string>();
|
|
15
|
-
loadedGoogleFonts.set(doc, set);
|
|
16
|
-
}
|
|
17
|
-
return set;
|
|
18
|
-
}
|
|
8
|
+
// Track loaded Google Fonts to avoid duplicate link tags
|
|
9
|
+
const loadedGoogleFonts = new Set<string>();
|
|
19
10
|
|
|
20
11
|
// System font keywords that shouldn't be loaded from Google Fonts
|
|
21
12
|
const systemFontKeywords = new Set([
|
|
@@ -36,42 +27,40 @@ function isSystemFont(fontName: string): boolean {
|
|
|
36
27
|
}
|
|
37
28
|
|
|
38
29
|
/**
|
|
39
|
-
* Load a Google Font by injecting a link tag
|
|
30
|
+
* Load a Google Font by injecting a link tag.
|
|
40
31
|
* @param fontName - The Google Font name (e.g., "Roboto", "Open Sans")
|
|
41
|
-
* @param doc - The Document to inject into.
|
|
42
32
|
*/
|
|
43
|
-
function loadGoogleFont(fontName: string
|
|
33
|
+
function loadGoogleFont(fontName: string): void {
|
|
44
34
|
const normalized = fontName.trim();
|
|
45
|
-
const loaded = getLoadedSet(doc);
|
|
46
35
|
|
|
47
36
|
// Skip if already loaded or is a system font
|
|
48
|
-
if (
|
|
37
|
+
if (loadedGoogleFonts.has(normalized) || isSystemFont(normalized)) {
|
|
49
38
|
return;
|
|
50
39
|
}
|
|
51
40
|
|
|
52
41
|
// Mark as loading to avoid duplicates
|
|
53
|
-
|
|
42
|
+
loadedGoogleFonts.add(normalized);
|
|
54
43
|
|
|
55
44
|
// Create link element for Google Fonts
|
|
56
|
-
const link =
|
|
45
|
+
const link = document.createElement("link");
|
|
57
46
|
link.rel = "stylesheet";
|
|
58
47
|
link.href = `https://fonts.googleapis.com/css2?family=${encodeURIComponent(normalized)}:wght@100;200;300;400;500;600;700;800;900&display=swap`;
|
|
59
48
|
|
|
60
49
|
// Add to document head
|
|
61
|
-
|
|
50
|
+
document.head.appendChild(link);
|
|
62
51
|
}
|
|
63
52
|
|
|
64
53
|
/**
|
|
65
54
|
* Parse a fontFamily value and load any Google Fonts.
|
|
66
55
|
* Returns a CSS-safe font-family string.
|
|
67
56
|
*/
|
|
68
|
-
function processFontFamily(value: string
|
|
57
|
+
function processFontFamily(value: string): string {
|
|
69
58
|
// Split by comma to handle font stacks
|
|
70
59
|
const fonts = value.split(",").map(f => f.trim().replace(/["']/g, ""));
|
|
71
60
|
|
|
72
61
|
for (const font of fonts) {
|
|
73
62
|
if (!isSystemFont(font)) {
|
|
74
|
-
loadGoogleFont(font
|
|
63
|
+
loadGoogleFont(font);
|
|
75
64
|
}
|
|
76
65
|
}
|
|
77
66
|
|
|
@@ -97,7 +86,7 @@ export const fontHandlers: Record<string, ApplicatorHandler> = {
|
|
|
97
86
|
fontFamily: (el, value) => {
|
|
98
87
|
const fontValue = String(value);
|
|
99
88
|
// Process font family and load Google Fonts as needed
|
|
100
|
-
el.style.fontFamily = processFontFamily(fontValue
|
|
89
|
+
el.style.fontFamily = processFontFamily(fontValue);
|
|
101
90
|
},
|
|
102
91
|
|
|
103
92
|
textAlign: (el, value) => {
|
|
@@ -138,20 +127,18 @@ export const fontHandlers: Record<string, ApplicatorHandler> = {
|
|
|
138
127
|
export const GoogleFonts = {
|
|
139
128
|
/**
|
|
140
129
|
* Preload a Google Font before it's used.
|
|
141
|
-
* Defaults to the global `document` if no doc is provided.
|
|
142
130
|
*/
|
|
143
|
-
preload:
|
|
131
|
+
preload: loadGoogleFont,
|
|
144
132
|
|
|
145
133
|
/**
|
|
146
|
-
* Check if a font has been loaded
|
|
134
|
+
* Check if a font has been loaded.
|
|
147
135
|
*/
|
|
148
|
-
isLoaded: (fontName: string
|
|
149
|
-
getLoadedSet(doc).has(fontName.trim()),
|
|
136
|
+
isLoaded: (fontName: string) => loadedGoogleFonts.has(fontName.trim()),
|
|
150
137
|
|
|
151
138
|
/**
|
|
152
|
-
* Get list of loaded fonts
|
|
139
|
+
* Get list of loaded fonts.
|
|
153
140
|
*/
|
|
154
|
-
getLoadedFonts: (
|
|
141
|
+
getLoadedFonts: () => Array.from(loadedGoogleFonts),
|
|
155
142
|
|
|
156
143
|
/**
|
|
157
144
|
* Popular Google Fonts for reference.
|
|
@@ -40,53 +40,39 @@ import { advancedLayoutHandlers } from "./advanced-layout.js";
|
|
|
40
40
|
import { backgroundHandlers } from "./background.js";
|
|
41
41
|
import { displayHandlers } from "./display.js";
|
|
42
42
|
import { transitionHandlers } from "./transition.js";
|
|
43
|
+
import { BREAKPOINTS as VARIANT_BREAKPOINTS, VALID_STATES } from "../../variants.js";
|
|
44
|
+
import { ariaHandlers } from "./aria.js";
|
|
43
45
|
|
|
44
46
|
/**
|
|
45
|
-
* Tailwind breakpoint values for responsive variants
|
|
47
|
+
* Tailwind breakpoint values for responsive variants.
|
|
48
|
+
* Derived from the shared `variants.ts` table (single source of truth across
|
|
49
|
+
* DOM + Canvas renderers) and expressed as CSS `px` strings for media queries.
|
|
46
50
|
*/
|
|
47
|
-
const BREAKPOINTS: Record<string, string> =
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
lg: '1024px',
|
|
51
|
-
xl: '1280px',
|
|
52
|
-
'2xl': '1536px',
|
|
53
|
-
};
|
|
51
|
+
const BREAKPOINTS: Record<string, string> = Object.fromEntries(
|
|
52
|
+
Object.entries(VARIANT_BREAKPOINTS).map(([name, px]) => [name, `${px}px`]),
|
|
53
|
+
);
|
|
54
54
|
|
|
55
55
|
/**
|
|
56
|
-
*
|
|
56
|
+
* Singleton stylesheet for variant CSS rules
|
|
57
57
|
*/
|
|
58
|
-
|
|
58
|
+
let variantStyleSheet: CSSStyleSheet | null = null;
|
|
59
59
|
|
|
60
60
|
/**
|
|
61
|
-
* Track inserted rules to avoid duplicates
|
|
61
|
+
* Track inserted rules to avoid duplicates
|
|
62
62
|
*/
|
|
63
|
-
const insertedRules = new
|
|
63
|
+
const insertedRules = new Set<string>();
|
|
64
64
|
|
|
65
65
|
/**
|
|
66
|
-
* Get or create the variant stylesheet
|
|
66
|
+
* Get or create the variant stylesheet
|
|
67
67
|
*/
|
|
68
|
-
function getVariantStyleSheet(
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
const style = doc.createElement('style');
|
|
68
|
+
function getVariantStyleSheet(): CSSStyleSheet {
|
|
69
|
+
if (!variantStyleSheet) {
|
|
70
|
+
const style = document.createElement('style');
|
|
72
71
|
style.id = 'hypen-variants';
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
variantStyleSheets.set(doc, sheet);
|
|
72
|
+
document.head.appendChild(style);
|
|
73
|
+
variantStyleSheet = style.sheet as CSSStyleSheet;
|
|
76
74
|
}
|
|
77
|
-
return
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
/**
|
|
81
|
-
* Get or create the inserted-rules dedup set for a document
|
|
82
|
-
*/
|
|
83
|
-
function getInsertedRules(doc: Document): Set<string> {
|
|
84
|
-
let set = insertedRules.get(doc);
|
|
85
|
-
if (!set) {
|
|
86
|
-
set = new Set<string>();
|
|
87
|
-
insertedRules.set(doc, set);
|
|
88
|
-
}
|
|
89
|
-
return set;
|
|
75
|
+
return variantStyleSheet;
|
|
90
76
|
}
|
|
91
77
|
|
|
92
78
|
/**
|
|
@@ -341,6 +327,36 @@ export class ApplicatorRegistry {
|
|
|
341
327
|
const atIndex = name.indexOf('@');
|
|
342
328
|
const colonIndex = name.indexOf(':');
|
|
343
329
|
|
|
330
|
+
// Combined responsive + state variant: backgroundColor@md:hover.
|
|
331
|
+
// Canonical key order is `<prop>@<bp>:<state>`, so the breakpoint sits
|
|
332
|
+
// between '@' and ':'. Emitted as a pseudo-class rule nested in a media
|
|
333
|
+
// query so it applies only when BOTH the width matches AND the state holds.
|
|
334
|
+
if (atIndex !== -1 && colonIndex !== -1 && colonIndex > atIndex) {
|
|
335
|
+
const prop = name.slice(0, atIndex);
|
|
336
|
+
const breakpoint = name.slice(atIndex + 1, colonIndex);
|
|
337
|
+
const state = name.slice(colonIndex + 1);
|
|
338
|
+
const minWidth = BREAKPOINTS[breakpoint];
|
|
339
|
+
|
|
340
|
+
if (minWidth && VALID_STATES.includes(state)) {
|
|
341
|
+
const cssName = this.toKebabCase(prop);
|
|
342
|
+
const cssValue = this.formatCssValue(cssName, value);
|
|
343
|
+
const className = `hypen-${cssName.replace(/[^a-zA-Z0-9-]/g, '')}-${breakpoint}-${state}-${hashValue(value)}`;
|
|
344
|
+
const ruleKey = `${className}:${cssValue}`;
|
|
345
|
+
|
|
346
|
+
if (!insertedRules.has(ruleKey)) {
|
|
347
|
+
const sheet = getVariantStyleSheet();
|
|
348
|
+
sheet.insertRule(
|
|
349
|
+
`@media (min-width: ${minWidth}) { .${className}:${state} { ${cssName}: ${cssValue}; } }`,
|
|
350
|
+
sheet.cssRules.length
|
|
351
|
+
);
|
|
352
|
+
insertedRules.add(ruleKey);
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
element.classList.add(className);
|
|
356
|
+
}
|
|
357
|
+
return;
|
|
358
|
+
}
|
|
359
|
+
|
|
344
360
|
// Responsive variant: padding@md, width@lg, etc.
|
|
345
361
|
if (atIndex !== -1) {
|
|
346
362
|
const prop = name.slice(0, atIndex);
|
|
@@ -348,21 +364,19 @@ export class ApplicatorRegistry {
|
|
|
348
364
|
const minWidth = BREAKPOINTS[breakpoint];
|
|
349
365
|
|
|
350
366
|
if (minWidth) {
|
|
351
|
-
const doc = element.ownerDocument as Document;
|
|
352
|
-
const dedup = getInsertedRules(doc);
|
|
353
367
|
const cssName = this.toKebabCase(prop);
|
|
354
368
|
const cssValue = this.formatCssValue(cssName, value);
|
|
355
369
|
const className = `hypen-${cssName.replace(/[^a-zA-Z0-9-]/g, '')}-${breakpoint}-${hashValue(value)}`;
|
|
356
370
|
const ruleKey = `${className}:${cssValue}`;
|
|
357
371
|
|
|
358
372
|
// Avoid duplicate rule insertion
|
|
359
|
-
if (!
|
|
360
|
-
const sheet = getVariantStyleSheet(
|
|
373
|
+
if (!insertedRules.has(ruleKey)) {
|
|
374
|
+
const sheet = getVariantStyleSheet();
|
|
361
375
|
sheet.insertRule(
|
|
362
376
|
`@media (min-width: ${minWidth}) { .${className} { ${cssName}: ${cssValue}; } }`,
|
|
363
377
|
sheet.cssRules.length
|
|
364
378
|
);
|
|
365
|
-
|
|
379
|
+
insertedRules.add(ruleKey);
|
|
366
380
|
}
|
|
367
381
|
|
|
368
382
|
element.classList.add(className);
|
|
@@ -375,24 +389,21 @@ export class ApplicatorRegistry {
|
|
|
375
389
|
const prop = name.slice(0, colonIndex);
|
|
376
390
|
const state = name.slice(colonIndex + 1);
|
|
377
391
|
|
|
378
|
-
// Only handle known CSS pseudo-states
|
|
379
|
-
|
|
380
|
-
if (validStates.includes(state)) {
|
|
381
|
-
const doc = element.ownerDocument as Document;
|
|
382
|
-
const dedup = getInsertedRules(doc);
|
|
392
|
+
// Only handle known CSS pseudo-states (shared with the Canvas resolver)
|
|
393
|
+
if (VALID_STATES.includes(state)) {
|
|
383
394
|
const cssName = this.toKebabCase(prop);
|
|
384
395
|
const cssValue = this.formatCssValue(cssName, value);
|
|
385
396
|
const className = `hypen-${cssName.replace(/[^a-zA-Z0-9-]/g, '')}-${state}-${hashValue(value)}`;
|
|
386
397
|
const ruleKey = `${className}:${cssValue}`;
|
|
387
398
|
|
|
388
399
|
// Avoid duplicate rule insertion
|
|
389
|
-
if (!
|
|
390
|
-
const sheet = getVariantStyleSheet(
|
|
400
|
+
if (!insertedRules.has(ruleKey)) {
|
|
401
|
+
const sheet = getVariantStyleSheet();
|
|
391
402
|
sheet.insertRule(
|
|
392
403
|
`.${className}:${state} { ${cssName}: ${cssValue}; }`,
|
|
393
404
|
sheet.cssRules.length
|
|
394
405
|
);
|
|
395
|
-
|
|
406
|
+
insertedRules.add(ruleKey);
|
|
396
407
|
}
|
|
397
408
|
|
|
398
409
|
element.classList.add(className);
|
|
@@ -510,5 +521,9 @@ export class ApplicatorRegistry {
|
|
|
510
521
|
for (const [name, handler] of Object.entries(transitionHandlers)) {
|
|
511
522
|
this.register(name, handler as ApplicatorHandler);
|
|
512
523
|
}
|
|
524
|
+
|
|
525
|
+
for (const [name, handler] of Object.entries(ariaHandlers)) {
|
|
526
|
+
this.register(name, handler as ApplicatorHandler);
|
|
527
|
+
}
|
|
513
528
|
}
|
|
514
529
|
}
|
package/src/dom/canvas/index.ts
CHANGED
|
@@ -8,8 +8,8 @@ import type { ComponentHandler } from "../components/index.js";
|
|
|
8
8
|
import type { ApplicatorHandler } from "../applicators/index.js";
|
|
9
9
|
|
|
10
10
|
export const canvasHandler: ComponentHandler = {
|
|
11
|
-
create(
|
|
12
|
-
const el =
|
|
11
|
+
create(): HTMLElement {
|
|
12
|
+
const el = document.createElement("canvas");
|
|
13
13
|
el.dataset.hypenType = "canvas";
|
|
14
14
|
return el as any as HTMLElement;
|
|
15
15
|
},
|
|
@@ -8,8 +8,8 @@
|
|
|
8
8
|
import type { ComponentHandler } from "./index.js";
|
|
9
9
|
|
|
10
10
|
export const appHandler: ComponentHandler = {
|
|
11
|
-
create(
|
|
12
|
-
const el =
|
|
11
|
+
create(): HTMLElement {
|
|
12
|
+
const el = document.createElement("div");
|
|
13
13
|
el.style.display = "flex";
|
|
14
14
|
el.style.flexDirection = "column";
|
|
15
15
|
el.style.minHeight = "100vh";
|
|
@@ -5,8 +5,8 @@
|
|
|
5
5
|
import type { ComponentHandler } from "./index.js";
|
|
6
6
|
|
|
7
7
|
export const audioHandler: ComponentHandler = {
|
|
8
|
-
create(
|
|
9
|
-
const el =
|
|
8
|
+
create(): HTMLElement {
|
|
9
|
+
const el = document.createElement("audio");
|
|
10
10
|
el.dataset.hypenType = "audio";
|
|
11
11
|
return el as any as HTMLElement;
|
|
12
12
|
},
|
|
@@ -5,8 +5,8 @@
|
|
|
5
5
|
import type { ComponentHandler } from "./index.js";
|
|
6
6
|
|
|
7
7
|
export const avatarHandler: ComponentHandler = {
|
|
8
|
-
create(
|
|
9
|
-
const el =
|
|
8
|
+
create(): HTMLElement {
|
|
9
|
+
const el = document.createElement("div");
|
|
10
10
|
el.dataset.hypenType = "avatar";
|
|
11
11
|
el.style.display = "inline-flex";
|
|
12
12
|
el.style.alignItems = "center";
|
|
@@ -23,11 +23,10 @@ export const avatarHandler: ComponentHandler = {
|
|
|
23
23
|
},
|
|
24
24
|
|
|
25
25
|
applyProps(el: HTMLElement, props: Record<string, any>): void {
|
|
26
|
-
const doc = el.ownerDocument as Document;
|
|
27
26
|
// Image source - support named arg with .0 suffix, positional arg, or plain name
|
|
28
27
|
const src = props["src.0"] || props["0"] || props.src || props.source;
|
|
29
28
|
if (src !== undefined) {
|
|
30
|
-
const img =
|
|
29
|
+
const img = document.createElement("img");
|
|
31
30
|
img.src = String(src);
|
|
32
31
|
img.style.width = "100%";
|
|
33
32
|
img.style.height = "100%";
|
|
@@ -5,8 +5,8 @@
|
|
|
5
5
|
import type { ComponentHandler } from "./index.js";
|
|
6
6
|
|
|
7
7
|
export const badgeHandler: ComponentHandler = {
|
|
8
|
-
create(
|
|
9
|
-
const el =
|
|
8
|
+
create(): HTMLElement {
|
|
9
|
+
const el = document.createElement("span");
|
|
10
10
|
el.dataset.hypenType = "badge";
|
|
11
11
|
el.style.display = "inline-block";
|
|
12
12
|
el.style.padding = "4px 8px";
|
|
@@ -35,8 +35,7 @@ export const badgeHandler: ComponentHandler = {
|
|
|
35
35
|
}
|
|
36
36
|
|
|
37
37
|
// Text content
|
|
38
|
-
|
|
39
|
-
const text = props["0"] ?? props.text;
|
|
38
|
+
const text = props["0"] || props.text;
|
|
40
39
|
if (text !== undefined) {
|
|
41
40
|
el.textContent = String(text);
|
|
42
41
|
}
|
|
@@ -9,8 +9,8 @@
|
|
|
9
9
|
import type { ComponentHandler } from "./index.js";
|
|
10
10
|
|
|
11
11
|
export const buttonHandler: ComponentHandler = {
|
|
12
|
-
create(
|
|
13
|
-
const el =
|
|
12
|
+
create(): HTMLElement {
|
|
13
|
+
const el = document.createElement("button");
|
|
14
14
|
// Reset default button styles for cross-platform consistency
|
|
15
15
|
el.style.border = "none";
|
|
16
16
|
el.style.background = "none";
|
|
@@ -5,8 +5,8 @@
|
|
|
5
5
|
import type { ComponentHandler } from "./index.js";
|
|
6
6
|
|
|
7
7
|
export const cardHandler: ComponentHandler = {
|
|
8
|
-
create(
|
|
9
|
-
const el =
|
|
8
|
+
create(): HTMLElement {
|
|
9
|
+
const el = document.createElement("div");
|
|
10
10
|
el.dataset.hypenType = "card";
|
|
11
11
|
el.style.backgroundColor = "#ffffff";
|
|
12
12
|
el.style.borderRadius = "8px";
|
|
@@ -9,8 +9,8 @@
|
|
|
9
9
|
import type { ComponentHandler } from "./index.js";
|
|
10
10
|
|
|
11
11
|
export const centerHandler: ComponentHandler = {
|
|
12
|
-
create(
|
|
13
|
-
const el =
|
|
12
|
+
create(): HTMLElement {
|
|
13
|
+
const el = document.createElement("div");
|
|
14
14
|
el.style.display = "flex";
|
|
15
15
|
el.style.alignItems = "center";
|
|
16
16
|
el.style.justifyContent = "center";
|
|
@@ -5,15 +5,15 @@
|
|
|
5
5
|
import type { ComponentHandler } from "./index.js";
|
|
6
6
|
|
|
7
7
|
export const checkboxHandler: ComponentHandler = {
|
|
8
|
-
create(
|
|
9
|
-
const wrapper =
|
|
8
|
+
create(): HTMLElement {
|
|
9
|
+
const wrapper = document.createElement("label");
|
|
10
10
|
wrapper.dataset.hypenType = "checkbox";
|
|
11
11
|
wrapper.style.display = "inline-flex";
|
|
12
12
|
wrapper.style.alignItems = "center";
|
|
13
13
|
wrapper.style.gap = "8px";
|
|
14
14
|
wrapper.style.cursor = "pointer";
|
|
15
15
|
|
|
16
|
-
const input =
|
|
16
|
+
const input = document.createElement("input");
|
|
17
17
|
input.type = "checkbox";
|
|
18
18
|
input.dataset.hypenCheckbox = "true";
|
|
19
19
|
|
|
@@ -23,7 +23,6 @@ export const checkboxHandler: ComponentHandler = {
|
|
|
23
23
|
},
|
|
24
24
|
|
|
25
25
|
applyProps(el: HTMLElement, props: Record<string, any>): void {
|
|
26
|
-
const doc = el.ownerDocument as Document;
|
|
27
26
|
const input = el.querySelector('input[type="checkbox"]') as HTMLInputElement;
|
|
28
27
|
if (!input) return;
|
|
29
28
|
|
|
@@ -47,7 +46,7 @@ export const checkboxHandler: ComponentHandler = {
|
|
|
47
46
|
textNodes.forEach(node => node.remove());
|
|
48
47
|
|
|
49
48
|
// Add new label text
|
|
50
|
-
el.appendChild(
|
|
49
|
+
el.appendChild(document.createTextNode(String(label)));
|
|
51
50
|
}
|
|
52
51
|
},
|
|
53
52
|
};
|
|
@@ -17,8 +17,8 @@
|
|
|
17
17
|
import type { ComponentHandler } from "./index.js";
|
|
18
18
|
|
|
19
19
|
export const columnHandler: ComponentHandler = {
|
|
20
|
-
create(
|
|
21
|
-
const el =
|
|
20
|
+
create(): HTMLElement {
|
|
21
|
+
const el = document.createElement("div");
|
|
22
22
|
el.style.display = "flex";
|
|
23
23
|
el.style.flexDirection = "column";
|
|
24
24
|
el.style.alignItems = "stretch";
|
|
@@ -5,8 +5,8 @@
|
|
|
5
5
|
import type { ComponentHandler } from "./index.js";
|
|
6
6
|
|
|
7
7
|
export const containerHandler: ComponentHandler = {
|
|
8
|
-
create(
|
|
9
|
-
const el =
|
|
8
|
+
create(): HTMLElement {
|
|
9
|
+
const el = document.createElement("div");
|
|
10
10
|
// Simple block container - wraps to content by default
|
|
11
11
|
// Use .fillMaxWidth(true) to stretch
|
|
12
12
|
el.dataset.hypenType = "container";
|
|
@@ -5,8 +5,8 @@
|
|
|
5
5
|
import type { ComponentHandler } from "./index.js";
|
|
6
6
|
|
|
7
7
|
export const dividerHandler: ComponentHandler = {
|
|
8
|
-
create(
|
|
9
|
-
const el =
|
|
8
|
+
create(): HTMLElement {
|
|
9
|
+
const el = document.createElement("hr");
|
|
10
10
|
el.dataset.hypenType = "divider";
|
|
11
11
|
el.style.border = "none";
|
|
12
12
|
el.style.borderTop = "1px solid #e0e0e0";
|
|
@@ -6,13 +6,13 @@
|
|
|
6
6
|
|
|
7
7
|
import type { ComponentHandler } from "./index.js";
|
|
8
8
|
|
|
9
|
-
// Inject global styles for grid children
|
|
10
|
-
|
|
11
|
-
function ensureGridStyles(
|
|
12
|
-
if (
|
|
13
|
-
|
|
9
|
+
// Inject global styles for grid children
|
|
10
|
+
let gridStylesInjected = false;
|
|
11
|
+
function ensureGridStyles(): void {
|
|
12
|
+
if (gridStylesInjected) return;
|
|
13
|
+
gridStylesInjected = true;
|
|
14
14
|
|
|
15
|
-
const style =
|
|
15
|
+
const style = document.createElement("style");
|
|
16
16
|
style.id = "hypen-grid-styles";
|
|
17
17
|
style.textContent = `
|
|
18
18
|
/* Grid children stretch to fill cells by default (matches Android behavior) */
|
|
@@ -21,14 +21,14 @@ function ensureGridStyles(doc: Document): void {
|
|
|
21
21
|
align-self: stretch;
|
|
22
22
|
}
|
|
23
23
|
`;
|
|
24
|
-
|
|
24
|
+
document.head.appendChild(style);
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
export const gridHandler: ComponentHandler = {
|
|
28
|
-
create(
|
|
29
|
-
ensureGridStyles(
|
|
28
|
+
create(): HTMLElement {
|
|
29
|
+
ensureGridStyles();
|
|
30
30
|
|
|
31
|
-
const el =
|
|
31
|
+
const el = document.createElement("div");
|
|
32
32
|
el.style.display = "grid";
|
|
33
33
|
el.dataset.hypenType = "grid";
|
|
34
34
|
return el;
|
|
@@ -5,18 +5,17 @@
|
|
|
5
5
|
import type { ComponentHandler } from "./index.js";
|
|
6
6
|
|
|
7
7
|
export const headingHandler: ComponentHandler = {
|
|
8
|
-
create(
|
|
9
|
-
const el =
|
|
8
|
+
create(): HTMLElement {
|
|
9
|
+
const el = document.createElement("h2");
|
|
10
10
|
el.dataset.hypenType = "heading";
|
|
11
11
|
return el;
|
|
12
12
|
},
|
|
13
13
|
|
|
14
14
|
applyProps(el: HTMLElement, props: Record<string, any>): void {
|
|
15
|
-
const doc = el.ownerDocument as Document;
|
|
16
15
|
// Level property (1-6)
|
|
17
16
|
if (props.level !== undefined) {
|
|
18
17
|
const level = Math.max(1, Math.min(6, Number(props.level)));
|
|
19
|
-
const newEl =
|
|
18
|
+
const newEl = document.createElement(`h${level}`);
|
|
20
19
|
newEl.dataset.hypenType = "heading";
|
|
21
20
|
|
|
22
21
|
// Copy content and attributes
|
|
@@ -32,8 +31,7 @@ export const headingHandler: ComponentHandler = {
|
|
|
32
31
|
}
|
|
33
32
|
|
|
34
33
|
// Text content
|
|
35
|
-
|
|
36
|
-
const text = props["0"] ?? props.text;
|
|
34
|
+
const text = props["0"] || props.text;
|
|
37
35
|
if (text !== undefined) {
|
|
38
36
|
el.textContent = String(text);
|
|
39
37
|
}
|