@hypen-space/web 0.2.12 → 0.3.0

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 (39) hide show
  1. package/dist/src/dom/applicators/effects.js +38 -2
  2. package/dist/src/dom/applicators/effects.js.map +3 -3
  3. package/dist/src/dom/applicators/events.js +280 -397
  4. package/dist/src/dom/applicators/events.js.map +5 -4
  5. package/dist/src/dom/applicators/font.js +94 -5
  6. package/dist/src/dom/applicators/font.js.map +3 -3
  7. package/dist/src/dom/applicators/index.js +590 -425
  8. package/dist/src/dom/applicators/index.js.map +10 -9
  9. package/dist/src/dom/applicators/layout.js +33 -5
  10. package/dist/src/dom/applicators/layout.js.map +3 -3
  11. package/dist/src/dom/applicators/size.js +81 -16
  12. package/dist/src/dom/applicators/size.js.map +3 -3
  13. package/dist/src/dom/components/hypenapp.js +296 -0
  14. package/dist/src/dom/components/hypenapp.js.map +10 -0
  15. package/dist/src/dom/components/index.js +263 -1
  16. package/dist/src/dom/components/index.js.map +5 -4
  17. package/dist/src/dom/element-data.js +140 -0
  18. package/dist/src/dom/element-data.js.map +10 -0
  19. package/dist/src/dom/index.js +857 -430
  20. package/dist/src/dom/index.js.map +13 -11
  21. package/dist/src/dom/renderer.js +857 -430
  22. package/dist/src/dom/renderer.js.map +13 -11
  23. package/dist/src/hypen.js +857 -430
  24. package/dist/src/hypen.js.map +13 -11
  25. package/dist/src/index.js +862 -430
  26. package/dist/src/index.js.map +15 -12
  27. package/package.json +3 -3
  28. package/src/canvas/QUICKSTART.md +2 -4
  29. package/src/dom/applicators/effects.ts +45 -1
  30. package/src/dom/applicators/events.ts +348 -537
  31. package/src/dom/applicators/font.ts +127 -2
  32. package/src/dom/applicators/index.ts +117 -7
  33. package/src/dom/applicators/layout.ts +40 -4
  34. package/src/dom/applicators/size.ts +101 -16
  35. package/src/dom/components/hypenapp.ts +348 -0
  36. package/src/dom/components/index.ts +2 -0
  37. package/src/dom/element-data.ts +234 -0
  38. package/src/dom/renderer.ts +8 -5
  39. package/src/index.ts +3 -0
@@ -973,6 +973,266 @@ var init_route = __esm(() => {
973
973
  };
974
974
  });
975
975
 
976
+ // src/dom/components/hypenapp.ts
977
+ var exports_hypenapp = {};
978
+ __export(exports_hypenapp, {
979
+ hypenAppHandler: () => hypenAppHandler,
980
+ disconnectHypenApp: () => disconnectHypenApp
981
+ });
982
+ import { RemoteEngine } from "@hypen-space/core/remote/client";
983
+ function applyPatches(container, nodes, patches, engine, onRoot) {
984
+ for (const patch of patches) {
985
+ switch (patch.type) {
986
+ case "create": {
987
+ const el = createElement(patch.element_type, patch.props || {});
988
+ el.dataset.hypenId = patch.id;
989
+ el.__hypenEngine = engine;
990
+ nodes.set(patch.id, el);
991
+ break;
992
+ }
993
+ case "setProp": {
994
+ const el = nodes.get(patch.id);
995
+ if (el) {
996
+ applyProp(el, patch.name, patch.value);
997
+ }
998
+ break;
999
+ }
1000
+ case "setText": {
1001
+ const el = nodes.get(patch.id);
1002
+ if (el) {
1003
+ el.textContent = patch.text;
1004
+ }
1005
+ break;
1006
+ }
1007
+ case "insert": {
1008
+ const parentId = patch.parent_id;
1009
+ const parent = parentId === "root" ? container : nodes.get(parentId);
1010
+ const child = nodes.get(patch.id);
1011
+ const beforeId = patch.before_id;
1012
+ if (parent && child) {
1013
+ if (parentId === "root") {
1014
+ onRoot(patch.id);
1015
+ }
1016
+ if (beforeId) {
1017
+ const before = nodes.get(beforeId);
1018
+ if (before && before.parentNode === parent) {
1019
+ parent.insertBefore(child, before);
1020
+ } else if (!parent.contains(child)) {
1021
+ parent.appendChild(child);
1022
+ }
1023
+ } else if (!parent.contains(child)) {
1024
+ parent.appendChild(child);
1025
+ }
1026
+ }
1027
+ break;
1028
+ }
1029
+ case "move": {
1030
+ const parentId = patch.parent_id;
1031
+ const parent = parentId === "root" ? container : nodes.get(parentId);
1032
+ const child = nodes.get(patch.id);
1033
+ const beforeId = patch.before_id;
1034
+ if (parent && child) {
1035
+ if (beforeId) {
1036
+ const before = nodes.get(beforeId);
1037
+ if (before && before.parentNode === parent) {
1038
+ parent.insertBefore(child, before);
1039
+ }
1040
+ } else {
1041
+ parent.appendChild(child);
1042
+ }
1043
+ }
1044
+ break;
1045
+ }
1046
+ case "remove": {
1047
+ const el = nodes.get(patch.id);
1048
+ if (el && el.parentNode) {
1049
+ el.parentNode.removeChild(el);
1050
+ }
1051
+ nodes.delete(patch.id);
1052
+ break;
1053
+ }
1054
+ }
1055
+ }
1056
+ }
1057
+ function createElement(type, props) {
1058
+ const normalizedType = type.toLowerCase();
1059
+ const tagMap = {
1060
+ column: "div",
1061
+ row: "div",
1062
+ text: "span",
1063
+ button: "button",
1064
+ input: "input",
1065
+ image: "img",
1066
+ container: "div",
1067
+ box: "div",
1068
+ center: "div",
1069
+ list: "div",
1070
+ spacer: "div",
1071
+ stack: "div",
1072
+ divider: "hr",
1073
+ grid: "div",
1074
+ card: "div",
1075
+ heading: "h2",
1076
+ link: "a",
1077
+ textarea: "textarea",
1078
+ checkbox: "input",
1079
+ select: "select",
1080
+ slider: "input",
1081
+ switch: "input",
1082
+ spinner: "div",
1083
+ badge: "span",
1084
+ avatar: "img",
1085
+ progressbar: "div",
1086
+ video: "video",
1087
+ audio: "audio"
1088
+ };
1089
+ const tag = tagMap[normalizedType] || "div";
1090
+ const el = document.createElement(tag);
1091
+ el.dataset.hypenType = normalizedType;
1092
+ if (normalizedType === "column") {
1093
+ el.style.display = "flex";
1094
+ el.style.flexDirection = "column";
1095
+ } else if (normalizedType === "row") {
1096
+ el.style.display = "flex";
1097
+ el.style.flexDirection = "row";
1098
+ } else if (normalizedType === "center") {
1099
+ el.style.display = "flex";
1100
+ el.style.alignItems = "center";
1101
+ el.style.justifyContent = "center";
1102
+ } else if (normalizedType === "text") {
1103
+ if (props["0"]) {
1104
+ el.textContent = String(props["0"]);
1105
+ }
1106
+ } else if (normalizedType === "button") {
1107
+ el.style.cursor = "pointer";
1108
+ } else if (normalizedType === "checkbox" || normalizedType === "switch") {
1109
+ el.type = "checkbox";
1110
+ } else if (normalizedType === "slider") {
1111
+ el.type = "range";
1112
+ }
1113
+ return el;
1114
+ }
1115
+ function applyProp(el, name, value) {
1116
+ if (name === "0" || name === "text") {
1117
+ el.textContent = String(value);
1118
+ return;
1119
+ }
1120
+ const styleProps = {
1121
+ padding: "padding",
1122
+ margin: "margin",
1123
+ backgroundColor: "backgroundColor",
1124
+ background: "background",
1125
+ color: "color",
1126
+ fontSize: "fontSize",
1127
+ fontWeight: "fontWeight",
1128
+ width: "width",
1129
+ height: "height",
1130
+ minWidth: "minWidth",
1131
+ minHeight: "minHeight",
1132
+ maxWidth: "maxWidth",
1133
+ maxHeight: "maxHeight",
1134
+ borderRadius: "borderRadius",
1135
+ border: "border",
1136
+ gap: "gap",
1137
+ flex: "flex",
1138
+ alignItems: "alignItems",
1139
+ justifyContent: "justifyContent",
1140
+ opacity: "opacity",
1141
+ overflow: "overflow"
1142
+ };
1143
+ if (styleProps[name]) {
1144
+ const cssValue = typeof value === "number" ? `${value}px` : String(value);
1145
+ el.style[styleProps[name]] = cssValue;
1146
+ return;
1147
+ }
1148
+ if (name === "onClick" || name === "onclick") {
1149
+ el.onclick = () => {
1150
+ const engine = el.__hypenEngine;
1151
+ if (engine && typeof value === "string" && value.startsWith("@actions.")) {
1152
+ const action = value.replace("@actions.", "");
1153
+ engine.dispatchAction(action);
1154
+ }
1155
+ };
1156
+ return;
1157
+ }
1158
+ el.setAttribute(name, String(value));
1159
+ }
1160
+ function disconnectHypenApp(element) {
1161
+ const instance = activeInstances.get(element);
1162
+ if (instance) {
1163
+ instance.engine.disconnect();
1164
+ activeInstances.delete(element);
1165
+ }
1166
+ }
1167
+ var activeInstances, hypenAppHandler;
1168
+ var init_hypenapp = __esm(() => {
1169
+ activeInstances = new WeakMap;
1170
+ hypenAppHandler = {
1171
+ create() {
1172
+ const el = document.createElement("div");
1173
+ el.dataset.hypenType = "hypenapp";
1174
+ el.style.display = "contents";
1175
+ return el;
1176
+ },
1177
+ applyProps(element, props) {
1178
+ const url = props["0"] || props.url;
1179
+ if (!url || typeof url !== "string") {
1180
+ console.error("[HypenApp] URL is required");
1181
+ element.innerHTML = '<div style="color: red;">HypenApp: URL required</div>';
1182
+ return;
1183
+ }
1184
+ const existing = activeInstances.get(element);
1185
+ if (existing) {
1186
+ return;
1187
+ }
1188
+ const engine = new RemoteEngine(url, {
1189
+ autoReconnect: props.autoReconnect ?? true,
1190
+ reconnectInterval: props.reconnectInterval ?? 3000,
1191
+ maxReconnectAttempts: props.maxReconnectAttempts ?? 10
1192
+ });
1193
+ const nodes = new Map;
1194
+ let rootId = null;
1195
+ activeInstances.set(element, { engine, nodes });
1196
+ engine.onPatches((patches) => {
1197
+ applyPatches(element, nodes, patches, engine, (id) => {
1198
+ if (!rootId)
1199
+ rootId = id;
1200
+ });
1201
+ });
1202
+ element.innerHTML = '<div class="hypen-app-loading">Connecting...</div>';
1203
+ engine.connect().then(() => {
1204
+ element.innerHTML = "";
1205
+ console.log(`[HypenApp] Connected to ${url}`);
1206
+ }).catch((error) => {
1207
+ element.innerHTML = `<div style="color: red;">HypenApp: Connection failed - ${error.message}</div>`;
1208
+ console.error("[HypenApp] Connection failed:", error);
1209
+ });
1210
+ engine.onDisconnect(() => {
1211
+ console.log("[HypenApp] Disconnected");
1212
+ });
1213
+ engine.onError((error) => {
1214
+ console.error("[HypenApp] Error:", error);
1215
+ });
1216
+ const observer = new MutationObserver((mutations) => {
1217
+ for (const mutation of mutations) {
1218
+ for (const removedNode of mutation.removedNodes) {
1219
+ if (removedNode === element || removedNode.contains?.(element)) {
1220
+ engine.disconnect();
1221
+ activeInstances.delete(element);
1222
+ observer.disconnect();
1223
+ console.log("[HypenApp] Cleaned up");
1224
+ return;
1225
+ }
1226
+ }
1227
+ }
1228
+ });
1229
+ if (element.parentNode) {
1230
+ observer.observe(element.parentNode, { childList: true, subtree: true });
1231
+ }
1232
+ }
1233
+ };
1234
+ });
1235
+
976
1236
  // src/dom/applicators/padding.ts
