@servlyadmin/runtime-core 0.1.7 → 0.1.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,3 +1,14 @@
1
+ import {
2
+ DEFAULT_SERVLY_TAILWIND_CONFIG,
3
+ addCustomStyles,
4
+ getTailwind,
5
+ initServlyTailwind,
6
+ injectTailwind,
7
+ isTailwindLoaded,
8
+ removeCustomStyles,
9
+ removeTailwind,
10
+ updateTailwindConfig
11
+ } from "./chunk-IWFVKY5N.js";
1
12
  import {
2
13
  buildRegistryFromBundle,
3
14
  collectAllDependencies,
@@ -526,16 +537,95 @@ var BINDING_SOURCES = [
526
537
  "input",
527
538
  "currentItem",
528
539
  "localStore",
540
+ "localStorage",
541
+ "sessionStorage",
529
542
  "config",
530
543
  "element",
531
544
  "self",
532
545
  "params",
533
- "query"
546
+ "query",
547
+ "window",
548
+ "bindings",
549
+ "binding",
550
+ "boundInputs",
551
+ "parent"
534
552
  ];
535
553
  var TEMPLATE_REGEX = /\{\{([^}]+)\}\}/g;
536
554
  function hasTemplateSyntax(value) {
537
555
  return typeof value === "string" && value.includes("{{") && value.includes("}}");
538
556
  }