977
1237
  var exports_padding = {};
978
1238
  __export(exports_padding, {
@@ -1116,43 +1376,108 @@ var exports_size = {};
1116
1376
  __export(exports_size, {
1117
1377
  sizeHandlers: () => sizeHandlers
1118
1378
  });
1379
+ function parseSizeValue(value) {
1380
+ if (value === null || value === undefined)
1381
+ return null;
1382
+ if (typeof value === "number") {
1383
+ return `${value}px`;
1384
+ }
1385
+ const str = String(value).trim().toLowerCase();
1386
+ switch (str) {
1387
+ case "fill":
1388
+ case "match_parent":
1389
+ return "100%";
1390
+ case "wrap":
1391
+ case "wrap_content":
1392
+ case "auto":
1393
+ return "auto";
1394
+ case "infinity":
1395
+ case "inf":
1396
+ case "max":
1397
+ return "100%";
1398
+ }
1399
+ const match = str.match(/^(-?[\d.]+)\s*(px|dp|pt|%|vw|vh|vmin|vmax|em|rem)?$/);
1400
+ if (!match) {
1401
+ return str;
1402
+ }
1403
+ const num = parseFloat(match[1]);
1404
+ const unit = match[2] || "px";
1405
+ switch (unit) {
1406
+ case "px":
1407
+ return `${num}px`;
1408
+ case "dp":
1409
+ case "pt":
1410
+ return `${num}px`;
1411
+ case "%":
1412
+ return `${num}%`;
1413
+ case "vw":
1414
+ return `${num}vw`;
1415
+ case "vh":
1416
+ return `${num}vh`;
1417
+ case "vmin":
1418
+ return `${num}vmin`;
1419
+ case "vmax":
1420
+ return `${num}vmax`;
1421
+ case "em":
1422
+ return `${num}em`;
1423
+ case "rem":
1424
+ return `${num}rem`;
1425
+ default:
1426
+ return `${num}px`;
1427
+ }
1428
+ }
1119
1429
  var sizeHandlers;
1120
1430
  var init_size = __esm(() => {
1121
1431
  sizeHandlers = {
1122
1432
  width: (el, value) => {
1123
- el.style.width = typeof value === "number" ? `${value}px` : String(value);
1433
+ const size = parseSizeValue(value);
1434
+ if (size)
1435
+ el.style.width = size;
1124
1436
  },
1125
1437
  height: (el, value) => {
1126
- el.style.height = typeof value === "number" ? `${value}px` : String(value);
1438
+ const size = parseSizeValue(value);
1439
+ if (size)
1440
+ el.style.height = size;
1127
1441
  },
1128
1442
  minWidth: (el, value) => {
1129
- el.style.minWidth = typeof value === "number" ? `${value}px` : String(value);
1443
+ const size = parseSizeValue(value);
1444
+ if (size)
1445
+ el.style.minWidth = size;
1130
1446
  },
1131
1447
  minHeight: (el, value) => {
1132
- el.style.minHeight = typeof value === "number" ? `${value}px` : String(value);
1448
+ const size = parseSizeValue(value);
1449
+ if (size)
1450
+ el.style.minHeight = size;
1133
1451
  },
1134
1452
  maxWidth: (el, value) => {
1135
- el.style.maxWidth = typeof value === "number" ? `${value}px` : String(value);
1453
+ const size = parseSizeValue(value);
1454
+ if (size)
1455
+ el.style.maxWidth = size;
1136
1456
  },
1137
1457
  maxHeight: (el, value) => {
1138
- el.style.maxHeight = typeof value === "number" ? `${value}px` : String(value);
1458
+ const size = parseSizeValue(value);
1459
+ if (size)
1460
+ el.style.maxHeight = size;
1139
1461
  },
1140
1462
  size: (el, value) => {
1141
- if (typeof value === "number") {
1142
- el.style.width = `${value}px`;
1143
- el.style.height = `${value}px`;
1144
- } else if (typeof value === "object" && value !== null) {
1463
+ if (typeof value === "object" && value !== null) {
1145
1464
  const obj = value;
1146
1465
  if (obj.width !== undefined) {
1147
- el.style.width = typeof obj.width === "number" ? `${obj.width}px` : String(obj.width);
1466
+ const w = parseSizeValue(obj.width);
1467
+ if (w)
1468
+ el.style.width = w;
1148
1469
  }
1149
1470
  if (obj.height !== undefined) {
1150
- el.style.height = typeof obj.height === "number" ? `${obj.height}px` : String(obj.height);
1471
+ const h = parseSizeValue(obj.height);
1472
+ if (h)
1473
+ el.style.height = h;
1151
1474
  }
1152
1475
  } else {
1153
- const size = String(value);
1154
- el.style.width = size;
1155
- el.style.height = size;
1476
+ const size = parseSizeValue(value);
1477
+ if (size) {
1478
+ el.style.width = size;
1479
+ el.style.height = size;
1480
+ }
1156
1481
  }
1157
1482
  },
1158
1483
  fillMaxWidth: (el, value) => {
@@ -1180,10 +1505,64 @@ var init_size = __esm(() => {
1180
1505
  // src/dom/applicators/font.ts
1181
1506
  var exports_font = {};
1182
1507
  __export(exports_font, {
1183
- fontHandlers: () => fontHandlers
1508
+ fontHandlers: () => fontHandlers,
1509
+ GoogleFonts: () => GoogleFonts
1184
1510
  });
1185
- var fontHandlers;
1511
+ function isSystemFont(fontName) {
1512
+ const normalized = fontName.toLowerCase().trim();
1513
+ return systemFontKeywords.has(normalized) || normalized.startsWith("-") || normalized.startsWith("ui-");
1514
+ }
1515
+ function loadGoogleFont(fontName) {
1516
+ const normalized = fontName.trim();
1517
+ if (loadedGoogleFonts.has(normalized) || isSystemFont(normalized)) {
1518
+ return;
1519
+ }
1520
+ loadedGoogleFonts.add(normalized);
1521
+ const link = document.createElement("link");
1522
+ link.rel = "stylesheet";
1523
+ link.href = `https://fonts.googleapis.com/css2?family=${encodeURIComponent(normalized)}:wght@100;200;300;400;500;600;700;800;900&display=swap`;
1524
+ document.head.appendChild(link);
1525
+ }
1526
+ function processFontFamily(value) {
1527
+ const fonts = value.split(",").map((f) => f.trim().replace(/["']/g, ""));
1528
+ for (const font of fonts) {
1529
+ if (!isSystemFont(font)) {
1530
+ loadGoogleFont(font);
1531
+ }
1532
+ }
1533
+ return fonts.map((f) => {
1534
+ if (f.includes(" ") && !f.startsWith('"') && !f.startsWith("'")) {
1535
+ return `"${f}"`;
1536
+ }
1537
+ return f;
1538
+ }).join(", ");
1539
+ }
1540
+ var loadedGoogleFonts, systemFontKeywords, fontHandlers, GoogleFonts;
1186
1541
  var init_font = __esm(() => {
1542
+ loadedGoogleFonts = new Set;
1543
+ systemFontKeywords = new Set([
1544
+ "default",
1545
+ "system",
1546
+ "system-ui",
1547
+ "inherit",
1548
+ "initial",
1549
+ "unset",
1550
+ "serif",
1551
+ "sans-serif",
1552
+ "monospace",
1553
+ "cursive",
1554
+ "fantasy",
1555
+ "-apple-system",
1556
+ "BlinkMacSystemFont",
1557
+ "Segoe UI",
1558
+ "Arial",
1559
+ "Helvetica",
1560
+ "Times New Roman",
1561
+ "Georgia",
1562
+ "Courier New",
1563
+ "Verdana",
1564
+ "Tahoma"
1565
+ ]);
1187
1566
  fontHandlers = {
1188
1567
  fontSize: (el, value) => {
1189
1568
  el.style.fontSize = typeof value === "number" ? `${value}px` : String(value);
@@ -1192,15 +1571,49 @@ var init_font = __esm(() => {
1192
1571
  el.style.fontWeight = String(value);
1193
1572
  },
1194
1573
  fontFamily: (el, value) => {
1195
- el.style.fontFamily = String(value);
1574
+ const fontValue = String(value);
1575
+ el.style.fontFamily = processFontFamily(fontValue);
1196
1576
  },
1197
1577
  textAlign: (el, value) => {
1198
1578
  el.style.textAlign = String(value);
1199
1579
  },
1200
1580
  lineHeight: (el, value) => {
1201
1581
  el.style.lineHeight = String(value);
1582
+ },
1583
+ fontStyle: (el, value) => {
1584
+ el.style.fontStyle = String(value);
1585
+ },
1586
+ textTransform: (el, value) => {
1587
+ el.style.textTransform = String(value);
1202
1588
  }
1203
1589
  };
1590
+ GoogleFonts = {
1591
+ preload: loadGoogleFont,
1592
+ isLoaded: (fontName) => loadedGoogleFonts.has(fontName.trim()),
1593
+ getLoadedFonts: () => Array.from(loadedGoogleFonts),
1594
+ popular: [
1595
+ "Roboto",
1596
+ "Open Sans",
1597
+ "Lato",
1598
+ "Montserrat",
1599
+ "Poppins",
1600
+ "Inter",
1601
+ "Nunito",
1602
+ "Playfair Display",
1603
+ "Merriweather",
1604
+ "Source Code Pro",
1605
+ "Fira Code",
1606
+ "JetBrains Mono",
1607
+ "Raleway",
1608
+ "Ubuntu",
1609
+ "Oswald",
1610
+ "Quicksand",
1611
+ "Work Sans",
1612
+ "Rubik",
1613
+ "Karla",
1614
+ "DM Sans"
1615
+ ]
1616
+ };
1204
1617
  });
1205
1618
 
1206
1619
  // src/dom/applicators/layout.ts
@@ -1208,11 +1621,39 @@ var exports_layout = {};
1208
1621
  __export(exports_layout, {
1209
1622
  layoutHandlers: () => layoutHandlers
1210
1623
  });
1624
+ function mapAlignmentValue(value) {
1625
+ const v = String(value).toLowerCase();
1626
+ switch (v) {
1627
+ case "top":
1628
+ case "start":
1629
+ case "leading":
1630
+ case "left":
1631
+ return "flex-start";
1632
+ case "bottom":
1633
+ case "end":
1634
+ case "trailing":
1635
+ case "right":
1636
+ return "flex-end";
1637
+ case "center":
1638
+ return "center";
1639
+ case "spacebetween":
1640
+ case "space-between":
1641
+ return "space-between";
1642
+ case "spacearound":
1643
+ case "space-around":
1644
+ return "space-around";
1645
+ case "spaceevenly":
1646
+ case "space-evenly":
1647
+ return "space-evenly";
1648
+ default:
1649
+ return v;
1650
+ }
1651
+ }
1211
1652
  var layoutHandlers;
1212
1653
  var init_layout = __esm(() => {
1213
1654
  layoutHandlers = {
1214
1655
  verticalAlignment: (el, value) => {
1215
- const val = String(value);
1656
+ const val = mapAlignmentValue(String(value));
1216
1657
  const flexDirection = getComputedStyle(el).flexDirection;
1217
1658
  if (flexDirection === "column" || flexDirection === "column-reverse") {
1218
1659
  el.style.justifyContent = val;
@@ -1221,7 +1662,7 @@ var init_layout = __esm(() => {
1221
1662
  }
1222
1663
  },
1223
1664
  horizontalAlignment: (el, value) => {
1224
- const val = String(value);
1665
+ const val = mapAlignmentValue(String(value));
1225
1666
  const flexDirection = getComputedStyle(el).flexDirection;
1226
1667
  if (flexDirection === "column" || flexDirection === "column-reverse") {
1227
1668
  el.style.alignItems = val;
@@ -1230,10 +1671,10 @@ var init_layout = __esm(() => {
1230
1671
  }
1231
1672
  },
1232
1673
  horizontalAlign: (el, value) => {
1233
- el.style.justifyContent = String(value);
1674
+ el.style.justifyContent = mapAlignmentValue(String(value));
1234
1675
  },
1235
1676
  verticalAlign: (el, value) => {
1236
- el.style.alignItems = String(value);
1677
+ el.style.alignItems = mapAlignmentValue(String(value));
1237
1678
  },
1238
1679
  gap: (el, value) => {
1239
1680
  el.style.gap = typeof value === "number" ? `${value}px` : String(value);
@@ -1276,11 +1717,105 @@ var init_layout = __esm(() => {
1276
1717
  };
1277
1718
  });
1278
1719
 
1720
+ // src/dom/element-data.ts
1721
+ import { getElementDisposables } from "@hypen/core";
1722
+ function getHypenData(element) {
1723
+ let data = elementDataMap.get(element);
1724
+ if (!data) {
1725
+ data = {};
1726
+ elementDataMap.set(element, data);
1727
+ }
1728
+ return data;
1729
+ }
1730
+ function hasHypenData(element) {
1731
+ return elementDataMap.has(element);
1732
+ }
1733
+ function clearHypenData(element) {
1734
+ elementDataMap.delete(element);
1735
+ }
1736
+ function getEngine(element) {
1737
+ return getHypenData(element).engine;
1738
+ }
1739
+ function setEngine(element, engine) {
1740
+ getHypenData(element).engine = engine;
1741
+ }
1742
+ function findEngine(element) {
1743
+ let current = element;
1744
+ while (current) {
1745
+ const engine = getEngine(current);
1746
+ if (engine)
1747
+ return engine;
1748
+ current = current.parentElement;
1749
+ }
1750
+ return;
1751
+ }
1752
+ function getRegisteredEvents(element) {
1753
+ const data = getHypenData(element);
1754
+ if (!data.registeredEvents) {
1755
+ data.registeredEvents = new Set;
1756
+ }
1757
+ return data.registeredEvents;
1758
+ }
1759
+ function isEventRegistered(element, eventKey) {
1760
+ return getRegisteredEvents(element).has(eventKey);
1761
+ }
1762
+ function registerEvent(element, eventKey) {
1763
+ getRegisteredEvents(element).add(eventKey);
1764
+ }
1765
+ function unregisterEvent(element, eventKey) {
1766
+ getRegisteredEvents(element).delete(eventKey);
1767
+ }
1768
+ function getKeyTarget(element) {
1769
+ return getHypenData(element).keyTarget;
1770
+ }
1771
+ function setKeyTarget(element, key) {
1772
+ getHypenData(element).keyTarget = key;
1773
+ }
1774
+ function getMeta(element, key) {
1775
+ return getHypenData(element).meta?.[key];
1776
+ }
1777
+ function setMeta(element, key, value) {
1778
+ const data = getHypenData(element);
1779
+ if (!data.meta) {
1780
+ data.meta = {};
1781
+ }
1782
+ data.meta[key] = value;
1783
+ }
1784
+ function disposeHypenElement(element) {
1785
+ try {
1786
+ const disposables = getElementDisposables(element);
1787
+ disposables.dispose();
1788
+ } catch {}
1789
+ clearHypenData(element);
1790
+ }
1791
+ function getLegacyEngine(element) {
1792
+ const engine = getEngine(element);
1793
+ if (engine)
1794
+ return engine;
1795
+ return element[HYPEN_ENGINE_SYMBOL] ?? element.__hypenEngine;
1796
+ }
1797
+ function setLegacyEngine(element, engine) {
1798
+ setEngine(element, engine);
1799
+ element.__hypenEngine = engine;
1800
+ }
1801
+ var elementDataMap, HYPEN_ENGINE_SYMBOL, REGISTERED_EVENTS_SYMBOL, KEY_TARGET_SYMBOL;
1802
+ var init_element_data = __esm(() => {
1803
+ elementDataMap = new WeakMap;
1804
+ HYPEN_ENGINE_SYMBOL = Symbol.for("hypen.engine");
1805
+ REGISTERED_EVENTS_SYMBOL = Symbol.for("hypen.registeredEvents");
1806
+ KEY_TARGET_SYMBOL = Symbol.for("hypen.keyTarget");
1807
+ });
1808
+
1279
1809
  // src/dom/applicators/events.ts
1280
1810
  var exports_events = {};
1281
1811
  __export(exports_events, {
1282
1812
  eventHandlers: () => eventHandlers
1283
1813
  });
1814
+ import {
1815
+ getElementDisposables as getElementDisposables2,
1816
+ disposableListener,
1817
+ disposableTimeout
1818
+ } from "@hypen/core";
1284
1819
  function toPlainObject(value) {
1285
1820
  if (value instanceof Map) {
1286
1821
  const obj = {};
@@ -1326,7 +1861,13 @@ function extractActionDetails(value) {
1326
1861
  }
1327
1862
  for (const [key, val] of Object.entries(plain)) {
1328
1863
  if (key !== "0") {
1329
- payload[key] = val;
1864
+ if (/^\d+$/.test(key) && val && typeof val === "object" && !Array.isArray(val)) {
1865
+ for (const [innerKey, innerVal] of Object.entries(val)) {
1866
+ payload[innerKey] = innerVal;
1867
+ }
1868
+ } else {
1869
+ payload[key] = val;
1870
+ }
1330
1871
  }
1331
1872
  }
1332
1873
  }
@@ -1364,420 +1905,203 @@ function extractEventData(event, element) {
1364
1905
  }
1365
1906
  return data;
1366
1907
  }
1367
- var eventHandlers;
1368
- var init_events = __esm(() => {
1369
- eventHandlers = {
1370
- onClick: (element, value) => {
1371
- console.log(`[EventApplicator] onClick called with value:`, value);
1372
- const { actionName, payload: customPayload } = extractActionDetails(value);
1373
- if (!actionName) {
1374
- console.warn(`[EventApplicator] onClick value must be an action reference, got:`, value);
1375
- return;
1376
- }
1377
- const existingListener = element.__hypenClickListener;
1378
- if (existingListener) {
1379
- element.removeEventListener("click", existingListener);
1380
- }
1381
- const listener = (event) => {
1382
- console.log(`\uD83D\uDD25 [EventApplicator] onClick fired, dispatching action: ${actionName}`);
1383
- const payload = Object.keys(customPayload).length > 0 ? { ...customPayload } : extractEventData(event, element);
1384
- console.log(`[EventApplicator] onClick payload:`, payload);
1385
- const engine = element.__hypenEngine;
1386
- if (engine) {
1387
- engine.dispatchAction(actionName, payload);
1388
- } else {
1389
- console.warn(`[EventApplicator] No engine attached to element for onClick`);
1390
- }
1391
- };
1392
- element.__hypenClickListener = listener;
1393
- element.addEventListener("click", listener);
1394
- console.log(`[EventApplicator] onClick handler attached for action: ${actionName}`);
1395
- },
1396
- onPress: (element, value) => {
1397
- eventHandlers.onClick(element, value);
1398
- },
1399
- onChange: (element, value) => {
1400
- const { actionName } = extractActionDetails(value);
1401
- if (!actionName) {
1402
- console.warn(`[EventApplicator] onChange value must be an action reference starting with @, got:`, value);
1403
- return;
1404
- }
1405
- const existingListener = element.__hypenChangeListener;
1406
- if (existingListener) {
1407
- element.removeEventListener("change", existingListener);
1408
- }
1409
- const listener = (event) => {
1410
- console.log(`\uD83D\uDD25 [EventApplicator] onChange fired, dispatching action: ${actionName}`);
1411
- const payload = extractEventData(event, element);
1412
- const engine = element.__hypenEngine;
1413
- if (engine) {
1414
- engine.dispatchAction(actionName, payload);
1415
- } else {
1416
- console.warn(`[EventApplicator] No engine attached to element for onChange`);
1417
- }
1418
- };
1419
- element.__hypenChangeListener = listener;
1420
- element.addEventListener("change", listener);
1421
- console.log(`[EventApplicator] onChange handler attached for action: ${actionName}`);
1422
- },
1423
- onSubmit: (element, value) => {
1424
- const { actionName } = extractActionDetails(value);
1425
- if (!actionName) {
1426
- console.warn(`[EventApplicator] onSubmit value must be an action reference starting with @, got:`, value);
1908
+ function createEventHandler(eventType, options = {}) {
1909
+ return (element, value) => {
1910
+ const { actionName, payload: customPayload } = extractActionDetails(value);
1911
+ if (!actionName) {
1912
+ console.warn(`[EventApplicator] ${eventType} requires an action reference starting with @, got:`, value);
1913
+ return;
1914
+ }
1915
+ const disposables = getElementDisposables2(element);
1916
+ const eventKey = `${eventType}:${actionName}`;
1917
+ if (getRegisteredEvents(element).has(eventKey)) {
1918
+ return;
1919
+ }
1920
+ registerEvent(element, eventKey);
1921
+ let throttleTimer = null;
1922
+ const listener = (event) => {
1923
+ if (options.throttleMs && throttleTimer) {
1427
1924
  return;
1428
1925
  }
1429
- const existingListener = element.__hypenSubmitListener;
1430
- if (existingListener) {
1431
- element.removeEventListener("submit", existingListener);
1926
+ if (options.throttleMs) {
1927
+ throttleTimer = disposableTimeout(() => {
1928
+ throttleTimer = null;
1929
+ }, options.throttleMs);
1432
1930
  }
1433
- const listener = (event) => {
1434
- console.log(`\uD83D\uDD25 [EventApplicator] onSubmit fired, dispatching action: ${actionName}`);
1931
+ if (options.preventDefault) {
1435
1932
  event.preventDefault();
1436
- const payload = extractEventData(event, element);
1437
- const engine = element.__hypenEngine;
1438
- if (engine) {
1439
- engine.dispatchAction(actionName, payload);
1440
- } else {
1441
- console.warn(`[EventApplicator] No engine attached to element for onSubmit`);
1442
- }
1443
- };
1444
- element.__hypenSubmitListener = listener;
1445
- element.addEventListener("submit", listener);
1446
- console.log(`[EventApplicator] onSubmit handler attached for action: ${actionName}`);
1447
- },
1448
- onInput: (element, value) => {
1449
- console.log(`[EventApplicator] onInput called with value:`, value);
1450
- const { actionName } = extractActionDetails(value);
1451
- if (!actionName) {
1452
- console.warn(`[EventApplicator] onInput value must be an action reference starting with @, got:`, value);
1453
- return;
1454
- }
1455
- const existingListener = element.__hypenInputListener;
1456
- if (existingListener) {
1457
- element.removeEventListener("input", existingListener);
1458
- }
1459
- const listener = (event) => {
1460
- console.log(`\uD83D\uDD25 [EventApplicator] onInput fired, dispatching action: ${actionName}`);
1461
- const target = event.target;
1462
- const payload = {
1463
- type: event.type,
1464
- timestamp: Date.now(),
1465
- value: target.value,
1466
- input: target.value
1467
- };
1468
- console.log(`[EventApplicator] onInput payload:`, payload);
1469
- const engine = element.__hypenEngine;
1470
- if (engine) {
1471
- engine.dispatchAction(actionName, payload);
1472
- } else {
1473
- console.warn(`[EventApplicator] No engine attached to element for onInput`);
1474
- }
1475
- };
1476
- element.__hypenInputListener = listener;
1477
- element.addEventListener("input", listener);
1478
- console.log(`[EventApplicator] onInput handler attached for action: ${actionName}`);
1479
- },
1480
- onKey: (element, value) => {
1481
- console.log(`[EventApplicator] onKey called with value:`, value);
1482
- const { actionName } = extractActionDetails(value);
1483
- if (actionName) {
1484
- const existingListener = element.__hypenKeyListener;
1485
- if (existingListener) {
1486
- element.removeEventListener("keydown", existingListener);
1487
- }
1488
- const listener = (event) => {
1489
- if (event.key === "Enter") {
1490
- console.log(`\uD83D\uDD25 [EventApplicator] onKey fired (Enter), dispatching action: ${actionName}`);
1491
- event.preventDefault();
1492
- const target = event.target;
1493
- const payload = {
1494
- type: event.type,
1495
- timestamp: Date.now(),
1496
- key: event.key,
1497
- code: event.code,
1498
- value: target.value,
1499
- input: target.value,
1500
- ctrlKey: event.ctrlKey,
1501
- shiftKey: event.shiftKey,
1502
- altKey: event.altKey,
1503
- metaKey: event.metaKey
1504
- };
1505
- const engine = element.__hypenEngine;
1506
- if (engine) {
1507
- engine.dispatchAction(actionName, payload);
1508
- } else {
1509
- console.warn(`[EventApplicator] No engine attached to element for onKey`);
1510
- }
1511
- }
1512
- };
1513
- element.__hypenKeyListener = listener;
1514
- element.addEventListener("keydown", listener);
1515
- console.log(`[EventApplicator] onKey handler attached for action: ${actionName} (triggers on Enter)`);
1516
- } else {
1517
- console.warn(`[EventApplicator] onKey value must be an action reference starting with @, got: ${value}`);
1518
- }
1519
- },
1520
- "onKey.key": (element, keyValue) => {
1521
- console.log(`[EventApplicator] onKey.key called with value:`, keyValue);
1522
- element.__hypenKeyTarget = keyValue;
1523
- },
1524
- "onKey.action": (element, value) => {
1525
- console.log(`[EventApplicator] onKey.action called with value:`, value);
1526
- const { actionName } = extractActionDetails(value);
1527
- if (actionName) {
1528
- const targetKey = element.__hypenKeyTarget || "Enter";
1529
- const existingListener = element.__hypenKeyListener;
1530
- if (existingListener) {
1531
- element.removeEventListener("keydown", existingListener);
1532
- }
1533
- const listener = (event) => {
1534
- const keyToMatch = targetKey.toLowerCase() === "return" ? "Enter" : targetKey;
1535
- if (event.key === keyToMatch) {
1536
- console.log(`\uD83D\uDD25 [EventApplicator] onKey fired (${keyToMatch}), dispatching action: ${actionName}`);
1537
- event.preventDefault();
1538
- const target = event.target;
1539
- const payload = {
1540
- type: event.type,
1541
- timestamp: Date.now(),
1542
- key: event.key,
1543
- code: event.code,
1544
- value: target.value,
1545
- input: target.value,
1546
- ctrlKey: event.ctrlKey,
1547
- shiftKey: event.shiftKey,
1548
- altKey: event.altKey,
1549
- metaKey: event.metaKey
1550
- };
1551
- const engine = element.__hypenEngine;
1552
- if (engine) {
1553
- engine.dispatchAction(actionName, payload);
1554
- }
1555
- }
1556
- };
1557
- element.__hypenKeyListener = listener;
1558
- element.addEventListener("keydown", listener);
1559
- console.log(`[EventApplicator] onKey handler attached for action: ${actionName} on key: ${targetKey}`);
1560
- }
1561
- },
1562
- onScroll: (element, value) => {
1563
- console.log(`[EventApplicator] onScroll called with value:`, value);
1564
- const { actionName } = extractActionDetails(value);
1565
- if (actionName) {
1566
- const existingListener = element.__hypenScrollListener;
1567
- if (existingListener) {
1568
- element.removeEventListener("scroll", existingListener);
1569
- }
1570
- let throttleTimer = null;
1571
- const listener = (event) => {
1572
- if (throttleTimer)
1573
- return;
1574
- throttleTimer = setTimeout(() => {
1575
- throttleTimer = null;
1576
- }, 100);
1577
- const target = event.target;
1578
- const scrollTop = target.scrollTop;
1579
- const scrollHeight = target.scrollHeight;
1580
- const clientHeight = target.clientHeight;
1581
- const scrollPercentage = scrollTop / (scrollHeight - clientHeight) * 100;
1582
- const nearBottom = scrollHeight - scrollTop - clientHeight < 100 || scrollPercentage > 90;
1583
- console.log(`\uD83D\uDD25 [EventApplicator] onScroll fired, scrollTop: ${scrollTop}, nearBottom: ${nearBottom}`);
1584
- const payload = {
1585
- type: "scroll",
1586
- timestamp: Date.now(),
1587
- scrollTop,
1588
- scrollLeft: target.scrollLeft,
1589
- scrollHeight,
1590
- scrollWidth: target.scrollWidth,
1591
- clientHeight,
1592
- clientWidth: target.clientWidth,
1593
- scrollPercentage: Math.round(scrollPercentage),
1594
- nearBottom,
1595
- atBottom: scrollHeight - scrollTop === clientHeight,
1596
- atTop: scrollTop === 0
1597
- };
1598
- const engine = element.__hypenEngine;
1599
- if (engine) {
1600
- engine.dispatchAction(actionName, payload);
1601
- } else {
1602
- console.warn(`[EventApplicator] No engine attached to element for onScroll`);
1603
- }
1604
- };
1605
- element.__hypenScrollListener = listener;
1606
- element.addEventListener("scroll", listener, { passive: true });
1607
- console.log(`[EventApplicator] onScroll handler attached for action: ${actionName}`);
1608
- } else {
1609
- console.warn(`[EventApplicator] onScroll value must be an action reference starting with @, got: ${value}`);
1610
1933
  }
1611
- },
1612
- onLongClick: (element, value) => {
1613
- console.log(`[EventApplicator] onLongClick called with value:`, value);
1614
- const { actionName, payload: customPayload } = extractActionDetails(value);
1615
- if (!actionName) {
1616
- console.warn(`[EventApplicator] onLongClick value must be an action reference, got:`, value);
1617
- return;
1934
+ const payload = Object.keys(customPayload).length > 0 ? { ...customPayload } : options.extractPayload ? options.extractPayload(event, element) : extractEventData(event, element);
1935
+ const engine = getEngine(element);
1936
+ if (engine) {
1937
+ engine.dispatchAction(actionName, payload);
1618
1938
  }
1619
- const existingDownListener = element.__hypenLongClickDownListener;
1620
- const existingUpListener = element.__hypenLongClickUpListener;
1621
- if (existingDownListener) {
1622
- element.removeEventListener("pointerdown", existingDownListener);
1623
- }
1624
- if (existingUpListener) {
1625
- element.removeEventListener("pointerup", existingUpListener);
1626
- element.removeEventListener("pointerleave", existingUpListener);
1627
- }
1628
- let longClickTimer = null;
1629
- const LONG_CLICK_THRESHOLD = 500;
1630
- const downListener = (event) => {
1631
- longClickTimer = setTimeout(() => {
1632
- console.log(`\uD83D\uDD25 [EventApplicator] onLongClick fired, dispatching action: ${actionName}`);
1633
- const payload = Object.keys(customPayload).length > 0 ? { ...customPayload } : {
1634
- type: "longclick",
1635
- timestamp: Date.now(),
1636
- clientX: event.clientX,
1637
- clientY: event.clientY
1638
- };
1639
- const engine = element.__hypenEngine;
1640
- if (engine) {
1641
- engine.dispatchAction(actionName, payload);
1642
- } else {
1643
- console.warn(`[EventApplicator] No engine attached to element for onLongClick`);
1644
- }
1645
- longClickTimer = null;
1646
- }, LONG_CLICK_THRESHOLD);
1647
- };
1648
- const upListener = () => {
1649
- if (longClickTimer) {
1650
- clearTimeout(longClickTimer);
1651
- longClickTimer = null;
1652
- }
1653
- };
1654
- element.__hypenLongClickDownListener = downListener;
1655
- element.__hypenLongClickUpListener = upListener;
1656
- element.addEventListener("pointerdown", downListener);
1657
- element.addEventListener("pointerup", upListener);
1658
- element.addEventListener("pointerleave", upListener);
1659
- console.log(`[EventApplicator] onLongClick handler attached for action: ${actionName}`);
1660
- },
1661
- onFocus: (element, value) => {
1662
- console.log(`[EventApplicator] onFocus called with value:`, value);
1663
- const { actionName, payload: customPayload } = extractActionDetails(value);
1664
- if (!actionName) {
1665
- console.warn(`[EventApplicator] onFocus value must be an action reference, got:`, value);
1666
- return;
1667
- }
1668
- const existingListener = element.__hypenFocusListener;
1669
- if (existingListener) {
1670
- element.removeEventListener("focus", existingListener);
1939
+ };
1940
+ disposables.add(disposableListener(element, eventType, listener, {
1941
+ passive: options.passive
1942
+ }));
1943
+ disposables.addCallback(() => {
1944
+ unregisterEvent(element, eventKey);
1945
+ if (throttleTimer) {
1946
+ throttleTimer.dispose();
1671
1947
  }
1672
- const listener = (event) => {
1673
- console.log(`\uD83D\uDD25 [EventApplicator] onFocus fired, dispatching action: ${actionName}`);
1674
- const target = event.target;
1675
- const payload = Object.keys(customPayload).length > 0 ? { ...customPayload } : {
1676
- type: "focus",
1677
- timestamp: Date.now(),
1678
- value: target.value ?? undefined
1679
- };
1680
- const engine = element.__hypenEngine;
1681
- if (engine) {
1682
- engine.dispatchAction(actionName, payload);
1683
- } else {
1684
- console.warn(`[EventApplicator] No engine attached to element for onFocus`);
1685
- }
1686
- };
1687
- element.__hypenFocusListener = listener;
1688
- element.addEventListener("focus", listener);
1689
- console.log(`[EventApplicator] onFocus handler attached for action: ${actionName}`);
1690
- },
1691
- onBlur: (element, value) => {
1692
- console.log(`[EventApplicator] onBlur called with value:`, value);
1693
- const { actionName, payload: customPayload } = extractActionDetails(value);
1694
- if (!actionName) {
1695
- console.warn(`[EventApplicator] onBlur value must be an action reference, got:`, value);
1948
+ });
1949
+ };
1950
+ }
1951
+ function createKeyHandler(defaultKey = "Enter") {
1952
+ return (element, value) => {
1953
+ const { actionName, payload: customPayload } = extractActionDetails(value);
1954
+ if (!actionName) {
1955
+ console.warn(`[EventApplicator] onKey requires an action reference starting with @, got:`, value);
1956
+ return;
1957
+ }
1958
+ const disposables = getElementDisposables2(element);
1959
+ const eventKey = `keydown:${actionName}:${defaultKey}`;
1960
+ if (getRegisteredEvents(element).has(eventKey)) {
1961
+ return;
1962
+ }
1963
+ registerEvent(element, eventKey);
1964
+ const targetKey = getKeyTarget(element) || defaultKey;
1965
+ const keyToMatch = targetKey.toLowerCase() === "return" ? "Enter" : targetKey;
1966
+ const listener = (event) => {
1967
+ const keyEvent = event;
1968
+ if (keyEvent.key !== keyToMatch) {
1696
1969
  return;
1697
1970
  }
1698
- const existingListener = element.__hypenBlurListener;
1699
- if (existingListener) {
1700
- element.removeEventListener("blur", existingListener);
1701
- }
1702
- const listener = (event) => {
1703
- console.log(`\uD83D\uDD25 [EventApplicator] onBlur fired, dispatching action: ${actionName}`);
1704
- const target = event.target;
1705
- const payload = Object.keys(customPayload).length > 0 ? { ...customPayload } : {
1706
- type: "blur",
1707
- timestamp: Date.now(),
1708
- value: target.value ?? undefined
1709
- };
1710
- const engine = element.__hypenEngine;
1711
- if (engine) {
1712
- engine.dispatchAction(actionName, payload);
1713
- } else {
1714
- console.warn(`[EventApplicator] No engine attached to element for onBlur`);
1715
- }
1971
+ event.preventDefault();
1972
+ const target = event.target;
1973
+ const payload = Object.keys(customPayload).length > 0 ? { ...customPayload } : {
1974
+ type: event.type,
1975
+ timestamp: Date.now(),
1976
+ key: keyEvent.key,
1977
+ code: keyEvent.code,
1978
+ value: target.value,
1979
+ input: target.value,
1980
+ ctrlKey: keyEvent.ctrlKey,
1981
+ shiftKey: keyEvent.shiftKey,
1982
+ altKey: keyEvent.altKey,
1983
+ metaKey: keyEvent.metaKey
1716
1984
  };
1717
- element.__hypenBlurListener = listener;
1718
- element.addEventListener("blur", listener);
1719
- console.log(`[EventApplicator] onBlur handler attached for action: ${actionName}`);
1720
- },
1721
- onMouseEnter: (element, value) => {
1722
- console.log(`[EventApplicator] onMouseEnter called with value:`, value);
1723
- const { actionName, payload: customPayload } = extractActionDetails(value);
1724
- if (!actionName) {
1725
- console.warn(`[EventApplicator] onMouseEnter value must be an action reference, got:`, value);
1726
- return;
1985
+ const engine = getEngine(element);
1986
+ if (engine) {
1987
+ engine.dispatchAction(actionName, payload);
1727
1988
  }
1728
- const existingListener = element.__hypenMouseEnterListener;
1729
- if (existingListener) {
1730
- element.removeEventListener("mouseenter", existingListener);
1731
- }
1732
- const listener = (event) => {
1733
- console.log(`\uD83D\uDD25 [EventApplicator] onMouseEnter fired, dispatching action: ${actionName}`);
1989
+ };
1990
+ disposables.add(disposableListener(element, "keydown", listener));
1991
+ disposables.addCallback(() => {
1992
+ unregisterEvent(element, eventKey);
1993
+ });
1994
+ };
1995
+ }
1996
+ function createLongClickHandler(thresholdMs = 500) {
1997
+ return (element, value) => {
1998
+ const { actionName, payload: customPayload } = extractActionDetails(value);
1999
+ if (!actionName) {
2000
+ console.warn(`[EventApplicator] onLongClick requires an action reference starting with @, got:`, value);
2001
+ return;
2002
+ }
2003
+ const disposables = getElementDisposables2(element);
2004
+ const eventKey = `longclick:${actionName}`;
2005
+ if (getRegisteredEvents(element).has(eventKey)) {
2006
+ return;
2007
+ }
2008
+ registerEvent(element, eventKey);
2009
+ let longClickTimer = null;
2010
+ const downListener = (event) => {
2011
+ const pointerEvent = event;
2012
+ longClickTimer = disposableTimeout(() => {
1734
2013
  const payload = Object.keys(customPayload).length > 0 ? { ...customPayload } : {
1735
- type: "mouseenter",
2014
+ type: "longclick",
1736
2015
  timestamp: Date.now(),
1737
- clientX: event.clientX,
1738
- clientY: event.clientY
2016
+ clientX: pointerEvent.clientX,
2017
+ clientY: pointerEvent.clientY
1739
2018
  };
1740
- const engine = element.__hypenEngine;
2019
+ const engine = getEngine(element);
1741
2020
  if (engine) {
1742
2021
  engine.dispatchAction(actionName, payload);
1743
- } else {
1744
- console.warn(`[EventApplicator] No engine attached to element for onMouseEnter`);
1745
2022
  }
1746
- };
1747
- element.__hypenMouseEnterListener = listener;
1748
- element.addEventListener("mouseenter", listener);
1749
- console.log(`[EventApplicator] onMouseEnter handler attached for action: ${actionName}`);
1750
- },
1751
- onMouseLeave: (element, value) => {
1752
- console.log(`[EventApplicator] onMouseLeave called with value:`, value);
1753
- const { actionName, payload: customPayload } = extractActionDetails(value);
1754
- if (!actionName) {
1755
- console.warn(`[EventApplicator] onMouseLeave value must be an action reference, got:`, value);
1756
- return;
1757
- }
1758
- const existingListener = element.__hypenMouseLeaveListener;
1759
- if (existingListener) {
1760
- element.removeEventListener("mouseleave", existingListener);
2023
+ longClickTimer = null;
2024
+ }, thresholdMs);
2025
+ };
2026
+ const cancelListener = () => {
2027
+ if (longClickTimer) {
2028
+ longClickTimer.dispose();
2029
+ longClickTimer = null;
1761
2030
  }
1762
- const listener = (event) => {
1763
- console.log(`\uD83D\uDD25 [EventApplicator] onMouseLeave fired, dispatching action: ${actionName}`);
1764
- const payload = Object.keys(customPayload).length > 0 ? { ...customPayload } : {
1765
- type: "mouseleave",
1766
- timestamp: Date.now(),
1767
- clientX: event.clientX,
1768
- clientY: event.clientY
1769
- };
1770
- const engine = element.__hypenEngine;
1771
- if (engine) {
1772
- engine.dispatchAction(actionName, payload);
1773
- } else {
1774
- console.warn(`[EventApplicator] No engine attached to element for onMouseLeave`);
1775
- }
1776
- };
1777
- element.__hypenMouseLeaveListener = listener;
1778
- element.addEventListener("mouseleave", listener);
1779
- console.log(`[EventApplicator] onMouseLeave handler attached for action: ${actionName}`);
1780
- }
2031
+ };
2032
+ disposables.add(disposableListener(element, "pointerdown", downListener));
2033
+ disposables.add(disposableListener(element, "pointerup", cancelListener));
2034
+ disposables.add(disposableListener(element, "pointerleave", cancelListener));
2035
+ disposables.addCallback(() => {
2036
+ unregisterEvent(element, eventKey);
2037
+ cancelListener();
2038
+ });
2039
+ };
2040
+ }
2041
+ var inputPayload = (event, element) => {
2042
+ const target = element;
2043
+ return {
2044
+ type: event.type,
2045
+ timestamp: Date.now(),
2046
+ value: target.value,
2047
+ input: target.value
2048
+ };
2049
+ }, scrollPayload = (_event, element) => {
2050
+ const scrollTop = element.scrollTop;
2051
+ const scrollHeight = element.scrollHeight;
2052
+ const clientHeight = element.clientHeight;
2053
+ const scrollPercentage = scrollHeight - clientHeight > 0 ? scrollTop / (scrollHeight - clientHeight) * 100 : 0;
2054
+ const nearBottom = scrollHeight - scrollTop - clientHeight < 100 || scrollPercentage > 90;
2055
+ return {
2056
+ type: "scroll",
2057
+ timestamp: Date.now(),
2058
+ scrollTop,
2059
+ scrollLeft: element.scrollLeft,
2060
+ scrollHeight,
2061
+ scrollWidth: element.scrollWidth,
2062
+ clientHeight,
2063
+ clientWidth: element.clientWidth,
2064
+ scrollPercentage: Math.round(scrollPercentage),
2065
+ nearBottom,
2066
+ atBottom: scrollHeight - scrollTop === clientHeight,
2067
+ atTop: scrollTop === 0
2068
+ };
2069
+ }, focusPayload = (event, element) => ({
2070
+ type: event.type,
2071
+ timestamp: Date.now(),
2072
+ value: element.value ?? undefined
2073
+ }), mousePayload = (event, _element) => {
2074
+ const mouseEvent = event;
2075
+ return {
2076
+ type: event.type,
2077
+ timestamp: Date.now(),
2078
+ clientX: mouseEvent.clientX,
2079
+ clientY: mouseEvent.clientY
2080
+ };
2081
+ }, eventHandlers;
2082
+ var init_events = __esm(() => {
2083
+ init_element_data();
2084
+ eventHandlers = {
2085
+ onClick: createEventHandler("click"),
2086
+ onPress: createEventHandler("click"),
2087
+ onChange: createEventHandler("change"),
2088
+ onSubmit: createEventHandler("submit", { preventDefault: true }),
2089
+ onInput: createEventHandler("input", { extractPayload: inputPayload }),
2090
+ onKey: createKeyHandler("Enter"),
2091
+ "onKey.key": (element, value) => {
2092
+ setKeyTarget(element, String(value));
2093
+ },
2094
+ "onKey.action": createKeyHandler("Enter"),
2095
+ onScroll: createEventHandler("scroll", {
2096
+ throttleMs: 100,
2097
+ passive: true,
2098
+ extractPayload: scrollPayload
2099
+ }),
2100
+ onLongClick: createLongClickHandler(500),
2101
+ onFocus: createEventHandler("focus", { extractPayload: focusPayload }),
2102
+ onBlur: createEventHandler("blur", { extractPayload: focusPayload }),
2103
+ onMouseEnter: createEventHandler("mouseenter", { extractPayload: mousePayload }),
2104
+ onMouseLeave: createEventHandler("mouseleave", { extractPayload: mousePayload })
1781
2105
  };
1782
2106
  });
1783
2107
 
@@ -1943,7 +2267,43 @@ var effectsHandlers;
1943
2267
  var init_effects = __esm(() => {
1944
2268
  effectsHandlers = {
1945
2269
  boxShadow: (el, value) => {
1946
- el.style.boxShadow = String(value);
2270
+ if (typeof value === "string") {
2271
+ el.style.boxShadow = value;
2272
+ } else if (typeof value === "object" && value !== null) {
2273
+ const obj = value;
2274
+ const x = typeof obj.x === "number" ? `${obj.x}px` : obj.x ?? obj.offsetX ?? "0px";
2275
+ const y = typeof obj.y === "number" ? `${obj.y}px` : obj.y ?? obj.offsetY ?? "0px";
2276
+ const blur = typeof obj.blur === "number" ? `${obj.blur}px` : obj.blur ?? obj.radius ?? "0px";
2277
+ const spread = typeof obj.spread === "number" ? `${obj.spread}px` : obj.spread ?? "0px";
2278
+ const color = obj.color ?? "rgba(0,0,0,0.2)";
2279
+ const inset = obj.inset ? "inset " : "";
2280
+ el.style.boxShadow = `${inset}${x} ${y} ${blur} ${spread} ${color}`;
2281
+ } else if (typeof value === "number") {
2282
+ el.style.boxShadow = `0 ${value}px ${value * 2}px rgba(0,0,0,0.2)`;
2283
+ }
2284
+ },
2285
+ shadow: (el, value) => {
2286
+ if (typeof value === "object" && value !== null) {
2287
+ const obj = value;
2288
+ const x = typeof obj.x === "number" ? `${obj.x}px` : obj.x ?? obj.offsetX ?? "0px";
2289
+ const y = typeof obj.y === "number" ? `${obj.y}px` : obj.y ?? obj.offsetY ?? "0px";
2290
+ const blur = typeof obj.blur === "number" ? `${obj.blur}px` : obj.blur ?? obj.radius ?? "4px";
2291
+ const color = obj.color ?? "rgba(0,0,0,0.2)";
2292
+ el.style.boxShadow = `${x} ${y} ${blur} ${color}`;
2293
+ } else if (typeof value === "number") {
2294
+ el.style.boxShadow = `0 ${value}px ${value * 2}px rgba(0,0,0,0.2)`;
2295
+ } else {
2296
+ el.style.boxShadow = String(value);
2297
+ }
2298
+ },
2299
+ elevation: (el, value) => {
2300
+ const level = typeof value === "number" ? value : parseInt(String(value), 10);
2301
+ if (!isNaN(level) && level >= 0) {
2302
+ const y = level * 0.5;
2303
+ const blur = level * 1.5;
2304
+ const opacity = Math.min(0.1 + level * 0.02, 0.4);
2305
+ el.style.boxShadow = `0 ${y}px ${blur}px rgba(0,0,0,${opacity})`;
2306
+ }
1947
2307
  },
1948
2308
  textShadow: (el, value) => {
1949
2309
  el.style.textShadow = String(value);
@@ -2313,6 +2673,7 @@ class ComponentRegistry {
2313
2673
  const { paragraphHandler: paragraphHandler2 } = (init_paragraph(), __toCommonJS(exports_paragraph));
2314
2674
  const { routerHandler: routerHandler2 } = (init_router(), __toCommonJS(exports_router));
2315
2675
  const { routeHandler: routeHandler2 } = (init_route(), __toCommonJS(exports_route));
2676
+ const { hypenAppHandler: hypenAppHandler2 } = (init_hypenapp(), __toCommonJS(exports_hypenapp));
2316
2677
  this.register("column", columnHandler2);
2317
2678
  this.register("row", rowHandler2);
2318
2679
  this.register("text", textHandler2);
@@ -2344,10 +2705,33 @@ class ComponentRegistry {
2344
2705
  this.register("paragraph", paragraphHandler2);
2345
2706
  this.register("router", routerHandler2);
2346
2707
  this.register("route", routeHandler2);
2708
+ this.register("hypenapp", hypenAppHandler2);
2347
2709
  }
2348
2710
  }
2349
2711
 
2350
2712
  // src/dom/applicators/index.ts
2713
+ var BREAKPOINTS = {
2714
+ sm: "640px",
2715
+ md: "768px",
2716
+ lg: "1024px",
2717
+ xl: "1280px",
2718
+ "2xl": "1536px"
2719
+ };
2720
+ var variantStyleSheet = null;
2721
+ var insertedRules = new Set;
2722
+ function getVariantStyleSheet() {
2723
+ if (!variantStyleSheet) {
2724
+ const style = document.createElement("style");
2725
+ style.id = "hypen-variants";
2726
+ document.head.appendChild(style);
2727
+ variantStyleSheet = style.sheet;
2728
+ }
2729
+ return variantStyleSheet;
2730
+ }
2731
+ function hashValue(value) {
2732
+ return String(value).replace(/[^a-zA-Z0-9]/g, "").slice(0, 8);
2733
+ }
2734
+
2351
2735
  class ApplicatorRegistry {
2352
2736
  handlers = new Map;
2353
2737
  elementState = new WeakMap;
@@ -2495,12 +2879,55 @@ class ApplicatorRegistry {
2495
2879
  }
2496
2880
  }
2497
2881
  setStyleProperty(element, name, value) {
2498
- const cssName = name.replace(/([A-Z])/g, "-$1").toLowerCase();
2882
+ const atIndex = name.indexOf("@");
2883
+ const colonIndex = name.indexOf(":");
2884
+ if (atIndex !== -1) {
2885
+ const prop = name.slice(0, atIndex);
2886
+ const breakpoint = name.slice(atIndex + 1);
2887
+ const minWidth = BREAKPOINTS[breakpoint];
2888
+ if (minWidth) {
2889
+ const cssName2 = this.toKebabCase(prop);
2890
+ const cssValue = this.formatCssValue(cssName2, value);
2891
+ const className = `hypen-${cssName2.replace(/[^a-zA-Z0-9-]/g, "")}-${breakpoint}-${hashValue(value)}`;
2892
+ const ruleKey = `${className}:${cssValue}`;
2893
+ if (!insertedRules.has(ruleKey)) {
2894
+ const sheet = getVariantStyleSheet();
2895
+ sheet.insertRule(`@media (min-width: ${minWidth}) { .${className} { ${cssName2}: ${cssValue}; } }`, sheet.cssRules.length);
2896
+ insertedRules.add(ruleKey);
2897
+ }
2898
+ element.classList.add(className);
2899
+ }
2900
+ return;
2901
+ }
2902
+ if (colonIndex !== -1) {
2903
+ const prop = name.slice(0, colonIndex);
2904
+ const state = name.slice(colonIndex + 1);
2905
+ const validStates = ["hover", "focus", "active", "disabled", "focus-visible", "focus-within"];
2906
+ if (validStates.includes(state)) {
2907
+ const cssName2 = this.toKebabCase(prop);
2908
+ const cssValue = this.formatCssValue(cssName2, value);
2909
+ const className = `hypen-${cssName2.replace(/[^a-zA-Z0-9-]/g, "")}-${state}-${hashValue(value)}`;
2910
+ const ruleKey = `${className}:${cssValue}`;
2911
+ if (!insertedRules.has(ruleKey)) {
2912
+ const sheet = getVariantStyleSheet();
2913
+ sheet.insertRule(`.${className}:${state} { ${cssName2}: ${cssValue}; }`, sheet.cssRules.length);
2914
+ insertedRules.add(ruleKey);
2915
+ }
2916
+ element.classList.add(className);
2917
+ }
2918
+ return;
2919
+ }
2920
+ const cssName = this.toKebabCase(name);
2921
+ element.style.setProperty(cssName, this.formatCssValue(cssName, value));
2922
+ }
2923
+ toKebabCase(name) {
2924
+ return name.replace(/([A-Z])/g, "-$1").toLowerCase();
2925
+ }
2926
+ formatCssValue(cssName, value) {
2499
2927
  if (typeof value === "number" && this.needsUnit(cssName)) {
2500
- element.style.setProperty(cssName, `${value}px`);
2501
- } else {
2502
- element.style.setProperty(cssName, String(value));
2928
+ return `${value}px`;
2503
2929
  }
2930
+ return String(value);
2504
2931
  }
2505
2932
  needsUnit(prop) {
2506
2933
  const unitless = [
@@ -2871,13 +3298,13 @@ class DOMRenderer {
2871
3298
  }
2872
3299
  onCreate(id, elementType, props) {
2873
3300
  const propsObj = props instanceof Map ? Object.fromEntries(props) : props;
2874
- const element = this.components.createElement(elementType, propsObj);
3301
+ let element = this.components.createElement(elementType, propsObj);
2875
3302
  if (!element) {
2876
3303
  const fallback = document.createElement("div");
2877
- fallback.dataset.hypenType = elementType;
2878
- fallback.textContent = `Unknown component: ${elementType}`;
2879
- this.nodes.set(id, fallback);
2880
- return;
3304
+ fallback.dataset.hypenType = elementType.toLowerCase();
3305
+ fallback.style.display = "contents";
3306
+ element = fallback;
3307
+ console.log(`[Renderer] Unknown component "${elementType}" - using transparent container`);
2881
3308
  }
2882
3309
  element.dataset.hypenType = elementType.toLowerCase();
2883
3310
  element.dataset.hypenId = id;
@@ -3107,4 +3534,4 @@ export {
3107
3534
  ApplicatorRegistry
3108
3535
  };
3109
3536
 
3110
- //# debugId=18866530C09B344A64756E2164756E21
3537
+ //# debugId=8AAA0D290328E56B64756E2164756E21