557
+ function getLocalStorageValue(key) {
558
+ if (typeof localStorage === "undefined") return void 0;
559
+ try {
560
+ const stored = localStorage.getItem(key);
561
+ if (stored === null) return void 0;
562
+ try {
563
+ return JSON.parse(stored);
564
+ } catch {
565
+ return stored;
566
+ }
567
+ } catch {
568
+ return void 0;
569
+ }
570
+ }
571
+ function getSessionStorageValue(key) {
572
+ if (typeof sessionStorage === "undefined") return void 0;
573
+ try {
574
+ const stored = sessionStorage.getItem(key);
575
+ if (stored === null) return void 0;
576
+ try {
577
+ return JSON.parse(stored);
578
+ } catch {
579
+ return stored;
580
+ }
581
+ } catch {
582
+ return void 0;
583
+ }
584
+ }
585
+ function getWindowInfo() {
586
+ if (typeof window === "undefined") return {};
587
+ const searchParams = {};
588
+ try {
589
+ const urlSearchParams = new URLSearchParams(window.location.search);
590
+ urlSearchParams.forEach((value, key) => {
591
+ searchParams[key] = value;
592
+ });
593
+ } catch {
594
+ }
595
+ return {
596
+ href: window.location.href,
597
+ pathname: window.location.pathname,
598
+ search: window.location.search,
599
+ hash: window.location.hash,
600
+ origin: window.location.origin,
601
+ protocol: window.location.protocol,
602
+ host: window.location.host,
603
+ hostname: window.location.hostname,
604
+ port: window.location.port,
605
+ searchParams,
606
+ params: searchParams,
607
+ innerWidth: window.innerWidth,
608
+ innerHeight: window.innerHeight,
609
+ screenWidth: window.screen?.width,
610
+ screenHeight: window.screen?.height
611
+ };
612
+ }
613
+ function navigatePath(obj, parts) {
614
+ let current = obj;
615
+ for (const part of parts) {
616
+ if (current === null || current === void 0) return void 0;
617
+ const arrayMatch = part.match(/^(\w+)\[(\d+)\]$/);
618
+ if (arrayMatch) {
619
+ const [, propName, indexStr] = arrayMatch;
620
+ current = current[propName];
621
+ if (!Array.isArray(current)) return void 0;
622
+ current = current[parseInt(indexStr, 10)];
623
+ } else {
624
+ current = current[part];
625
+ }
626
+ }
627
+ return current;
628
+ }
539
629
  function resolveBindingPath(path, context) {
540
630
  const trimmed = path.trim();
541
631
  const parts = trimmed.split(".");
@@ -543,17 +633,70 @@ function resolveBindingPath(path, context) {
543
633
  return void 0;
544
634
  }
545
635
  const prefix = parts[0].toLowerCase();
636
+ if (prefix === "localstore" || prefix === "localstorage") {
637
+ if (parts.length === 1) {
638
+ const all = {};
639
+ if (typeof localStorage !== "undefined") {
640
+ for (let i = 0; i < localStorage.length; i++) {
641
+ const key2 = localStorage.key(i);
642
+ if (key2) {
643
+ all[key2] = getLocalStorageValue(key2);
644
+ }
645
+ }
646
+ }
647
+ return all;
648
+ }
649
+ const key = parts[1];
650
+ const value = getLocalStorageValue(key);
651
+ if (parts.length === 2) return value;
652
+ return navigatePath(value, parts.slice(2));
653
+ }
654
+ if (prefix === "sessionstorage") {
655
+ if (parts.length === 1) {
656
+ const all = {};
657
+ if (typeof sessionStorage !== "undefined") {
658
+ for (let i = 0; i < sessionStorage.length; i++) {
659
+ const key2 = sessionStorage.key(i);
660
+ if (key2) {
661
+ all[key2] = getSessionStorageValue(key2);
662
+ }
663
+ }
664
+ }
665
+ return all;
666
+ }
667
+ const key = parts[1];
668
+ const value = getSessionStorageValue(key);
669
+ if (parts.length === 2) return value;
670
+ return navigatePath(value, parts.slice(2));
671
+ }
672
+ if (prefix === "window" || prefix === "url") {
673
+ const windowInfo = getWindowInfo();
674
+ if (parts.length === 1) return windowInfo;
675
+ return navigatePath(windowInfo, parts.slice(1));
676
+ }
677
+ if (prefix === "params" || prefix === "query") {
678
+ const windowInfo = getWindowInfo();
679
+ const params = windowInfo.searchParams || {};
680
+ if (parts.length === 1) return params;
681
+ return navigatePath(params, parts.slice(1));
682
+ }
546
683
  let source;
547
684
  let startIndex = 0;
548
- if (prefix === "props" || prefix === "input") {
685
+ if (prefix === "props" || prefix === "input" || prefix === "bindings" || prefix === "binding" || prefix === "boundinputs" || prefix === "parent") {
549
686
  source = context.props;
550
687
  startIndex = 1;
551
688
  } else if (prefix === "state" || prefix === "appstate") {
552
689
  source = context.state;
553
690
  startIndex = 1;
554
- } else if (prefix === "context") {
691
+ } else if (prefix === "context" || prefix === "config") {
555
692
  source = context.context;
556
693
  startIndex = 1;
694
+ } else if (prefix === "currentitem") {
695
+ source = context.props;
696
+ startIndex = 0;
697
+ } else if (prefix === "self" || prefix === "element") {
698
+ source = context.state;
699
+ startIndex = 1;
557
700
  } else if (BINDING_SOURCES.includes(prefix)) {
558
701
  source = context.props;
559
702
  startIndex = 1;
@@ -564,41 +707,49 @@ function resolveBindingPath(path, context) {
564
707
  if (!source) {
565
708
  return void 0;
566
709
  }
567
- let value = source;
568
- for (let i = startIndex; i < parts.length; i++) {
569
- if (value === null || value === void 0) {
570
- return void 0;
571
- }
572
- value = value[parts[i]];
573
- }
574
- return value;
710
+ return navigatePath(source, parts.slice(startIndex));
575
711
  }
576
712
  function resolveExpression(expression, context) {
577
713
  const trimmed = expression.trim();
578
- const defaultMatch = trimmed.match(/^(.+?)\|\|(.+)$/);
579
- if (defaultMatch) {
580
- const mainValue = resolveExpression(defaultMatch[1].trim(), context);
581
- if (mainValue !== void 0 && mainValue !== null && mainValue !== "") {
582
- return mainValue;
583
- }
584
- const defaultVal = defaultMatch[2].trim();
585
- if (defaultVal.startsWith('"') && defaultVal.endsWith('"') || defaultVal.startsWith("'") && defaultVal.endsWith("'")) {
586
- return defaultVal.slice(1, -1);
587
- }
588
- if (!isNaN(Number(defaultVal))) {
589
- return Number(defaultVal);
714
+ const comparisonOperators = ["===", "!==", "==", "!=", ">=", "<=", ">", "<"];
715
+ for (const op of comparisonOperators) {
716
+ if (trimmed.includes(op)) {
717
+ const [left, right] = trimmed.split(op, 2);
718
+ const leftVal = resolveExpressionValue(left.trim(), context);
719
+ const rightVal = resolveExpressionValue(right.trim(), context);
720
+ switch (op) {
721
+ case "===":
722
+ return leftVal === rightVal;
723
+ case "!==":
724
+ return leftVal !== rightVal;
725
+ case "==":
726
+ return leftVal == rightVal;
727
+ case "!=":
728
+ return leftVal != rightVal;
729
+ case ">":
730
+ return leftVal > rightVal;
731
+ case "<":
732
+ return leftVal < rightVal;
733
+ case ">=":
734
+ return leftVal >= rightVal;
735
+ case "<=":
736
+ return leftVal <= rightVal;
737
+ }
590
738
  }
591
- if (defaultVal === "true") return true;
592
- if (defaultVal === "false") return false;
593
- return resolveExpression(defaultVal, context);
594
739
  }
595
- const ternaryMatch = trimmed.match(/^(.+?)\s*\?\s*(.+?)\s*:\s*(.+)$/);
596
- if (ternaryMatch) {
597
- const condition = resolveExpression(ternaryMatch[1].trim(), context);
598
- if (condition) {
599
- return resolveTernaryValue(ternaryMatch[2].trim(), context);
740
+ if (trimmed.startsWith("!") && !trimmed.startsWith("!=")) {
741
+ const innerValue = resolveExpression(trimmed.slice(1).trim(), context);
742
+ return !innerValue;
743
+ }
744
+ if (trimmed.includes("||")) {
745
+ const parts = trimmed.split("||");
746
+ for (let i = 0; i < parts.length; i++) {
747
+ const part = parts[i].trim();
748
+ const value = resolveExpressionValue(part, context);
749
+ if (value || i === parts.length - 1) {
750
+ return value;
751
+ }
600
752
  }
601
- return resolveTernaryValue(ternaryMatch[3].trim(), context);
602
753
  }
603
754
  if (trimmed.includes("&&")) {
604
755
  const parts = trimmed.split("&&");
@@ -608,6 +759,28 @@ function resolveExpression(expression, context) {
608
759
  }
609
760
  return resolveExpression(parts[parts.length - 1].trim(), context);
610
761
  }
762
+ const ternaryMatch = trimmed.match(/^(.+?)\s*\?\s*(.+?)\s*:\s*(.+)$/);
763
+ if (ternaryMatch) {
764
+ const condition = resolveExpression(ternaryMatch[1].trim(), context);
765
+ if (condition) {
766
+ return resolveTernaryValue(ternaryMatch[2].trim(), context);
767
+ }
768
+ return resolveTernaryValue(ternaryMatch[3].trim(), context);
769
+ }
770
+ return resolveExpressionValue(trimmed, context);
771
+ }
772
+ function resolveExpressionValue(value, context) {
773
+ const trimmed = value.trim();
774
+ if (trimmed.startsWith('"') && trimmed.endsWith('"') || trimmed.startsWith("'") && trimmed.endsWith("'")) {
775
+ return trimmed.slice(1, -1);
776
+ }
777
+ if (!isNaN(Number(trimmed)) && trimmed !== "") {
778
+ return Number(trimmed);
779
+ }
780
+ if (trimmed === "true") return true;
781
+ if (trimmed === "false") return false;
782
+ if (trimmed === "null") return null;
783
+ if (trimmed === "undefined") return void 0;
611
784
  return resolveBindingPath(trimmed, context);
612
785
  }
613
786
  function resolveTernaryValue(value, context) {
@@ -639,7 +812,7 @@ function resolveTemplate(template, context, componentId) {
639
812
  }
640
813
  return String(value);
641
814
  }
642
- return template.replace(TEMPLATE_REGEX, (match, expression) => {
815
+ return template.replace(TEMPLATE_REGEX, (_match, expression) => {
643
816
  const value = resolveExpression(expression, context);
644
817
  if (value === void 0 || value === null) {
645
818
  return "";
@@ -1069,157 +1242,1206 @@ function resetLongTaskObserver() {
1069
1242
  longTaskObserverInstance = null;
1070
1243
  }
1071
1244
 
1072
- // src/renderer.ts
1073
- var COMPONENT_TO_TAG = {
1074
- container: "div",
1075
- text: "span",
1076
- button: "button",
1077
- input: "input",
1078
- image: "img",
1079
- link: "a",
1080
- form: "form",
1081
- label: "label",
1082
- textarea: "textarea",
1083
- select: "select",
1084
- option: "option",
1085
- list: "ul",
1086
- listItem: "li",
1087
- heading: "h1",
1088
- paragraph: "p",
1089
- section: "section",
1090
- article: "article",
1091
- header: "header",
1092
- footer: "footer",
1093
- nav: "nav",
1094
- aside: "aside",
1095
- main: "main",
1096
- span: "span",
1097
- div: "div"
1098
- };
1099
- var SELF_CLOSING_TAGS = /* @__PURE__ */ new Set([
1100
- "input",
1101
- "img",
1102
- "br",
1103
- "hr",
1104
- "area",
1105
- "base",
1106
- "col",
1107
- "embed",
1108
- "link",
1109
- "meta",
1110
- "param",
1111
- "source",
1112
- "track",
1113
- "wbr"
1114
- ]);
1115
- function getElementTag(element) {
1116
- const config = element.configuration;
1117
- if (config?.tag) {
1118
- return config.tag;
1245
+ // src/stateManager.ts
1246
+ var StateManager = class {
1247
+ constructor(config = {}) {
1248
+ this.state = {};
1249
+ this.listeners = /* @__PURE__ */ new Set();
1250
+ this.config = config;
1251
+ this.state = config.initialState || {};
1252
+ if (config.persistToLocalStorage && typeof localStorage !== "undefined") {
1253
+ this.loadFromLocalStorage();
1254
+ }
1255
+ if (config.onStateChange) {
1256
+ this.listeners.add(config.onStateChange);
1257
+ }
1119
1258
  }
1120
- if (element.componentId && COMPONENT_TO_TAG[element.componentId]) {
1121
- return COMPONENT_TO_TAG[element.componentId];
1259
+ /**
1260
+ * Get value by path from state
1261
+ */
1262
+ get(path) {
1263
+ return getValueByPath(this.state, path);
1122
1264
  }
1123
- if (element.isGroup) {
1124
- return "div";
1265
+ /**
1266
+ * Get the entire state object
1267
+ */
1268
+ getState() {
1269
+ return { ...this.state };
1125
1270
  }
1126
- return "div";
1127
- }
1128
- function isSelfClosing(tag) {
1129
- return SELF_CLOSING_TAGS.has(tag.toLowerCase());
1130
- }
1131
- function buildTree(elements) {
1132
- const tree = /* @__PURE__ */ new Map();
1133
- for (const element of elements) {
1134
- const parentId = element.parent || null;
1135
- if (!tree.has(parentId)) {
1136
- tree.set(parentId, []);
1137
- }
1138
- tree.get(parentId).push(element);
1271
+ /**
1272
+ * Set a value in state
1273
+ */
1274
+ set(key, value, elementId) {
1275
+ const previousValue = this.get(key);
1276
+ setValueByPath(this.state, key, value);
1277
+ this.notifyChange({
1278
+ key,
1279
+ value,
1280
+ previousValue,
1281
+ operation: "set",
1282
+ elementId,
1283
+ timestamp: Date.now()
1284
+ });
1139
1285
  }
1140
- return tree;
1141
- }
1142
- function getTextContent(element, context) {
1143
- const config = element.configuration;
1144
- if (config?.dynamicText) {
1145
- return resolveTemplate(config.dynamicText, context);
1286
+ /**
1287
+ * Merge an object into state at the given path
1288
+ */
1289
+ merge(key, value, deep = false, elementId) {
1290
+ const previousValue = this.get(key);
1291
+ const currentValue = previousValue || {};
1292
+ const newValue = deep ? deepMerge(currentValue, value) : { ...currentValue, ...value };
1293
+ setValueByPath(this.state, key, newValue);
1294
+ this.notifyChange({
1295
+ key,
1296
+ value: newValue,
1297
+ previousValue,
1298
+ operation: "merge",
1299
+ elementId,
1300
+ timestamp: Date.now()
1301
+ });
1146
1302
  }
1147
- if (config?.text) {
1148
- if (hasTemplateSyntax(config.text)) {
1149
- return resolveTemplate(config.text, context);
1150
- }
1151
- return config.text;
1303
+ /**
1304
+ * Delete a key from state
1305
+ */
1306
+ delete(key, elementId) {
1307
+ const previousValue = this.get(key);
1308
+ deleteValueByPath(this.state, key);
1309
+ this.notifyChange({
1310
+ key,
1311
+ value: void 0,
1312
+ previousValue,
1313
+ operation: "delete",
1314
+ elementId,
1315
+ timestamp: Date.now()
1316
+ });
1152
1317
  }
1153
- return "";
1154
- }
1155
- function applyAttributes(domElement, element, context) {
1156
- const config = element.configuration || {};
1157
- const attributeMap = [
1158
- { key: "id", attr: "id" },
1159
- { key: "src", dynamicKey: "dynamicSrc", attr: "src" },
1160
- { key: "alt", attr: "alt" },
1161
- { key: "href", dynamicKey: "dynamicHref", attr: "href" },
1162
- { key: "target", attr: "target" },
1163
- { key: "placeholder", attr: "placeholder" },
1164
- { key: "type", attr: "type" },
1165
- { key: "name", attr: "name" },
1166
- { key: "value", dynamicKey: "dynamicValue", attr: "value" }
1167
- ];
1168
- for (const { key, dynamicKey, attr } of attributeMap) {
1169
- const dynamicValue = dynamicKey ? config[dynamicKey] : void 0;
1170
- if (dynamicValue !== void 0 && dynamicValue !== null && dynamicValue !== "") {
1171
- const resolved = resolveTemplate(String(dynamicValue), context);
1172
- if (resolved) {
1173
- domElement.setAttribute(attr, resolved);
1318
+ /**
1319
+ * Append to an array in state
1320
+ */
1321
+ append(key, value, elementId) {
1322
+ const previousValue = this.get(key);
1323
+ const currentArray = Array.isArray(previousValue) ? [...previousValue] : [];
1324
+ currentArray.push(value);
1325
+ setValueByPath(this.state, key, currentArray);
1326
+ this.notifyChange({
1327
+ key,
1328
+ value: currentArray,
1329
+ previousValue,
1330
+ operation: "append",
1331
+ elementId,
1332
+ timestamp: Date.now()
1333
+ });
1334
+ }
1335
+ /**
1336
+ * Prepend to an array in state
1337
+ */
1338
+ prepend(key, value, elementId) {
1339
+ const previousValue = this.get(key);
1340
+ const currentArray = Array.isArray(previousValue) ? [...previousValue] : [];
1341
+ currentArray.unshift(value);
1342
+ setValueByPath(this.state, key, currentArray);
1343
+ this.notifyChange({
1344
+ key,
1345
+ value: currentArray,
1346
+ previousValue,
1347
+ operation: "prepend",
1348
+ elementId,
1349
+ timestamp: Date.now()
1350
+ });
1351
+ }
1352
+ /**
1353
+ * Toggle a boolean value in state
1354
+ */
1355
+ toggle(key, elementId) {
1356
+ const previousValue = this.get(key);
1357
+ const newValue = !previousValue;
1358
+ setValueByPath(this.state, key, newValue);
1359
+ this.notifyChange({
1360
+ key,
1361
+ value: newValue,
1362
+ previousValue,
1363
+ operation: "toggle",
1364
+ elementId,
1365
+ timestamp: Date.now()
1366
+ });
1367
+ }
1368
+ /**
1369
+ * Subscribe to state changes
1370
+ */
1371
+ subscribe(listener) {
1372
+ this.listeners.add(listener);
1373
+ return () => this.listeners.delete(listener);
1374
+ }
1375
+ /**
1376
+ * Notify all listeners of a state change
1377
+ */
1378
+ notifyChange(event) {
1379
+ if (this.config.persistToLocalStorage) {
1380
+ this.saveToLocalStorage();
1381
+ }
1382
+ for (const listener of this.listeners) {
1383
+ try {
1384
+ listener(event);
1385
+ } catch (error) {
1386
+ console.error("State change listener error:", error);
1174
1387
  }
1175
- continue;
1176
1388
  }
1177
- const staticValue = config[key];
1178
- if (staticValue !== void 0 && staticValue !== null && staticValue !== "") {
1179
- const resolved = hasTemplateSyntax(String(staticValue)) ? resolveTemplate(String(staticValue), context) : String(staticValue);
1180
- if (resolved) {
1181
- domElement.setAttribute(attr, resolved);
1389
+ }
1390
+ /**
1391
+ * Load state from localStorage
1392
+ */
1393
+ loadFromLocalStorage() {
1394
+ try {
1395
+ const prefix = this.config.localStoragePrefix || "servly_state_";
1396
+ const stored = localStorage.getItem(`${prefix}state`);
1397
+ if (stored) {
1398
+ const parsed = JSON.parse(stored);
1399
+ this.state = { ...this.state, ...parsed };
1182
1400
  }
1401
+ } catch (error) {
1402
+ console.warn("Failed to load state from localStorage:", error);
1183
1403
  }
1184
1404
  }
1185
- if (config.disabled) domElement.setAttribute("disabled", "");
1186
- if (config.required) domElement.setAttribute("required", "");
1187
- if (config.readOnly) domElement.setAttribute("readonly", "");
1188
- for (const [key, value] of Object.entries(config)) {
1189
- if (key.startsWith("data-") && value !== void 0) {
1190
- const resolved = hasTemplateSyntax(String(value)) ? resolveTemplate(String(value), context) : String(value);
1191
- domElement.setAttribute(key, resolved);
1405
+ /**
1406
+ * Save state to localStorage
1407
+ */
1408
+ saveToLocalStorage() {
1409
+ try {
1410
+ const prefix = this.config.localStoragePrefix || "servly_state_";
1411
+ localStorage.setItem(`${prefix}state`, JSON.stringify(this.state));
1412
+ } catch (error) {
1413
+ console.warn("Failed to save state to localStorage:", error);
1192
1414
  }
1193
1415
  }
1194
- for (const [key, value] of Object.entries(config)) {
1195
- if (key.startsWith("aria-") && value !== void 0) {
1196
- const resolved = hasTemplateSyntax(String(value)) ? resolveTemplate(String(value), context) : String(value);
1197
- domElement.setAttribute(key, resolved);
1416
+ /**
1417
+ * Clear all state
1418
+ */
1419
+ clear() {
1420
+ const previousState = { ...this.state };
1421
+ this.state = {};
1422
+ if (this.config.persistToLocalStorage) {
1423
+ try {
1424
+ const prefix = this.config.localStoragePrefix || "servly_state_";
1425
+ localStorage.removeItem(`${prefix}state`);
1426
+ } catch (error) {
1427
+ console.warn("Failed to clear localStorage:", error);
1428
+ }
1198
1429
  }
1430
+ this.notifyChange({
1431
+ key: "",
1432
+ value: {},
1433
+ previousValue: previousState,
1434
+ operation: "delete",
1435
+ timestamp: Date.now()
1436
+ });
1199
1437
  }
1200
- }
1201
- function resolveFunctionBinding(binding, context) {
1202
- if (binding.source === "props" || binding.source === "parent") {
1203
- const path = binding.path;
1204
- if (!path) return void 0;
1205
- const parts = path.split(".");
1206
- let value = context.props;
1207
- for (const part of parts) {
1208
- if (value === null || value === void 0) return void 0;
1209
- value = value[part];
1438
+ };
1439
+ function getValueByPath(obj, path) {
1440
+ if (!obj || !path) return void 0;
1441
+ const parts = path.split(".");
1442
+ let current = obj;
1443
+ for (const part of parts) {
1444
+ if (current === null || current === void 0) return void 0;
1445
+ const arrayMatch = part.match(/^(\w+)\[(\d+)\]$/);
1446
+ if (arrayMatch) {
1447
+ const [, propName, indexStr] = arrayMatch;
1448
+ current = current[propName];
1449
+ if (!Array.isArray(current)) return void 0;
1450
+ current = current[parseInt(indexStr, 10)];
1451
+ } else {
1452
+ current = current[part];
1210
1453
  }
1211
- if (typeof value === "function") {
1212
- return value;
1454
+ }
1455
+ return current;
1456
+ }
1457
+ function setValueByPath(obj, path, value) {
1458
+ if (!obj || !path) return;
1459
+ const parts = path.split(".");
1460
+ let current = obj;
1461
+ for (let i = 0; i < parts.length - 1; i++) {
1462
+ const part = parts[i];
1463
+ const arrayMatch2 = part.match(/^(\w+)\[(\d+)\]$/);
1464
+ if (arrayMatch2) {
1465
+ const [, propName, indexStr] = arrayMatch2;
1466
+ if (!current[propName]) current[propName] = [];
1467
+ const index = parseInt(indexStr, 10);
1468
+ if (!current[propName][index]) current[propName][index] = {};
1469
+ current = current[propName][index];
1470
+ } else {
1471
+ if (!current[part]) current[part] = {};
1472
+ current = current[part];
1213
1473
  }
1214
- return void 0;
1215
1474
  }
1216
- if (binding.source === "static" && typeof binding.value === "function") {
1217
- return binding.value;
1475
+ const lastPart = parts[parts.length - 1];
1476
+ const arrayMatch = lastPart.match(/^(\w+)\[(\d+)\]$/);
1477
+ if (arrayMatch) {
1478
+ const [, propName, indexStr] = arrayMatch;
1479
+ if (!current[propName]) current[propName] = [];
1480
+ current[propName][parseInt(indexStr, 10)] = value;
1481
+ } else {
1482
+ current[lastPart] = value;
1218
1483
  }
1219
- return void 0;
1220
1484
  }
1221
- function attachEventHandlers(domElement, element, eventHandlers, context, elementState, isRootElement = false) {
1222
- const elementId = element.i;
1485
+ function deleteValueByPath(obj, path) {
1486
+ if (!obj || !path) return;
1487
+ const parts = path.split(".");
1488
+ let current = obj;
1489
+ for (let i = 0; i < parts.length - 1; i++) {
1490
+ const part = parts[i];
1491
+ if (current[part] === void 0) return;
1492
+ current = current[part];
1493
+ }
1494
+ const lastPart = parts[parts.length - 1];
1495
+ delete current[lastPart];
1496
+ }
1497
+ function deepMerge(target, source) {
1498
+ if (!source) return target;
1499
+ if (!target) return source;
1500
+ const result = { ...target };
1501
+ for (const key of Object.keys(source)) {
1502
+ if (source[key] && typeof source[key] === "object" && !Array.isArray(source[key]) && target[key] && typeof target[key] === "object" && !Array.isArray(target[key])) {
1503
+ result[key] = deepMerge(target[key], source[key]);
1504
+ } else {
1505
+ result[key] = source[key];
1506
+ }
1507
+ }
1508
+ return result;
1509
+ }
1510
+ function addClass(currentClasses, classToAdd) {
1511
+ const classes = currentClasses.split(/\s+/).filter(Boolean);
1512
+ if (!classes.includes(classToAdd)) {
1513
+ classes.push(classToAdd);
1514
+ }
1515
+ return classes.join(" ");
1516
+ }
1517
+ function removeClass(currentClasses, classToRemove) {
1518
+ return currentClasses.split(/\s+/).filter((cls) => cls && cls !== classToRemove).join(" ");
1519
+ }
1520
+ function toggleClass(currentClasses, classToToggle) {
1521
+ const classes = currentClasses.split(/\s+/).filter(Boolean);
1522
+ const index = classes.indexOf(classToToggle);
1523
+ if (index > -1) {
1524
+ classes.splice(index, 1);
1525
+ } else {
1526
+ classes.push(classToToggle);
1527
+ }
1528
+ return classes.join(" ");
1529
+ }
1530
+ function hasClass(currentClasses, classToCheck) {
1531
+ return currentClasses.split(/\s+/).includes(classToCheck);
1532
+ }
1533
+ function getLocalStorage(key, defaultValue) {
1534
+ if (typeof localStorage === "undefined") return defaultValue;
1535
+ try {
1536
+ const stored = localStorage.getItem(key);
1537
+ if (stored === null) return defaultValue;
1538
+ return JSON.parse(stored);
1539
+ } catch {
1540
+ return localStorage.getItem(key) ?? defaultValue;
1541
+ }
1542
+ }
1543
+ function setLocalStorage(key, value) {
1544
+ if (typeof localStorage === "undefined") return;
1545
+ try {
1546
+ localStorage.setItem(key, JSON.stringify(value));
1547
+ } catch (error) {
1548
+ console.warn("Failed to set localStorage:", error);
1549
+ }
1550
+ }
1551
+ function removeLocalStorage(key) {
1552
+ if (typeof localStorage === "undefined") return;
1553
+ localStorage.removeItem(key);
1554
+ }
1555
+ function getSessionStorage(key, defaultValue) {
1556
+ if (typeof sessionStorage === "undefined") return defaultValue;
1557
+ try {
1558
+ const stored = sessionStorage.getItem(key);
1559
+ if (stored === null) return defaultValue;
1560
+ return JSON.parse(stored);
1561
+ } catch {
1562
+ return sessionStorage.getItem(key) ?? defaultValue;
1563
+ }
1564
+ }
1565
+ function setSessionStorage(key, value) {
1566
+ if (typeof sessionStorage === "undefined") return;
1567
+ try {
1568
+ sessionStorage.setItem(key, JSON.stringify(value));
1569
+ } catch (error) {
1570
+ console.warn("Failed to set sessionStorage:", error);
1571
+ }
1572
+ }
1573
+ function removeSessionStorage(key) {
1574
+ if (typeof sessionStorage === "undefined") return;
1575
+ sessionStorage.removeItem(key);
1576
+ }
1577
+ function navigateTo(url, options = {}) {
1578
+ if (typeof window === "undefined") return;
1579
+ const { replace = false, state, newTab = false } = options;
1580
+ if (newTab) {
1581
+ window.open(url, "_blank");
1582
+ return;
1583
+ }
1584
+ if (url.startsWith("http://") || url.startsWith("https://")) {
1585
+ if (replace) {
1586
+ window.location.replace(url);
1587
+ } else {
1588
+ window.location.href = url;
1589
+ }
1590
+ return;
1591
+ }
1592
+ if (url.startsWith("#")) {
1593
+ window.location.hash = url;
1594
+ return;
1595
+ }
1596
+ if (replace) {
1597
+ window.history.replaceState(state, "", url);
1598
+ } else {
1599
+ window.history.pushState(state, "", url);
1600
+ }
1601
+ window.dispatchEvent(new PopStateEvent("popstate", { state }));
1602
+ }
1603
+ function goBack() {
1604
+ if (typeof window === "undefined") return;
1605
+ window.history.back();
1606
+ }
1607
+ function goForward() {
1608
+ if (typeof window === "undefined") return;
1609
+ window.history.forward();
1610
+ }
1611
+ function getUrlInfo() {
1612
+ if (typeof window === "undefined") {
1613
+ return {
1614
+ href: "",
1615
+ pathname: "",
1616
+ search: "",
1617
+ hash: "",
1618
+ searchParams: {}
1619
+ };
1620
+ }
1621
+ const searchParams = {};
1622
+ const urlSearchParams = new URLSearchParams(window.location.search);
1623
+ urlSearchParams.forEach((value, key) => {
1624
+ searchParams[key] = value;
1625
+ });
1626
+ return {
1627
+ href: window.location.href,
1628
+ pathname: window.location.pathname,
1629
+ search: window.location.search,
1630
+ hash: window.location.hash,
1631
+ searchParams
1632
+ };
1633
+ }
1634
+
1635
+ // src/eventSystem.ts
1636
+ var builtInPlugins = {
1637
+ /**
1638
+ * Set state value
1639
+ * Mirrors: state-setState from actions.ts
1640
+ */
1641
+ "state-setState": (action, ctx) => {
1642
+ const { stateConfig } = action.config || {};
1643
+ if (!stateConfig || !ctx.stateManager) return;
1644
+ const config = typeof stateConfig === "string" ? JSON.parse(stateConfig) : stateConfig;
1645
+ const { key, value, operation = "set" } = config;
1646
+ if (!key) return;
1647
+ switch (operation) {
1648
+ case "set":
1649
+ ctx.stateManager.set(key, value, ctx.elementId);
1650
+ break;
1651
+ case "merge":
1652
+ ctx.stateManager.merge(key, value, false, ctx.elementId);
1653
+ break;
1654
+ case "deepMerge":
1655
+ ctx.stateManager.merge(key, value, true, ctx.elementId);
1656
+ break;
1657
+ case "toggle":
1658
+ ctx.stateManager.toggle(key, ctx.elementId);
1659
+ break;
1660
+ case "append":
1661
+ ctx.stateManager.append(key, value, ctx.elementId);
1662
+ break;
1663
+ case "prepend":
1664
+ ctx.stateManager.prepend(key, value, ctx.elementId);
1665
+ break;
1666
+ case "delete":
1667
+ ctx.stateManager.delete(key, ctx.elementId);
1668
+ break;
1669
+ case "increment":
1670
+ const currentVal = ctx.stateManager.get(key) || 0;
1671
+ ctx.stateManager.set(key, currentVal + (value || 1), ctx.elementId);
1672
+ break;
1673
+ case "decrement":
1674
+ const currVal = ctx.stateManager.get(key) || 0;
1675
+ ctx.stateManager.set(key, currVal - (value || 1), ctx.elementId);
1676
+ break;
1677
+ }
1678
+ return { success: true, key, operation };
1679
+ },
1680
+ /**
1681
+ * Navigate to URL
1682
+ * Mirrors: navigateTo from actions.ts
1683
+ */
1684
+ "navigateTo": (action, ctx) => {
1685
+ const { url, replace, newTab, state } = action.config || {};
1686
+ if (!url) return;
1687
+ navigateTo(url, { replace, newTab, state });
1688
+ return { success: true, url };
1689
+ },
1690
+ /**
1691
+ * Set localStorage value
1692
+ */
1693
+ "localStorage-set": (action, ctx) => {
1694
+ const { key, value } = action.config || {};
1695
+ if (!key) return;
1696
+ setLocalStorage(key, value);
1697
+ return { success: true, key };
1698
+ },
1699
+ /**
1700
+ * Get localStorage value
1701
+ */
1702
+ "localStorage-get": (action, ctx) => {
1703
+ const { key, defaultValue } = action.config || {};
1704
+ if (!key) return defaultValue;
1705
+ return getLocalStorage(key, defaultValue);
1706
+ },
1707
+ /**
1708
+ * Remove localStorage value
1709
+ */
1710
+ "localStorage-remove": (action, ctx) => {
1711
+ const { key } = action.config || {};
1712
+ if (!key) return;
1713
+ if (typeof localStorage !== "undefined") {
1714
+ localStorage.removeItem(key);
1715
+ }
1716
+ return { success: true, key };
1717
+ },
1718
+ /**
1719
+ * Set sessionStorage value
1720
+ */
1721
+ "sessionStorage-set": (action, ctx) => {
1722
+ const { key, value } = action.config || {};
1723
+ if (!key) return;
1724
+ setSessionStorage(key, value);
1725
+ return { success: true, key };
1726
+ },
1727
+ /**
1728
+ * Get sessionStorage value
1729
+ */
1730
+ "sessionStorage-get": (action, ctx) => {
1731
+ const { key, defaultValue } = action.config || {};
1732
+ if (!key) return defaultValue;
1733
+ return getSessionStorage(key, defaultValue);
1734
+ },
1735
+ /**
1736
+ * Console log (for debugging)
1737
+ */
1738
+ "console-log": (action, ctx) => {
1739
+ const { message, data } = action.config || {};
1740
+ console.log("[Servly]", message, data);
1741
+ return { success: true };
1742
+ },
1743
+ /**
1744
+ * Show alert
1745
+ */
1746
+ "alert": (action, ctx) => {
1747
+ const { message } = action.config || {};
1748
+ if (typeof alert !== "undefined") {
1749
+ alert(message);
1750
+ }
1751
+ return { success: true };
1752
+ },
1753
+ /**
1754
+ * Copy to clipboard
1755
+ */
1756
+ "clipboard-copy": async (action, ctx) => {
1757
+ const { text } = action.config || {};
1758
+ if (!text || typeof navigator === "undefined") return { success: false };
1759
+ try {
1760
+ await navigator.clipboard.writeText(text);
1761
+ return { success: true };
1762
+ } catch (error) {
1763
+ return { success: false, error };
1764
+ }
1765
+ },
1766
+ /**
1767
+ * Scroll to element
1768
+ */
1769
+ "scrollTo": (action, ctx) => {
1770
+ const { selector, behavior = "smooth", block = "start" } = action.config || {};
1771
+ if (!selector || typeof document === "undefined") return;
1772
+ const element = document.querySelector(selector);
1773
+ if (element) {
1774
+ element.scrollIntoView({ behavior, block });
1775
+ return { success: true };
1776
+ }
1777
+ return { success: false, error: "Element not found" };
1778
+ },
1779
+ /**
1780
+ * Focus element
1781
+ */
1782
+ "focus": (action, ctx) => {
1783
+ const { selector } = action.config || {};
1784
+ if (!selector || typeof document === "undefined") return;
1785
+ const element = document.querySelector(selector);
1786
+ if (element && typeof element.focus === "function") {
1787
+ element.focus();
1788
+ return { success: true };
1789
+ }
1790
+ return { success: false, error: "Element not found" };
1791
+ },
1792
+ /**
1793
+ * Blur element
1794
+ */
1795
+ "blur": (action, ctx) => {
1796
+ const { selector } = action.config || {};
1797
+ if (!selector || typeof document === "undefined") return;
1798
+ const element = document.querySelector(selector);
1799
+ if (element && typeof element.blur === "function") {
1800
+ element.blur();
1801
+ return { success: true };
1802
+ }
1803
+ return { success: false, error: "Element not found" };
1804
+ },
1805
+ /**
1806
+ * Add class to element
1807
+ */
1808
+ "addClass": (action, ctx) => {
1809
+ const { selector, className } = action.config || {};
1810
+ if (!selector || !className || typeof document === "undefined") return;
1811
+ const element = document.querySelector(selector);
1812
+ if (element) {
1813
+ element.classList.add(className);
1814
+ return { success: true };
1815
+ }
1816
+ return { success: false, error: "Element not found" };
1817
+ },
1818
+ /**
1819
+ * Remove class from element
1820
+ */
1821
+ "removeClass": (action, ctx) => {
1822
+ const { selector, className } = action.config || {};
1823
+ if (!selector || !className || typeof document === "undefined") return;
1824
+ const element = document.querySelector(selector);
1825
+ if (element) {
1826
+ element.classList.remove(className);
1827
+ return { success: true };
1828
+ }
1829
+ return { success: false, error: "Element not found" };
1830
+ },
1831
+ /**
1832
+ * Toggle class on element
1833
+ */
1834
+ "toggleClass": (action, ctx) => {
1835
+ const { selector, className } = action.config || {};
1836
+ if (!selector || !className || typeof document === "undefined") return;
1837
+ const element = document.querySelector(selector);
1838
+ if (element) {
1839
+ element.classList.toggle(className);
1840
+ return { success: true };
1841
+ }
1842
+ return { success: false, error: "Element not found" };
1843
+ },
1844
+ /**
1845
+ * Set element attribute
1846
+ */
1847
+ "setAttribute": (action, ctx) => {
1848
+ const { selector, attribute, value } = action.config || {};
1849
+ if (!selector || !attribute || typeof document === "undefined") return;
1850
+ const element = document.querySelector(selector);
1851
+ if (element) {
1852
+ element.setAttribute(attribute, value);
1853
+ return { success: true };
1854
+ }
1855
+ return { success: false, error: "Element not found" };
1856
+ },
1857
+ /**
1858
+ * Remove element attribute
1859
+ */
1860
+ "removeAttribute": (action, ctx) => {
1861
+ const { selector, attribute } = action.config || {};
1862
+ if (!selector || !attribute || typeof document === "undefined") return;
1863
+ const element = document.querySelector(selector);
1864
+ if (element) {
1865
+ element.removeAttribute(attribute);
1866
+ return { success: true };
1867
+ }
1868
+ return { success: false, error: "Element not found" };
1869
+ },
1870
+ /**
1871
+ * Dispatch custom event
1872
+ */
1873
+ "dispatchEvent": (action, ctx) => {
1874
+ const { selector, eventName, detail } = action.config || {};
1875
+ if (!eventName || typeof document === "undefined") return;
1876
+ const target = selector ? document.querySelector(selector) : document;
1877
+ if (target) {
1878
+ const event = new CustomEvent(eventName, { detail, bubbles: true });
1879
+ target.dispatchEvent(event);
1880
+ return { success: true };
1881
+ }
1882
+ return { success: false, error: "Target not found" };
1883
+ },
1884
+ /**
1885
+ * Delay execution
1886
+ */
1887
+ "delay": async (action, ctx) => {
1888
+ const { ms = 0 } = action.config || {};
1889
+ await new Promise((resolve) => setTimeout(resolve, ms));
1890
+ return { success: true };
1891
+ },
1892
+ /**
1893
+ * Conditional execution
1894
+ */
1895
+ "condition": (action, ctx) => {
1896
+ const { condition, thenActions, elseActions } = action.config || {};
1897
+ return { condition, thenActions, elseActions };
1898
+ }
1899
+ };
1900
+ var EventSystem = class {
1901
+ constructor(config = {}) {
1902
+ this.debounceTimers = /* @__PURE__ */ new Map();
1903
+ this.throttleTimers = /* @__PURE__ */ new Map();
1904
+ this.config = config;
1905
+ this.pluginExecutors = {
1906
+ ...builtInPlugins,
1907
+ ...config.pluginExecutors
1908
+ };
1909
+ }
1910
+ /**
1911
+ * Register a custom plugin executor
1912
+ */
1913
+ registerPlugin(key, executor) {
1914
+ this.pluginExecutors[key] = executor;
1915
+ }
1916
+ /**
1917
+ * Create an event handler from Servly plugin format
1918
+ */
1919
+ createHandler(elementId, handlerConfig, context, options = {}) {
1920
+ return (event) => {
1921
+ if (handlerConfig.preventDefault || options.preventDefault) {
1922
+ event.preventDefault();
1923
+ }
1924
+ if (handlerConfig.stopPropagation || options.stopPropagation) {
1925
+ event.stopPropagation();
1926
+ }
1927
+ if (this.config.onEvent) {
1928
+ this.config.onEvent(event.type, elementId, event);
1929
+ }
1930
+ if (handlerConfig.plugins && handlerConfig.plugins.length > 0) {
1931
+ this.executePlugins(handlerConfig.plugins, {
1932
+ event,
1933
+ elementId,
1934
+ context,
1935
+ stateManager: this.config.stateManager
1936
+ });
1937
+ }
1938
+ };
1939
+ }
1940
+ /**
1941
+ * Execute a sequence of plugin actions
1942
+ */
1943
+ async executePlugins(plugins, eventContext) {
1944
+ const results = [];
1945
+ for (const action of plugins) {
1946
+ try {
1947
+ const executor = this.pluginExecutors[action.key];
1948
+ if (executor) {
1949
+ const result = await executor(action, eventContext);
1950
+ results.push(result);
1951
+ if (this.config.onPluginExecute) {
1952
+ this.config.onPluginExecute(action, result);
1953
+ }
1954
+ } else {
1955
+ console.warn(`[EventSystem] Unknown plugin: ${action.key}`);
1956
+ results.push({ error: `Unknown plugin: ${action.key}` });
1957
+ }
1958
+ } catch (error) {
1959
+ console.error(`[EventSystem] Plugin error (${action.key}):`, error);
1960
+ results.push({ error });
1961
+ }
1962
+ }
1963
+ return results;
1964
+ }
1965
+ /**
1966
+ * Create a debounced event handler
1967
+ */
1968
+ createDebouncedHandler(elementId, handler, delay) {
1969
+ const key = `${elementId}-debounce`;
1970
+ return (event) => {
1971
+ const existingTimer = this.debounceTimers.get(key);
1972
+ if (existingTimer) {
1973
+ clearTimeout(existingTimer);
1974
+ }
1975
+ const timer = setTimeout(() => {
1976
+ handler(event);
1977
+ this.debounceTimers.delete(key);
1978
+ }, delay);
1979
+ this.debounceTimers.set(key, timer);
1980
+ };
1981
+ }
1982
+ /**
1983
+ * Create a throttled event handler
1984
+ */
1985
+ createThrottledHandler(elementId, handler, delay) {
1986
+ const key = `${elementId}-throttle`;
1987
+ return (event) => {
1988
+ if (this.throttleTimers.get(key)) {
1989
+ return;
1990
+ }
1991
+ handler(event);
1992
+ this.throttleTimers.set(key, true);
1993
+ setTimeout(() => {
1994
+ this.throttleTimers.delete(key);
1995
+ }, delay);
1996
+ };
1997
+ }
1998
+ /**
1999
+ * Clean up all timers
2000
+ */
2001
+ destroy() {
2002
+ for (const timer of this.debounceTimers.values()) {
2003
+ clearTimeout(timer);
2004
+ }
2005
+ this.debounceTimers.clear();
2006
+ this.throttleTimers.clear();
2007
+ }
2008
+ };
2009
+ var EVENT_HANDLERS = {
2010
+ onClick: "click",
2011
+ onDoubleClick: "dblclick",
2012
+ onMouseDown: "mousedown",
2013
+ onMouseUp: "mouseup",
2014
+ onMouseEnter: "mouseenter",
2015
+ onMouseLeave: "mouseleave",
2016
+ onMouseMove: "mousemove",
2017
+ onMouseOver: "mouseover",
2018
+ onMouseOut: "mouseout",
2019
+ onKeyDown: "keydown",
2020
+ onKeyUp: "keyup",
2021
+ onKeyPress: "keypress",
2022
+ onFocus: "focus",
2023
+ onBlur: "blur",
2024
+ onChange: "change",
2025
+ onInput: "input",
2026
+ onSubmit: "submit",
2027
+ onReset: "reset",
2028
+ onScroll: "scroll",
2029
+ onWheel: "wheel",
2030
+ onDragStart: "dragstart",
2031
+ onDrag: "drag",
2032
+ onDragEnd: "dragend",
2033
+ onDragEnter: "dragenter",
2034
+ onDragLeave: "dragleave",
2035
+ onDragOver: "dragover",
2036
+ onDrop: "drop",
2037
+ onTouchStart: "touchstart",
2038
+ onTouchMove: "touchmove",
2039
+ onTouchEnd: "touchend",
2040
+ onTouchCancel: "touchcancel",
2041
+ onContextMenu: "contextmenu",
2042
+ onCopy: "copy",
2043
+ onCut: "cut",
2044
+ onPaste: "paste",
2045
+ onLoad: "load",
2046
+ onError: "error",
2047
+ onAnimationStart: "animationstart",
2048
+ onAnimationEnd: "animationend",
2049
+ onAnimationIteration: "animationiteration",
2050
+ onTransitionEnd: "transitionend"
2051
+ };
2052
+ function toDomEventName(reactEventName) {
2053
+ return EVENT_HANDLERS[reactEventName] || reactEventName.replace(/^on/, "").toLowerCase();
2054
+ }
2055
+ function toReactEventName(domEventName) {
2056
+ const entry = Object.entries(EVENT_HANDLERS).find(([, dom]) => dom === domEventName);
2057
+ return entry ? entry[0] : `on${domEventName.charAt(0).toUpperCase()}${domEventName.slice(1)}`;
2058
+ }
2059
+ var defaultEventSystem = null;
2060
+ function getEventSystem(config) {
2061
+ if (!defaultEventSystem || config) {
2062
+ defaultEventSystem = new EventSystem(config);
2063
+ }
2064
+ return defaultEventSystem;
2065
+ }
2066
+ function resetEventSystem() {
2067
+ if (defaultEventSystem) {
2068
+ defaultEventSystem.destroy();
2069
+ defaultEventSystem = null;
2070
+ }
2071
+ }
2072
+
2073
+ // src/overrides.ts
2074
+ var OverrideSystem = class {
2075
+ constructor(config = {}) {
2076
+ this.elementStates = /* @__PURE__ */ new Map();
2077
+ this.watchIntervals = /* @__PURE__ */ new Map();
2078
+ this.config = config;
2079
+ }
2080
+ /**
2081
+ * Get overrides from an element
2082
+ */
2083
+ getOverrides(element) {
2084
+ const rawOverrides = element.configuration?._overrides_;
2085
+ if (!rawOverrides) return [];
2086
+ if (Array.isArray(rawOverrides)) {
2087
+ return rawOverrides.filter((o) => o && typeof o === "object");
2088
+ }
2089
+ if (typeof rawOverrides === "object") {
2090
+ console.warn("[OverrideSystem] Legacy object override format detected, ignoring");
2091
+ return [];
2092
+ }
2093
+ return [];
2094
+ }
2095
+ /**
2096
+ * Initialize overrides for an element (called on mount)
2097
+ */
2098
+ async initializeElement(element, context) {
2099
+ const elementId = element.i;
2100
+ const overrides = this.getOverrides(element);
2101
+ if (overrides.length === 0) return;
2102
+ const state = {
2103
+ previousValues: /* @__PURE__ */ new Map(),
2104
+ initialized: false,
2105
+ abortController: new AbortController()
2106
+ };
2107
+ this.elementStates.set(elementId, state);
2108
+ const mountOverrides = overrides.filter((o) => !o.isCleanUp);
2109
+ overrides.forEach((override, index) => {
2110
+ if (override.isCleanUp) return;
2111
+ if (!override.dependencies || override.dependencies.length === 0) return;
2112
+ const initialValues = override.dependencies.map(
2113
+ (dep) => resolveTemplate(dep, context)
2114
+ );
2115
+ state.previousValues.set(index, initialValues);
2116
+ });
2117
+ await this.processOverrides(elementId, mountOverrides, context, "onMount");
2118
+ state.initialized = true;
2119
+ }
2120
+ /**
2121
+ * Check and process dependency changes for an element
2122
+ */
2123
+ async checkDependencies(element, context) {
2124
+ const elementId = element.i;
2125
+ const overrides = this.getOverrides(element);
2126
+ const state = this.elementStates.get(elementId);
2127
+ if (!state || !state.initialized) return;
2128
+ const overridesToTrigger = [];
2129
+ overrides.forEach((override, index) => {
2130
+ if (override.isCleanUp) return;
2131
+ if (!override.dependencies || override.dependencies.length === 0) return;
2132
+ const currentValues = override.dependencies.map(
2133
+ (dep) => resolveTemplate(dep, context)
2134
+ );
2135
+ const previousValues = state.previousValues.get(index) || [];
2136
+ let hasChanged = false;
2137
+ if (previousValues.length !== currentValues.length) {
2138
+ hasChanged = true;
2139
+ } else {
2140
+ for (let i = 0; i < currentValues.length; i++) {
2141
+ if (JSON.stringify(previousValues[i]) !== JSON.stringify(currentValues[i])) {
2142
+ hasChanged = true;
2143
+ break;
2144
+ }
2145
+ }
2146
+ }
2147
+ if (hasChanged) {
2148
+ overridesToTrigger.push(override);
2149
+ state.previousValues.set(index, currentValues);
2150
+ if (this.config.onDependencyChange) {
2151
+ this.config.onDependencyChange(elementId, override.dependencies, currentValues);
2152
+ }
2153
+ }
2154
+ });
2155
+ if (overridesToTrigger.length > 0) {
2156
+ await this.processOverrides(elementId, overridesToTrigger, context, "onDependencyChange");
2157
+ }
2158
+ }
2159
+ /**
2160
+ * Cleanup overrides for an element (called on unmount)
2161
+ */
2162
+ async cleanupElement(element, context) {
2163
+ const elementId = element.i;
2164
+ const overrides = this.getOverrides(element);
2165
+ const state = this.elementStates.get(elementId);
2166
+ if (state?.abortController) {
2167
+ state.abortController.abort();
2168
+ }
2169
+ this.stopWatching(elementId);
2170
+ const cleanupOverrides = overrides.filter((o) => o.isCleanUp);
2171
+ if (cleanupOverrides.length > 0) {
2172
+ await this.processOverrides(elementId, cleanupOverrides, context, "onUnmount");
2173
+ }
2174
+ this.elementStates.delete(elementId);
2175
+ }
2176
+ /**
2177
+ * Process a list of overrides
2178
+ */
2179
+ async processOverrides(elementId, overrides, context, eventType) {
2180
+ if (!overrides || overrides.length === 0) return;
2181
+ const validOverrides = overrides.filter((o) => o.plugins && o.plugins.length > 0);
2182
+ if (validOverrides.length === 0) return;
2183
+ const results = await Promise.allSettled(
2184
+ validOverrides.map(async (override) => {
2185
+ if (this.config.onOverrideTrigger) {
2186
+ this.config.onOverrideTrigger(elementId, override, eventType);
2187
+ }
2188
+ if (this.config.eventSystem && override.plugins) {
2189
+ const eventContext = {
2190
+ event: new CustomEvent(eventType),
2191
+ elementId,
2192
+ context,
2193
+ stateManager: this.config.stateManager
2194
+ };
2195
+ return this.config.eventSystem.executePlugins(override.plugins, eventContext);
2196
+ }
2197
+ return null;
2198
+ })
2199
+ );
2200
+ results.forEach((result, index) => {
2201
+ if (result.status === "rejected") {
2202
+ console.error(`[OverrideSystem] Override ${index} failed:`, result.reason);
2203
+ }
2204
+ });
2205
+ }
2206
+ /**
2207
+ * Start watching an element for dependency changes
2208
+ */
2209
+ startWatching(element, context, intervalMs = 100) {
2210
+ const elementId = element.i;
2211
+ this.stopWatching(elementId);
2212
+ const overrides = this.getOverrides(element);
2213
+ const hasDependencies = overrides.some(
2214
+ (o) => !o.isCleanUp && o.dependencies && o.dependencies.length > 0
2215
+ );
2216
+ if (!hasDependencies) return;
2217
+ const interval = setInterval(() => {
2218
+ this.checkDependencies(element, context);
2219
+ }, intervalMs);
2220
+ this.watchIntervals.set(elementId, interval);
2221
+ }
2222
+ /**
2223
+ * Stop watching an element
2224
+ */
2225
+ stopWatching(elementId) {
2226
+ const interval = this.watchIntervals.get(elementId);
2227
+ if (interval) {
2228
+ clearInterval(interval);
2229
+ this.watchIntervals.delete(elementId);
2230
+ }
2231
+ }
2232
+ /**
2233
+ * Destroy the override system
2234
+ */
2235
+ destroy() {
2236
+ for (const interval of this.watchIntervals.values()) {
2237
+ clearInterval(interval);
2238
+ }
2239
+ this.watchIntervals.clear();
2240
+ for (const state of this.elementStates.values()) {
2241
+ if (state.abortController) {
2242
+ state.abortController.abort();
2243
+ }
2244
+ }
2245
+ this.elementStates.clear();
2246
+ }
2247
+ };
2248
+ function extractOverrideDependencies(element) {
2249
+ const overrides = element.configuration?._overrides_;
2250
+ if (!Array.isArray(overrides)) return [];
2251
+ const dependencies = [];
2252
+ for (const override of overrides) {
2253
+ if (override?.dependencies && Array.isArray(override.dependencies)) {
2254
+ dependencies.push(...override.dependencies);
2255
+ }
2256
+ }
2257
+ return [...new Set(dependencies)];
2258
+ }
2259
+ function hasOverrides(element) {
2260
+ const overrides = element.configuration?._overrides_;
2261
+ return Array.isArray(overrides) && overrides.length > 0;
2262
+ }
2263
+ function hasDependencyOverrides(element) {
2264
+ const overrides = element.configuration?._overrides_;
2265
+ if (!Array.isArray(overrides)) return false;
2266
+ return overrides.some(
2267
+ (o) => !o?.isCleanUp && o?.dependencies && o.dependencies.length > 0
2268
+ );
2269
+ }
2270
+ function getMountOverrides(element) {
2271
+ const overrides = element.configuration?._overrides_;
2272
+ if (!Array.isArray(overrides)) return [];
2273
+ return overrides.filter((o) => o && !o.isCleanUp);
2274
+ }
2275
+ function getCleanupOverrides(element) {
2276
+ const overrides = element.configuration?._overrides_;
2277
+ if (!Array.isArray(overrides)) return [];
2278
+ return overrides.filter((o) => o && o.isCleanUp);
2279
+ }
2280
+ var defaultOverrideSystem = null;
2281
+ function getOverrideSystem(config) {
2282
+ if (!defaultOverrideSystem || config) {
2283
+ defaultOverrideSystem = new OverrideSystem(config);
2284
+ }
2285
+ return defaultOverrideSystem;
2286
+ }
2287
+ function resetOverrideSystem() {
2288
+ if (defaultOverrideSystem) {
2289
+ defaultOverrideSystem.destroy();
2290
+ defaultOverrideSystem = null;
2291
+ }
2292
+ }
2293
+
2294
+ // src/renderer.ts
2295
+ var COMPONENT_TO_TAG = {
2296
+ container: "div",
2297
+ text: "span",
2298
+ button: "button",
2299
+ input: "input",
2300
+ image: "img",
2301
+ link: "a",
2302
+ form: "form",
2303
+ label: "label",
2304
+ textarea: "textarea",
2305
+ select: "select",
2306
+ option: "option",
2307
+ list: "ul",
2308
+ listItem: "li",
2309
+ heading: "h1",
2310
+ paragraph: "p",
2311
+ section: "section",
2312
+ article: "article",
2313
+ header: "header",
2314
+ footer: "footer",
2315
+ nav: "nav",
2316
+ aside: "aside",
2317
+ main: "main",
2318
+ span: "span",
2319
+ div: "div"
2320
+ };
2321
+ var SELF_CLOSING_TAGS = /* @__PURE__ */ new Set([
2322
+ "input",
2323
+ "img",
2324
+ "br",
2325
+ "hr",
2326
+ "area",
2327
+ "base",
2328
+ "col",
2329
+ "embed",
2330
+ "link",
2331
+ "meta",
2332
+ "param",
2333
+ "source",
2334
+ "track",
2335
+ "wbr"
2336
+ ]);
2337
+ function getElementTag(element) {
2338
+ const config = element.configuration;
2339
+ if (config?.tag) {
2340
+ return config.tag;
2341
+ }
2342
+ if (element.componentId && COMPONENT_TO_TAG[element.componentId]) {
2343
+ return COMPONENT_TO_TAG[element.componentId];
2344
+ }
2345
+ if (element.isGroup) {
2346
+ return "div";
2347
+ }
2348
+ return "div";
2349
+ }
2350
+ function isSelfClosing(tag) {
2351
+ return SELF_CLOSING_TAGS.has(tag.toLowerCase());
2352
+ }
2353
+ function buildTree(elements) {
2354
+ const tree = /* @__PURE__ */ new Map();
2355
+ for (const element of elements) {
2356
+ const parentId = element.parent || null;
2357
+ if (!tree.has(parentId)) {
2358
+ tree.set(parentId, []);
2359
+ }
2360
+ tree.get(parentId).push(element);
2361
+ }
2362
+ return tree;
2363
+ }
2364
+ function getTextContent(element, context) {
2365
+ const config = element.configuration;
2366
+ if (config?.dynamicText) {
2367
+ return resolveTemplate(config.dynamicText, context);
2368
+ }
2369
+ if (config?.text) {
2370
+ if (hasTemplateSyntax(config.text)) {
2371
+ return resolveTemplate(config.text, context);
2372
+ }
2373
+ return config.text;
2374
+ }
2375
+ return "";
2376
+ }
2377
+ function applyAttributes(domElement, element, context) {
2378
+ const config = element.configuration || {};
2379
+ const attributeMap = [
2380
+ { key: "id", attr: "id" },
2381
+ { key: "src", dynamicKey: "dynamicSrc", attr: "src" },
2382
+ { key: "alt", attr: "alt" },
2383
+ { key: "href", dynamicKey: "dynamicHref", attr: "href" },
2384
+ { key: "target", attr: "target" },
2385
+ { key: "placeholder", attr: "placeholder" },
2386
+ { key: "type", attr: "type" },
2387
+ { key: "name", attr: "name" },
2388
+ { key: "value", dynamicKey: "dynamicValue", attr: "value" }
2389
+ ];
2390
+ for (const { key, dynamicKey, attr } of attributeMap) {
2391
+ const dynamicValue = dynamicKey ? config[dynamicKey] : void 0;
2392
+ if (dynamicValue !== void 0 && dynamicValue !== null && dynamicValue !== "") {
2393
+ const resolved = resolveTemplate(String(dynamicValue), context);
2394
+ if (resolved) {
2395
+ domElement.setAttribute(attr, resolved);
2396
+ }
2397
+ continue;
2398
+ }
2399
+ const staticValue = config[key];
2400
+ if (staticValue !== void 0 && staticValue !== null && staticValue !== "") {
2401
+ const resolved = hasTemplateSyntax(String(staticValue)) ? resolveTemplate(String(staticValue), context) : String(staticValue);
2402
+ if (resolved) {
2403
+ domElement.setAttribute(attr, resolved);
2404
+ }
2405
+ }
2406
+ }
2407
+ if (config.disabled) domElement.setAttribute("disabled", "");
2408
+ if (config.required) domElement.setAttribute("required", "");
2409
+ if (config.readOnly) domElement.setAttribute("readonly", "");
2410
+ for (const [key, value] of Object.entries(config)) {
2411
+ if (key.startsWith("data-") && value !== void 0) {
2412
+ const resolved = hasTemplateSyntax(String(value)) ? resolveTemplate(String(value), context) : String(value);
2413
+ domElement.setAttribute(key, resolved);
2414
+ }
2415
+ }
2416
+ for (const [key, value] of Object.entries(config)) {
2417
+ if (key.startsWith("aria-") && value !== void 0) {
2418
+ const resolved = hasTemplateSyntax(String(value)) ? resolveTemplate(String(value), context) : String(value);
2419
+ domElement.setAttribute(key, resolved);
2420
+ }
2421
+ }
2422
+ }
2423
+ function resolveFunctionBinding(binding, context) {
2424
+ if (binding.source === "props" || binding.source === "parent") {
2425
+ const path = binding.path;
2426
+ if (!path) return void 0;
2427
+ const parts = path.split(".");
2428
+ let value = context.props;
2429
+ for (const part of parts) {
2430
+ if (value === null || value === void 0) return void 0;
2431
+ value = value[part];
2432
+ }
2433
+ if (typeof value === "function") {
2434
+ return value;
2435
+ }
2436
+ return void 0;
2437
+ }
2438
+ if (binding.source === "static" && typeof binding.value === "function") {
2439
+ return binding.value;
2440
+ }
2441
+ return void 0;
2442
+ }
2443
+ function attachEventHandlers(domElement, element, eventHandlers, context, elementState, isRootElement = false, state) {
2444
+ const elementId = element.i;
1223
2445
  if (eventHandlers && eventHandlers[elementId]) {
1224
2446
  const handlers = eventHandlers[elementId];
1225
2447
  for (const [eventName, handler] of Object.entries(handlers)) {
@@ -1228,6 +2450,28 @@ function attachEventHandlers(domElement, element, eventHandlers, context, elemen
1228
2450
  domElement.addEventListener(domEventName, handler);
1229
2451
  }
1230
2452
  }
2453
+ const config = element.configuration || {};
2454
+ for (const eventPropName of Object.keys(EVENT_HANDLERS)) {
2455
+ const handlerConfig = config[eventPropName];
2456
+ if (handlerConfig && handlerConfig.plugins && handlerConfig.plugins.length > 0) {
2457
+ const domEventName = toDomEventName(eventPropName);
2458
+ if (!elementState.eventListeners.has(domEventName)) {
2459
+ if (state?.eventSystem) {
2460
+ const handler = state.eventSystem.createHandler(elementId, handlerConfig, context);
2461
+ elementState.eventListeners.set(domEventName, handler);
2462
+ domElement.addEventListener(domEventName, handler);
2463
+ } else {
2464
+ const handler = (e) => {
2465
+ if (handlerConfig.preventDefault) e.preventDefault();
2466
+ if (handlerConfig.stopPropagation) e.stopPropagation();
2467
+ console.log(`[Servly] Event ${eventPropName} triggered on ${elementId}`, handlerConfig.plugins);
2468
+ };
2469
+ elementState.eventListeners.set(domEventName, handler);
2470
+ domElement.addEventListener(domEventName, handler);
2471
+ }
2472
+ }
2473
+ }
2474
+ }
1231
2475
  const bindings = element.configuration?.bindings?.inputs;
1232
2476
  if (bindings) {
1233
2477
  for (const [propName, binding] of Object.entries(bindings)) {
@@ -1262,10 +2506,16 @@ function detachEventHandlers(elementState) {
1262
2506
  }
1263
2507
  elementState.eventListeners.clear();
1264
2508
  }
1265
- function createElement(element, context, eventHandlers, isRootElement = false) {
2509
+ function createElement(element, context, eventHandlers, isRootElement = false, state) {
1266
2510
  const tag = getElementTag(element);
1267
2511
  const domElement = document.createElement(tag);
1268
2512
  domElement.setAttribute("data-servly-id", element.i);
2513
+ const slotName = element.slotName || element.configuration?.slotName;
2514
+ if (element.componentId === "slot" || slotName) {
2515
+ const name = slotName || element.i;
2516
+ domElement.setAttribute("data-slot", name);
2517
+ domElement.setAttribute("data-servly-slot", "true");
2518
+ }
1269
2519
  const styles = buildElementStyles(element, context);
1270
2520
  applyStyles(domElement, styles);
1271
2521
  const className = buildClassName(element, context);
@@ -1288,7 +2538,7 @@ function createElement(element, context, eventHandlers, isRootElement = false) {
1288
2538
  textContent,
1289
2539
  eventListeners: /* @__PURE__ */ new Map()
1290
2540
  };
1291
- attachEventHandlers(domElement, element, eventHandlers, context, elementState, isRootElement);
2541
+ attachEventHandlers(domElement, element, eventHandlers, context, elementState, isRootElement, state);
1292
2542
  return elementState;
1293
2543
  }
1294
2544
  var globalRenderingStack = /* @__PURE__ */ new Set();
@@ -1349,8 +2599,81 @@ function renderComponentRef(element, container, context, state) {
1349
2599
  globalRenderingStack.delete(refId);
1350
2600
  }
1351
2601
  }
2602
+ function extractViewId(binding) {
2603
+ if (!binding) return null;
2604
+ if (typeof binding === "string") return binding;
2605
+ if (binding.source === "static" && typeof binding.value === "string") return binding.value;
2606
+ if (binding.source === "node" && binding.binding?.viewId) return binding.binding.viewId;
2607
+ if (binding.source === "view" && typeof binding.value === "string") return binding.value;
2608
+ if (binding.viewId) return binding.viewId;
2609
+ if (binding.type === "view" && binding.viewId) return binding.viewId;
2610
+ return null;
2611
+ }
2612
+ function resolveComponentViewInputs(bindings, context, parentInputs) {
2613
+ if (!bindings) return {};
2614
+ const resolved = {};
2615
+ for (const [key, binding] of Object.entries(bindings)) {
2616
+ if (!binding) continue;
2617
+ const source = (binding.source || "").toLowerCase();
2618
+ switch (source) {
2619
+ case "static":
2620
+ case "value":
2621
+ case "constant":
2622
+ resolved[key] = binding.value;
2623
+ break;
2624
+ case "state":
2625
+ if (binding.path && context.state) {
2626
+ resolved[key] = getValueByPath2(context.state, binding.path);
2627
+ }
2628
+ break;
2629
+ case "props":
2630
+ case "parent":
2631
+ case "input":
2632
+ if (binding.path) {
2633
+ const source2 = parentInputs || context.props;
2634
+ const resolvedValue = getValueByPath2(source2, binding.path);
2635
+ if (resolvedValue !== void 0) {
2636
+ resolved[key] = resolvedValue;
2637
+ } else {
2638
+ resolved[key] = binding.value;
2639
+ }
2640
+ } else {
2641
+ resolved[key] = binding.value;
2642
+ }
2643
+ break;
2644
+ case "node":
2645
+ if (binding.binding?.viewId) {
2646
+ resolved[key] = binding.binding.viewId;
2647
+ } else if (binding.binding) {
2648
+ resolved[key] = binding.binding;
2649
+ } else {
2650
+ resolved[key] = binding.value;
2651
+ }
2652
+ break;
2653
+ default:
2654
+ resolved[key] = binding.value;
2655
+ }
2656
+ }
2657
+ return resolved;
2658
+ }
2659
+ function getValueByPath2(obj, path) {
2660
+ if (!obj || !path) return void 0;
2661
+ const parts = path.split(".");
2662
+ let current = obj;
2663
+ for (const part of parts) {
2664
+ if (current === null || current === void 0) return void 0;
2665
+ current = current[part];
2666
+ }
2667
+ return current;
2668
+ }
1352
2669
  function renderElement(element, tree, context, eventHandlers, elementStates, state, isRootElement = false) {
1353
- const elementState = createElement(element, context, eventHandlers, isRootElement);
2670
+ if (element.isComponentView) {
2671
+ return renderComponentViewElement(element, tree, context, eventHandlers, elementStates, state, isRootElement);
2672
+ }
2673
+ if (element.componentId === "slot") {
2674
+ return renderSlotElement(element, tree, context, eventHandlers, elementStates, state, isRootElement);
2675
+ }
2676
+ const elementState = createElement(element, context, eventHandlers, isRootElement, state);
1354
2677
  elementStates.set(element.i, elementState);
1355
2678
  const config = element.configuration;
1356
2679
  if (config?.componentViewRef) {
@@ -1367,8 +2690,147 @@ function renderElement(element, tree, context, eventHandlers, elementStates, sta
1367
2690
  }
1368
2691
  return elementState.domElement;
1369
2692
  }
2693
+ function renderComponentViewElement(element, tree, context, eventHandlers, elementStates, state, isRootElement) {
2694
+ const componentViewId = `${element.componentId}-${element.i}`;
2695
+ if (globalRenderingStack.has(componentViewId)) {
2696
+ const placeholder = document.createElement("div");
2697
+ placeholder.className = "border-2 border-red-500 border-dashed p-4 bg-red-50";
2698
+ placeholder.innerHTML = `<p class="text-red-600 text-sm">Recursive component: ${element.componentId}</p>`;
2699
+ return placeholder;
2700
+ }
2701
+ let viewLayout;
2702
+ if (state.views?.has(element.componentId)) {
2703
+ const view = state.views.get(element.componentId);
2704
+ viewLayout = view?.layout;
2705
+ }
2706
+ if (!viewLayout && state.componentRegistry) {
2707
+ const component = state.componentRegistry.get(element.componentId);
2708
+ if (component) {
2709
+ viewLayout = component.layout;
2710
+ }
2711
+ }
2712
+ if (!viewLayout) {
2713
+ const placeholder = document.createElement("div");
2714
+ placeholder.className = "border-2 border-yellow-500 border-dashed p-4 bg-yellow-50";
2715
+ placeholder.innerHTML = `<p class="text-yellow-600 text-sm">Component not found: ${element.componentId}</p>`;
2716
+ return placeholder;
2717
+ }
2718
+ const bindings = element.configuration?.bindings?.inputs || {};
2719
+ const resolvedInputs = resolveComponentViewInputs(bindings, context, context.props);
2720
+ const componentContext = {
2721
+ props: { ...context.props, ...resolvedInputs },
2722
+ state: context.state,
2723
+ context: context.context
2724
+ };
2725
+ globalRenderingStack.add(componentViewId);
2726
+ try {
2727
+ const wrapper = document.createElement("div");
2728
+ wrapper.id = element.i;
2729
+ wrapper.className = "contents";
2730
+ const viewTree = buildTree(viewLayout);
2731
+ const viewRoots = viewLayout.filter((el) => !el.parent || el.parent === null);
2732
+ const nestedState = {
2733
+ ...state,
2734
+ elements: viewLayout,
2735
+ context: componentContext,
2736
+ elementStates: /* @__PURE__ */ new Map(),
2737
+ rootElement: null,
2738
+ renderingStack: new Set(state.renderingStack)
2739
+ };
2740
+ nestedState.renderingStack.add(componentViewId);
2741
+ for (const root of viewRoots) {
2742
+ const rootElement = renderElement(
2743
+ root,
2744
+ viewTree,
2745
+ componentContext,
2746
+ eventHandlers,
2747
+ nestedState.elementStates,
2748
+ nestedState,
2749
+ true
2750
+ );
2751
+ wrapper.appendChild(rootElement);
2752
+ }
2753
+ const elementState = {
2754
+ element,
2755
+ domElement: wrapper,
2756
+ styles: {},
2757
+ className: "contents",
2758
+ textContent: "",
2759
+ eventListeners: /* @__PURE__ */ new Map()
2760
+ };
2761
+ elementStates.set(element.i, elementState);
2762
+ return wrapper;
2763
+ } finally {
2764
+ globalRenderingStack.delete(componentViewId);
2765
+ }
2766
+ }
2767
+ function renderSlotElement(element, tree, context, eventHandlers, elementStates, state, isRootElement) {
2768
+ const elementState = createElement(element, context, eventHandlers, isRootElement, state);
2769
+ elementStates.set(element.i, elementState);
2770
+ const bindings = element.configuration?.bindings?.inputs || {};
2771
+ let childViewId = extractViewId(bindings.child) || extractViewId(bindings.children) || extractViewId(bindings.content);
2772
+ if (!childViewId && context.props) {
2773
+ childViewId = extractViewId(context.props.child) || extractViewId(context.props.children) || extractViewId(context.props.content);
2774
+ }
2775
+ if (childViewId && typeof childViewId === "string") {
2776
+ let viewLayout;
2777
+ if (state.views?.has(childViewId)) {
2778
+ const view = state.views.get(childViewId);
2779
+ viewLayout = view?.layout;
2780
+ }
2781
+ if (!viewLayout && state.componentRegistry) {
2782
+ const component = state.componentRegistry.get(childViewId);
2783
+ if (component) {
2784
+ viewLayout = component.layout;
2785
+ }
2786
+ }
2787
+ if (viewLayout) {
2788
+ const viewTree = buildTree(viewLayout);
2789
+ const viewRoots = viewLayout.filter((el) => !el.parent || el.parent === null);
2790
+ const nestedState = {
2791
+ ...state,
2792
+ elements: viewLayout,
2793
+ elementStates: /* @__PURE__ */ new Map(),
2794
+ rootElement: null
2795
+ };
2796
+ for (const root of viewRoots) {
2797
+ const rootElement = renderElement(
2798
+ root,
2799
+ viewTree,
2800
+ context,
2801
+ eventHandlers,
2802
+ nestedState.elementStates,
2803
+ nestedState,
2804
+ false
2805
+ );
2806
+ elementState.domElement.appendChild(rootElement);
2807
+ }
2808
+ return elementState.domElement;
2809
+ }
2810
+ }
2811
+ const children = tree.get(element.i) || [];
2812
+ for (const child of children) {
2813
+ const childElement = renderElement(child, tree, context, eventHandlers, elementStates, state, false);
2814
+ elementState.domElement.appendChild(childElement);
2815
+ }
2816
+ return elementState.domElement;
2817
+ }
1370
2818
  function render(options) {
1371
- const { container, elements, context, eventHandlers, componentRegistry, onDependencyNeeded } = options;
2819
+ const {
2820
+ container,
2821
+ elements,
2822
+ context,
2823
+ eventHandlers,
2824
+ componentRegistry,
2825
+ onDependencyNeeded,
2826
+ views,
2827
+ enableStateManager,
2828
+ initialState,
2829
+ onStateChange,
2830
+ pluginExecutors,
2831
+ onNavigate,
2832
+ onApiCall
2833
+ } = options;
1372
2834
  const startTime = performance.now();
1373
2835
  const memorySampler = getMemorySampler();
1374
2836
  const longTaskObserver = getLongTaskObserver();
@@ -1377,6 +2839,29 @@ function render(options) {
1377
2839
  const rootElements = elements.filter((el) => !el.parent || el.parent === null);
1378
2840
  const componentId = rootElements[0]?.componentId || "unknown";
1379
2841
  const version = "latest";
2842
+ let stateManager;
2843
+ if (enableStateManager) {
2844
+ stateManager = new StateManager({
2845
+ initialState: initialState || context.state,
2846
+ onStateChange: onStateChange ? (event) => onStateChange({
2847
+ key: event.key,
2848
+ value: event.value,
2849
+ previousValue: event.previousValue
2850
+ }) : void 0
2851
+ });
2852
+ }
2853
+ const eventSystem = new EventSystem({
2854
+ stateManager,
2855
+ pluginExecutors,
2856
+ onNavigate,
2857
+ onApiCall
2858
+ });
2859
+ const overrideSystem = new OverrideSystem({
2860
+ eventSystem,
2861
+ stateManager
2862
+ });
2863
+ const hasAnyOverrides = elements.some((el) => hasOverrides(el));
2864
+ const hasAnyDependencyOverrides = elements.some((el) => hasDependencyOverrides(el));
1380
2865
  try {
1381
2866
  const tree = buildTree(elements);
1382
2867
  const state = {
@@ -1388,7 +2873,12 @@ function render(options) {
1388
2873
  rootElement: null,
1389
2874
  componentRegistry,
1390
2875
  onDependencyNeeded,
1391
- renderingStack: /* @__PURE__ */ new Set()
2876
+ renderingStack: /* @__PURE__ */ new Set(),
2877
+ views,
2878
+ eventSystem,
2879
+ stateManager,
2880
+ overrideSystem,
2881
+ enableOverrides: hasAnyOverrides
1392
2882
  };
1393
2883
  container.innerHTML = "";
1394
2884
  if (rootElements.length === 0) {
@@ -1426,6 +2916,16 @@ function render(options) {
1426
2916
  state.rootElement = wrapper;
1427
2917
  container.appendChild(wrapper);
1428
2918
  }
2919
+ if (hasAnyOverrides && overrideSystem) {
2920
+ for (const element of elements) {
2921
+ if (hasOverrides(element)) {
2922
+ overrideSystem.initializeElement(element, context);
2923
+ if (hasDependencyOverrides(element)) {
2924
+ overrideSystem.startWatching(element, context);
2925
+ }
2926
+ }
2927
+ }
2928
+ }
1429
2929
  const duration = performance.now() - startTime;
1430
2930
  const memoryAfter = memorySampler.sample();
1431
2931
  const longTasks = longTaskObserver.stop();
@@ -1444,6 +2944,7 @@ function render(options) {
1444
2944
  };
1445
2945
  } catch (error) {
1446
2946
  longTaskObserver.stop();
2947
+ overrideSystem.destroy();
1447
2948
  analytics.trackError(componentId, version, error, {
1448
2949
  errorType: "render"
1449
2950
  });
@@ -1474,12 +2975,23 @@ function update(state, newContext) {
1474
2975
  }
1475
2976
  }
1476
2977
  function destroy(state) {
2978
+ if (state.overrideSystem && state.enableOverrides) {
2979
+ for (const element of state.elements) {
2980
+ if (hasOverrides(element)) {
2981
+ state.overrideSystem.cleanupElement(element, state.context);
2982
+ }
2983
+ }
2984
+ state.overrideSystem.destroy();
2985
+ }
1477
2986
  for (const elementState of state.elementStates.values()) {
1478
2987
  detachEventHandlers(elementState);
1479
2988
  if (elementState.nestedRender) {
1480
2989
  elementState.nestedRender.destroy();
1481
2990
  }
1482
2991
  }
2992
+ if (state.eventSystem) {
2993
+ state.eventSystem.destroy();
2994
+ }
1483
2995
  state.elementStates.clear();
1484
2996
  if (state.rootElement && state.rootElement.parentNode) {
1485
2997
  state.rootElement.parentNode.removeChild(state.rootElement);
@@ -1546,6 +3058,200 @@ function renderDynamicList(options) {
1546
3058
  }
1547
3059
  return results;
1548
3060
  }
3061
+ function renderNode(options) {
3062
+ const {
3063
+ container,
3064
+ elements,
3065
+ nodeId,
3066
+ context,
3067
+ includeChildren = true,
3068
+ eventHandlers,
3069
+ componentRegistry,
3070
+ views
3071
+ } = options;
3072
+ const targetNode = elements.find((el) => el.i === nodeId);
3073
+ if (!targetNode) {
3074
+ console.error(`renderNode: Node not found: ${nodeId}`);
3075
+ return null;
3076
+ }
3077
+ if (!includeChildren) {
3078
+ const singleElementLayout = [targetNode];
3079
+ return render({
3080
+ container,
3081
+ elements: singleElementLayout,
3082
+ context,
3083
+ eventHandlers,
3084
+ componentRegistry,
3085
+ views
3086
+ });
3087
+ }
3088
+ const descendantIds = /* @__PURE__ */ new Set();
3089
+ const collectDescendants = (parentId) => {
3090
+ for (const el of elements) {
3091
+ if (el.parent === parentId) {
3092
+ descendantIds.add(el.i);
3093
+ collectDescendants(el.i);
3094
+ }
3095
+ }
3096
+ };
3097
+ descendantIds.add(nodeId);
3098
+ collectDescendants(nodeId);
3099
+ const nodeElements = elements.filter((el) => descendantIds.has(el.i));
3100
+ const rootedElements = nodeElements.map(
3101
+ (el) => el.i === nodeId ? { ...el, parent: null } : el
3102
+ );
3103
+ return render({
3104
+ container,
3105
+ elements: rootedElements,
3106
+ context,
3107
+ eventHandlers,
3108
+ componentRegistry,
3109
+ views
3110
+ });
3111
+ }
3112
+ function renderInShadow(options) {
3113
+ const { container, mode = "open", styles, injectTailwind: shouldInjectTailwind, ...renderOptions } = options;
3114
+ const shadowRoot = container.attachShadow({ mode });
3115
+ const innerContainer = document.createElement("div");
3116
+ innerContainer.className = "servly-shadow-container";
3117
+ shadowRoot.appendChild(innerContainer);
3118
+ if (styles) {
3119
+ const styleEl = document.createElement("style");
3120
+ styleEl.textContent = styles;
3121
+ shadowRoot.insertBefore(styleEl, innerContainer);
3122
+ }
3123
+ if (shouldInjectTailwind) {
3124
+ const tailwindLink = document.createElement("link");
3125
+ tailwindLink.rel = "stylesheet";
3126
+ tailwindLink.href = "https://cdn.tailwindcss.com";
3127
+ shadowRoot.insertBefore(tailwindLink, innerContainer);
3128
+ }
3129
+ const result = render({
3130
+ ...renderOptions,
3131
+ container: innerContainer
3132
+ });
3133
+ return {
3134
+ ...result,
3135
+ shadowRoot
3136
+ };
3137
+ }
3138
+ async function createServlyRenderer(options) {
3139
+ const {
3140
+ container: containerOption,
3141
+ injectTailwind: shouldInjectTailwind = true,
3142
+ tailwindConfig,
3143
+ initialState,
3144
+ onStateChange,
3145
+ onNavigate
3146
+ } = options;
3147
+ let container;
3148
+ if (typeof containerOption === "string") {
3149
+ const el = document.querySelector(containerOption);
3150
+ if (!el) {
3151
+ throw new Error(`Container not found: ${containerOption}`);
3152
+ }
3153
+ container = el;
3154
+ } else {
3155
+ container = containerOption;
3156
+ }
3157
+ if (shouldInjectTailwind) {
3158
+ const { initServlyTailwind: initServlyTailwind2 } = await import("./tailwind-CGAHPC3O.js");
3159
+ await initServlyTailwind2(tailwindConfig);
3160
+ }
3161
+ const activeRenders = [];
3162
+ return {
3163
+ render: (elements, context = { props: {} }) => {
3164
+ const result = render({
3165
+ container,
3166
+ elements,
3167
+ context,
3168
+ enableStateManager: true,
3169
+ initialState,
3170
+ onStateChange,
3171
+ onNavigate
3172
+ });
3173
+ activeRenders.push(result);
3174
+ return result;
3175
+ },
3176
+ renderNode: (elements, nodeId, context = { props: {} }) => {
3177
+ const result = renderNode({
3178
+ container,
3179
+ elements,
3180
+ nodeId,
3181
+ context
3182
+ });
3183
+ if (result) {
3184
+ activeRenders.push(result);
3185
+ }
3186
+ return result;
3187
+ },
3188
+ renderDynamicList,
3189
+ destroy: () => {
3190
+ for (const result of activeRenders) {
3191
+ result.destroy();
3192
+ }
3193
+ activeRenders.length = 0;
3194
+ container.innerHTML = "";
3195
+ }
3196
+ };
3197
+ }
3198
+ function createViewsMap(views) {
3199
+ const viewsMap = /* @__PURE__ */ new Map();
3200
+ for (const view of views) {
3201
+ const id = view.id || view._id;
3202
+ if (id && view.layout) {
3203
+ viewsMap.set(id, {
3204
+ id,
3205
+ layout: view.layout,
3206
+ props: view.props
3207
+ });
3208
+ }
3209
+ }
3210
+ return viewsMap;
3211
+ }
3212
+ function extractReferencedViewIds(elements) {
3213
+ const viewIds = /* @__PURE__ */ new Set();
3214
+ for (const element of elements) {
3215
+ if (element.isComponentView && element.componentId) {
3216
+ viewIds.add(element.componentId);
3217
+ }
3218
+ if (element.configuration?.componentViewRef) {
3219
+ viewIds.add(element.configuration.componentViewRef);
3220
+ }
3221
+ const bindings = element.configuration?.bindings?.inputs;
3222
+ if (bindings) {
3223
+ for (const binding of Object.values(bindings)) {
3224
+ if (binding && typeof binding === "object") {
3225
+ if (binding.source === "node" && binding.binding?.viewId) {
3226
+ viewIds.add(binding.binding.viewId);
3227
+ }
3228
+ if (binding.viewId) {
3229
+ viewIds.add(binding.viewId);
3230
+ }
3231
+ }
3232
+ }
3233
+ }
3234
+ }
3235
+ return Array.from(viewIds);
3236
+ }
3237
+ function collectAllViewDependencies(views, startViewId) {
3238
+ const collected = /* @__PURE__ */ new Set();
3239
+ const toProcess = [startViewId];
3240
+ while (toProcess.length > 0) {
3241
+ const viewId = toProcess.pop();
3242
+ if (collected.has(viewId)) continue;
3243
+ collected.add(viewId);
3244
+ const view = views.get(viewId);
3245
+ if (!view) continue;
3246
+ const referencedIds = extractReferencedViewIds(view.layout);
3247
+ for (const refId of referencedIds) {
3248
+ if (!collected.has(refId)) {
3249
+ toProcess.push(refId);
3250
+ }
3251
+ }
3252
+ }
3253
+ return collected;
3254
+ }
1549
3255
 
1550
3256
  // src/cache.ts
1551
3257
  var DEFAULT_CACHE_CONFIG = {
@@ -1819,6 +3525,18 @@ function calculateBackoffDelay(retryCount, config) {
1819
3525
  function sleep(ms) {
1820
3526
  return new Promise((resolve) => setTimeout(resolve, ms));
1821
3527
  }
3528
+ function buildViewsMap(views) {
3529
+ if (!views || views.length === 0) return void 0;
3530
+ const viewsMap = /* @__PURE__ */ new Map();
3531
+ for (const view of views) {
3532
+ viewsMap.set(view.id, {
3533
+ id: view.id,
3534
+ layout: view.layout,
3535
+ props: view.props
3536
+ });
3537
+ }
3538
+ return viewsMap;
3539
+ }
1822
3540
  async function resolveVersionFromApi(id, specifier, apiKey) {
1823
3541
  if (/^\d+\.\d+\.\d+$/.test(specifier)) {
1824
3542
  return specifier;
@@ -1848,7 +3566,7 @@ async function resolveVersionFromApi(id, specifier, apiKey) {
1848
3566
  return "latest";
1849
3567
  }
1850
3568
  }
1851
- async function fetchFromRegistry(id, version, apiKey, includeBundle) {
3569
+ async function fetchFromRegistry(id, version, apiKey, includeBundle, includeViews) {
1852
3570
  const baseUrl = getRegistryUrl();
1853
3571
  const headers = {
1854
3572
  "Content-Type": "application/json"
@@ -1867,6 +3585,9 @@ async function fetchFromRegistry(id, version, apiKey, includeBundle) {
1867
3585
  if (includeBundle) {
1868
3586
  url += (url.includes("?") ? "&" : "?") + "bundle=true";
1869
3587
  }
3588
+ if (includeViews) {
3589
+ url += (url.includes("?") ? "&" : "?") + "includeViews=true";
3590
+ }
1870
3591
  const response = await fetch(url, { headers });
1871
3592
  if (!response.ok) {
1872
3593
  if (response.status === 404) {
@@ -1900,7 +3621,9 @@ async function fetchComponent(id, options = {}) {
1900
3621
  forceRefresh = false,
1901
3622
  signal,
1902
3623
  bundleStrategy = "eager",
1903
- includeBundle = true
3624
+ includeBundle = true,
3625
+ includeViews = true
3626
+ // Default to true - fetch all views needed
1904
3627
  } = options;
1905
3628
  const fullRetryConfig = {
1906
3629
  ...DEFAULT_RETRY_CONFIG,
@@ -1914,6 +3637,7 @@ async function fetchComponent(id, options = {}) {
1914
3637
  if (cached.bundle) {
1915
3638
  registry = buildRegistryFromBundle(cached);
1916
3639
  }
3640
+ const views = buildViewsMap(cached.views);
1917
3641
  const duration = performance.now() - startTime;
1918
3642
  analytics.trackFetch(id, cached.version, duration, true, {
1919
3643
  cacheHit: true,
@@ -1923,7 +3647,8 @@ async function fetchComponent(id, options = {}) {
1923
3647
  data: cached,
1924
3648
  fromCache: true,
1925
3649
  version: cached.version,
1926
- registry
3650
+ registry,
3651
+ views
1927
3652
  };
1928
3653
  }
1929
3654
  }
@@ -1935,11 +3660,13 @@ async function fetchComponent(id, options = {}) {
1935
3660
  if (cached.bundle) {
1936
3661
  registry = buildRegistryFromBundle(cached);
1937
3662
  }
3663
+ const views = buildViewsMap(cached.views);
1938
3664
  return {
1939
3665
  data: cached,
1940
3666
  fromCache: true,
1941
3667
  version: resolvedVersion,
1942
- registry
3668
+ registry,
3669
+ views
1943
3670
  };
1944
3671
  }
1945
3672
  }
@@ -1950,7 +3677,7 @@ async function fetchComponent(id, options = {}) {
1950
3677
  throw new Error("Fetch aborted");
1951
3678
  }
1952
3679
  try {
1953
- const data = await fetchFromRegistry(id, resolvedVersion, apiKey, shouldIncludeBundle);
3680
+ const data = await fetchFromRegistry(id, resolvedVersion, apiKey, shouldIncludeBundle, includeViews);
1954
3681
  setInCache(id, resolvedVersion, data, cacheStrategy, cacheConfig);
1955
3682
  if (version !== resolvedVersion) {
1956
3683
  setInCache(id, version, data, cacheStrategy, cacheConfig);
@@ -1965,6 +3692,7 @@ async function fetchComponent(id, options = {}) {
1965
3692
  version: entry.resolved || entry.version
1966
3693
  }));
1967
3694
  }
3695
+ const views = buildViewsMap(data.views);
1968
3696
  const duration = performance.now() - startTime;
1969
3697
  analytics.trackFetch(id, resolvedVersion, duration, false, {
1970
3698
  cacheHit: false,
@@ -1976,7 +3704,8 @@ async function fetchComponent(id, options = {}) {
1976
3704
  fromCache: false,
1977
3705
  version: resolvedVersion,
1978
3706
  registry,
1979
- pendingDependencies
3707
+ pendingDependencies,
3708
+ views
1980
3709
  };
1981
3710
  } catch (error) {
1982
3711
  lastError = error instanceof Error ? error : new Error(String(error));
@@ -2347,13 +4076,13 @@ function validateAssertion(container, assertion) {
2347
4076
  message: `Element "${assertion.selector}" not found`
2348
4077
  };
2349
4078
  }
2350
- const hasClass = elements[0].classList.contains(assertion.expected);
4079
+ const hasClass2 = elements[0].classList.contains(assertion.expected);
2351
4080
  return {
2352
4081
  assertion,
2353
- passed: hasClass,
4082
+ passed: hasClass2,
2354
4083
  actual: Array.from(elements[0].classList),
2355
4084
  expected: assertion.expected,
2356
- message: hasClass ? `Element has class "${assertion.expected}"` : `Element does not have class "${assertion.expected}"`
4085
+ message: hasClass2 ? `Element has class "${assertion.expected}"` : `Element does not have class "${assertion.expected}"`
2357
4086
  };
2358
4087
  case "style":
2359
4088
  if (elements.length === 0) {
@@ -2474,9 +4203,16 @@ export {
2474
4203
  AnalyticsCollector,
2475
4204
  DEFAULT_CACHE_CONFIG,
2476
4205
  DEFAULT_RETRY_CONFIG,
4206
+ DEFAULT_SERVLY_TAILWIND_CONFIG,
4207
+ EVENT_HANDLERS,
4208
+ EventSystem,
2477
4209
  LongTaskObserver,
2478
4210
  MemorySampler,
4211
+ OverrideSystem,
2479
4212
  SessionManager,
4213
+ StateManager,
4214
+ addClass,
4215
+ addCustomStyles,
2480
4216
  analytics,
2481
4217
  applyStyles,
2482
4218
  batchFetchComponents,
@@ -2490,13 +4226,20 @@ export {
2490
4226
  clearMemoryCache,
2491
4227
  clearStyles,
2492
4228
  collectAllDependencies,
4229
+ collectAllViewDependencies,
2493
4230
  compareVersions,
2494
4231
  configureAnalytics,
2495
4232
  createRegistry,
4233
+ createServlyRenderer,
4234
+ createViewsMap,
4235
+ deepMerge,
4236
+ deleteValueByPath,
2496
4237
  detectCircularDependencies,
2497
4238
  extractBindingKeys,
2498
4239
  extractDependencies,
2499
4240
  extractDependenciesFromCode,
4241
+ extractOverrideDependencies,
4242
+ extractReferencedViewIds,
2500
4243
  fetchComponent,
2501
4244
  fetchComponentWithDependencies,
2502
4245
  formatStyleValue,
@@ -2504,25 +4247,52 @@ export {
2504
4247
  generateTestCases,
2505
4248
  getAnalytics,
2506
4249
  getCacheKey,
4250
+ getCleanupOverrides,
2507
4251
  getDependencyTree,
4252
+ getEventSystem,
2508
4253
  getFromCache,
4254
+ getLocalStorage,
2509
4255
  getLongTaskObserver,
2510
4256
  getMemoryCacheSize,
2511
4257
  getMemorySampler,
4258
+ getMountOverrides,
4259
+ getOverrideSystem,
2512
4260
  getRegistryUrl,
2513
4261
  getSessionManager,
4262
+ getSessionStorage,
4263
+ getTailwind,
4264
+ getUrlInfo,
4265
+ getValueByPath,
4266
+ goBack,
4267
+ goForward,
4268
+ hasClass,
4269
+ hasDependencyOverrides,
4270
+ hasOverrides,
2514
4271
  hasTemplateSyntax,
4272
+ initServlyTailwind,
4273
+ injectTailwind,
2515
4274
  invalidateCache,
2516
4275
  isComponentAvailable,
4276
+ isTailwindLoaded,
2517
4277
  isValidSpecifier,
4278
+ navigateTo,
2518
4279
  parseVersion,
2519
4280
  prefetchComponents,
2520
4281
  processStyles,
4282
+ removeClass,
4283
+ removeCustomStyles,
4284
+ removeLocalStorage,
4285
+ removeSessionStorage,
4286
+ removeTailwind,
2521
4287
  render,
2522
4288
  renderDynamicList,
4289
+ renderInShadow,
4290
+ renderNode,
2523
4291
  resetAnalytics,
4292
+ resetEventSystem,
2524
4293
  resetLongTaskObserver,
2525
4294
  resetMemorySampler,
4295
+ resetOverrideSystem,
2526
4296
  resetSessionManager,
2527
4297
  resolveBindingPath,
2528
4298
  resolveTemplate,
@@ -2533,8 +4303,15 @@ export {
2533
4303
  runTestCase,
2534
4304
  satisfiesVersion,
2535
4305
  setInCache,
4306
+ setLocalStorage,
2536
4307
  setRegistryUrl,
4308
+ setSessionStorage,
4309
+ setValueByPath,
4310
+ toDomEventName,
4311
+ toReactEventName,
4312
+ toggleClass,
2537
4313
  updateStyles,
4314
+ updateTailwindConfig,
2538
4315
  validateAssertion,
2539
4316
  validateProps
2540
4317
  };