@lvce-editor/preview-worker 2.0.0 → 2.2.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.
@@ -514,7 +514,7 @@ const IpcParentWithMessagePort$1 = {
514
514
 
515
515
  const Two$1 = '2.0';
516
516
  const callbacks$1 = Object.create(null);
517
- const get$2 = id => {
517
+ const get$3 = id => {
518
518
  return callbacks$1[id];
519
519
  };
520
520
  const remove$1 = id => {
@@ -663,7 +663,7 @@ const warn = (...args) => {
663
663
  console.warn(...args);
664
664
  };
665
665
  const resolve = (id, response) => {
666
- const fn = get$2(id);
666
+ const fn = get$3(id);
667
667
  if (!fn) {
668
668
  console.log(response);
669
669
  warn(`callback ${id} may already be disposed`);
@@ -1131,8 +1131,6 @@ const Style = 72;
1131
1131
  const Html = 73;
1132
1132
  const Reference = 100;
1133
1133
 
1134
- const TargetName = 'event.target.name';
1135
-
1136
1134
  const EditorWorker = 99;
1137
1135
  const RendererWorker = 1;
1138
1136
 
@@ -1140,10 +1138,10 @@ const SetDom2 = 'Viewlet.setDom2';
1140
1138
  const SetPatches = 'Viewlet.setPatches';
1141
1139
 
1142
1140
  const rpcs = Object.create(null);
1143
- const set$3 = (id, rpc) => {
1141
+ const set$4 = (id, rpc) => {
1144
1142
  rpcs[id] = rpc;
1145
1143
  };
1146
- const get$1 = id => {
1144
+ const get$2 = id => {
1147
1145
  return rpcs[id];
1148
1146
  };
1149
1147
  const remove = id => {
@@ -1154,18 +1152,18 @@ const remove = id => {
1154
1152
  const create$2 = rpcId => {
1155
1153
  return {
1156
1154
  async dispose() {
1157
- const rpc = get$1(rpcId);
1155
+ const rpc = get$2(rpcId);
1158
1156
  await rpc.dispose();
1159
1157
  },
1160
1158
  // @ts-ignore
1161
1159
  invoke(method, ...params) {
1162
- const rpc = get$1(rpcId);
1160
+ const rpc = get$2(rpcId);
1163
1161
  // @ts-ignore
1164
1162
  return rpc.invoke(method, ...params);
1165
1163
  },
1166
1164
  // @ts-ignore
1167
1165
  invokeAndTransfer(method, ...params) {
1168
- const rpc = get$1(rpcId);
1166
+ const rpc = get$2(rpcId);
1169
1167
  // @ts-ignore
1170
1168
  return rpc.invokeAndTransfer(method, ...params);
1171
1169
  },
@@ -1173,7 +1171,7 @@ const create$2 = rpcId => {
1173
1171
  const mockRpc = createMockRpc({
1174
1172
  commandMap
1175
1173
  });
1176
- set$3(rpcId, mockRpc);
1174
+ set$4(rpcId, mockRpc);
1177
1175
  // @ts-ignore
1178
1176
  mockRpc[Symbol.dispose] = () => {
1179
1177
  remove(rpcId);
@@ -1182,20 +1180,20 @@ const create$2 = rpcId => {
1182
1180
  return mockRpc;
1183
1181
  },
1184
1182
  set(rpc) {
1185
- set$3(rpcId, rpc);
1183
+ set$4(rpcId, rpc);
1186
1184
  }
1187
1185
  };
1188
1186
  };
1189
1187
 
1190
1188
  const {
1191
1189
  invoke: invoke$1,
1192
- set: set$2
1190
+ set: set$3
1193
1191
  } = create$2(EditorWorker);
1194
1192
 
1195
1193
  const {
1196
1194
  invoke,
1197
1195
  invokeAndTransfer,
1198
- set: set$1
1196
+ set: set$2
1199
1197
  } = create$2(RendererWorker);
1200
1198
  const sendMessagePortToEditorWorker = async (port, rpcId) => {
1201
1199
  const command = 'HandleMessagePort.handleMessagePort';
@@ -1295,11 +1293,11 @@ const terminate = () => {
1295
1293
  };
1296
1294
 
1297
1295
  const {
1298
- get,
1296
+ get: get$1,
1299
1297
  getCommandIds,
1300
1298
  getKeys: getKeys$1,
1301
1299
  registerCommands,
1302
- set,
1300
+ set: set$1,
1303
1301
  wrapCommand,
1304
1302
  wrapGetter
1305
1303
  } = create$1();
@@ -1320,7 +1318,7 @@ const create = (uid, uri, x, y, width, height, platform, assetDir) => {
1320
1318
  uri,
1321
1319
  warningCount: 0
1322
1320
  };
1323
- set(uid, state, state);
1321
+ set$1(uid, state, state);
1324
1322
  };
1325
1323
 
1326
1324
  const iEqual = (oldState, newState) => {
@@ -1361,11 +1359,73 @@ const diff2 = uid => {
1361
1359
  const {
1362
1360
  newState,
1363
1361
  oldState
1364
- } = get(uid);
1362
+ } = get$1(uid);
1365
1363
  const result = diff(oldState, newState);
1366
1364
  return result;
1367
1365
  };
1368
1366
 
1367
+ const dispatchEvent = (element, event) => {
1368
+ element.dispatchEvent(event);
1369
+
1370
+ // Also invoke direct on* handler if set (e.g. element.onclick = function(){})
1371
+ const handlerName = `on${event.type}`;
1372
+ const handler = element[handlerName];
1373
+ if (typeof handler === 'function') {
1374
+ handler.call(element, event);
1375
+ } else if (handler === null || handler === undefined) {
1376
+ // Check if there's an inline HTML attribute that wasn't converted to a property
1377
+ const attrValue = element.getAttribute(handlerName);
1378
+ if (attrValue && typeof attrValue === 'string' && element.ownerDocument && element.ownerDocument.defaultView) {
1379
+ const window = element.ownerDocument.defaultView;
1380
+ // Handle inline event handlers like onclick="someFunction(2)"
1381
+ // Evaluate in the context of the window so functions are in scope
1382
+ // eslint-disable-next-line @typescript-eslint/no-implied-eval
1383
+ const fn = new Function('event', `with(this) { ${attrValue} }`);
1384
+ fn.call(window, event);
1385
+ }
1386
+ }
1387
+ };
1388
+
1389
+ const dispatchClickEvent = (element, window) => {
1390
+ const clickEvent = new window.MouseEvent('click', {
1391
+ bubbles: true
1392
+ });
1393
+ dispatchEvent(element, clickEvent);
1394
+ };
1395
+
1396
+ const getParsedNodesChildNodeCount = parsedDom => {
1397
+ array(parsedDom);
1398
+ const rootCountFromParse = parsedDom.rootChildCount;
1399
+ if (typeof rootCountFromParse === 'number') {
1400
+ return rootCountFromParse;
1401
+ }
1402
+ let rootChildCount = 0;
1403
+ let i = 0;
1404
+ while (i < parsedDom.length) {
1405
+ rootChildCount++;
1406
+
1407
+ // skip the entire subtree of the current node
1408
+ let toSkip = parsedDom[i].childCount;
1409
+ i++;
1410
+ while (toSkip > 0 && i < parsedDom.length) {
1411
+ toSkip -= 1;
1412
+ toSkip += parsedDom[i].childCount;
1413
+ i++;
1414
+ }
1415
+ }
1416
+ return rootChildCount;
1417
+ };
1418
+
1419
+ /* eslint-disable @typescript-eslint/prefer-readonly-parameter-types */
1420
+
1421
+ const states = new Map();
1422
+ const get = uid => {
1423
+ return states.get(uid);
1424
+ };
1425
+ const set = (uid, instance) => {
1426
+ states.set(uid, instance);
1427
+ };
1428
+
1369
1429
  const text = data => {
1370
1430
  return {
1371
1431
  childCount: 0,
@@ -1824,29 +1884,6 @@ const getVirtualDomTag = text => {
1824
1884
  }
1825
1885
  };
1826
1886
 
1827
- const None = 0;
1828
- const OpeningAngleBracket = 1;
1829
- const ClosingAngleBracket = 2;
1830
- const TagNameStart = 3;
1831
- const TagNameEnd = 4;
1832
- const Content = 5;
1833
- const ClosingTagSlash = 6;
1834
- const WhitespaceInsideOpeningTag = 7;
1835
- const AttributeName = 8;
1836
- const AttributeEqualSign = 9;
1837
- const AttributeQuoteStart = 10;
1838
- const AttributeValue = 11;
1839
- const AttributeQuoteEnd = 12;
1840
- const WhitespaceAfterClosingTagSlash = 13;
1841
- const WhitespaceAfterOpeningTagOpenAngleBracket = 14;
1842
- const ExclamationMark = 15;
1843
- const Doctype = 16;
1844
- const StartCommentDashes = 17;
1845
- const Comment = 18;
1846
- const EndCommentTag = 19;
1847
- const Text = 20;
1848
- const CommentStart = 21;
1849
-
1850
1887
  /* eslint-disable @cspell/spellchecker */
1851
1888
  // Common HTML attributes that are safe to allow by default
1852
1889
  const commonAllowedAttributes = new Set([
@@ -1885,6 +1922,211 @@ const isDefaultAllowedAttribute = (attributeName, defaultAllowedAttributes) => {
1885
1922
  return defaultAllowedAttributes.includes(attributeName);
1886
1923
  };
1887
1924
 
1925
+ // Tags to skip entirely during serialization
1926
+ const TAGS_TO_SKIP = new Set(['script', 'meta', 'title']);
1927
+
1928
+ // Tags to skip but process children
1929
+ const TAGS_TO_SKIP_TAG_ONLY$1 = new Set(['html', 'body', 'head']);
1930
+
1931
+ // Tags where we extract content as CSS
1932
+ const CSS_TAGS = new Set(['style']);
1933
+ // eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
1934
+ const serializeNode = (node, dom, css, context) => {
1935
+ const {
1936
+ nodeType
1937
+ } = node;
1938
+
1939
+ // Text node
1940
+ if (nodeType === 3) {
1941
+ const textContent = node.textContent || '';
1942
+ if (textContent) {
1943
+ dom.push(text(textContent));
1944
+ return 1;
1945
+ }
1946
+ return 0;
1947
+ }
1948
+
1949
+ // Not an element node — skip
1950
+ if (nodeType !== 1) {
1951
+ return 0;
1952
+ }
1953
+ const tagName = (node.tagName || '').toLowerCase();
1954
+
1955
+ // Extract CSS from style elements
1956
+ if (CSS_TAGS.has(tagName)) {
1957
+ const styleContent = node.textContent || '';
1958
+ if (styleContent.trim()) {
1959
+ css.push(styleContent);
1960
+ }
1961
+ return 0;
1962
+ }
1963
+
1964
+ // Skip certain tags entirely
1965
+ if (TAGS_TO_SKIP.has(tagName)) {
1966
+ return 0;
1967
+ }
1968
+
1969
+ // For html/body tags, serialize children only
1970
+ if (TAGS_TO_SKIP_TAG_ONLY$1.has(tagName)) {
1971
+ let childCount = 0;
1972
+ const {
1973
+ childNodes
1974
+ } = node;
1975
+ for (let i = 0; i < childNodes.length; i++) {
1976
+ childCount += serializeNode(childNodes[i], dom, css, context);
1977
+ }
1978
+ return childCount;
1979
+ }
1980
+
1981
+ // Normal element - create a VirtualDomNode
1982
+ const newNode = {
1983
+ childCount: 0,
1984
+ type: getVirtualDomTag(tagName)
1985
+ };
1986
+
1987
+ // Copy allowed attributes
1988
+ const {
1989
+ attributes
1990
+ } = node;
1991
+ if (attributes) {
1992
+ for (let i = 0; i < attributes.length; i++) {
1993
+ const attr = attributes[i];
1994
+ const attrName = attr.name;
1995
+ if (isDefaultAllowedAttribute(attrName, [])) {
1996
+ let finalName = attrName;
1997
+ if (attrName === 'class') {
1998
+ finalName = 'className';
1999
+ } else if (attrName === 'type') {
2000
+ finalName = 'inputType';
2001
+ }
2002
+ newNode[finalName] = attr.value;
2003
+ }
2004
+ }
2005
+ }
2006
+
2007
+ // Assign element tracking ID for interactivity
2008
+ if (context.elementMap) {
2009
+ const hdId = String(context.nextId++);
2010
+ newNode['data-id'] = hdId;
2011
+ context.elementMap.set(hdId, node);
2012
+ }
2013
+ dom.push(newNode);
2014
+
2015
+ // Serialize children
2016
+ let childCount = 0;
2017
+ const {
2018
+ childNodes
2019
+ } = node;
2020
+ for (let i = 0; i < childNodes.length; i++) {
2021
+ childCount += serializeNode(childNodes[i], dom, css, context);
2022
+ }
2023
+ newNode.childCount = childCount;
2024
+ return 1;
2025
+ };
2026
+
2027
+ // eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
2028
+ const serialize = (document, elementMap) => {
2029
+ const dom = [];
2030
+ const css = [];
2031
+ const context = {
2032
+ elementMap,
2033
+ nextId: 0
2034
+ };
2035
+
2036
+ // Start from document.documentElement (the <html> element)
2037
+ const root = document.documentElement || document.body;
2038
+ if (!root) {
2039
+ return {
2040
+ css,
2041
+ dom
2042
+ };
2043
+ }
2044
+ let rootChildCount = 0;
2045
+ const {
2046
+ childNodes
2047
+ } = root;
2048
+ for (let i = 0; i < childNodes.length; i++) {
2049
+ rootChildCount += serializeNode(childNodes[i], dom, css, context);
2050
+ }
2051
+ try {
2052
+ Object.defineProperty(dom, 'rootChildCount', {
2053
+ configurable: true,
2054
+ enumerable: false,
2055
+ value: rootChildCount
2056
+ });
2057
+ } catch {
2058
+ dom.rootChildCount = rootChildCount;
2059
+ }
2060
+ return {
2061
+ css,
2062
+ dom
2063
+ };
2064
+ };
2065
+
2066
+ const handleClick = (state, hdId) => {
2067
+ // console.log('click,', hdId)
2068
+ if (!hdId) {
2069
+ return state;
2070
+ }
2071
+ const happyDomInstance = get(state.uid);
2072
+ if (!happyDomInstance) {
2073
+ return state;
2074
+ }
2075
+ const element = happyDomInstance.elementMap.get(hdId);
2076
+ if (!element) {
2077
+ return state;
2078
+ }
2079
+
2080
+ // console.log({ element })
2081
+ // Dispatch click event in happy-dom so event listeners fire
2082
+ dispatchClickEvent(element, happyDomInstance.window);
2083
+
2084
+ // Re-serialize the (potentially mutated) DOM
2085
+ const elementMap = new Map();
2086
+ const serialized = serialize(happyDomInstance.document, elementMap);
2087
+
2088
+ // Update happy-dom state with new element map
2089
+ set(state.uid, {
2090
+ document: happyDomInstance.document,
2091
+ elementMap,
2092
+ window: happyDomInstance.window
2093
+ });
2094
+ const parsedDom = serialized.dom;
2095
+ const {
2096
+ css
2097
+ } = serialized;
2098
+ const parsedNodesChildNodeCount = getParsedNodesChildNodeCount(parsedDom);
2099
+ return {
2100
+ ...state,
2101
+ css,
2102
+ parsedDom,
2103
+ parsedNodesChildNodeCount
2104
+ };
2105
+ };
2106
+
2107
+ const None = 0;
2108
+ const OpeningAngleBracket = 1;
2109
+ const ClosingAngleBracket = 2;
2110
+ const TagNameStart = 3;
2111
+ const TagNameEnd = 4;
2112
+ const Content = 5;
2113
+ const ClosingTagSlash = 6;
2114
+ const WhitespaceInsideOpeningTag = 7;
2115
+ const AttributeName = 8;
2116
+ const AttributeEqualSign = 9;
2117
+ const AttributeQuoteStart = 10;
2118
+ const AttributeValue = 11;
2119
+ const AttributeQuoteEnd = 12;
2120
+ const WhitespaceAfterClosingTagSlash = 13;
2121
+ const WhitespaceAfterOpeningTagOpenAngleBracket = 14;
2122
+ const ExclamationMark = 15;
2123
+ const Doctype = 16;
2124
+ const StartCommentDashes = 17;
2125
+ const Comment = 18;
2126
+ const EndCommentTag = 19;
2127
+ const Text = 20;
2128
+ const CommentStart = 21;
2129
+
1888
2130
  const isSelfClosingTag = tag => {
1889
2131
  switch (tag.toLowerCase()) {
1890
2132
  case 'area':
@@ -2237,7 +2479,7 @@ const tokenizeHtml = text => {
2237
2479
  const TAGS_TO_SKIP_COMPLETELY = new Set(['meta', 'title']);
2238
2480
 
2239
2481
  // Tags that should have their opening/closing tags skipped but content processed
2240
- const TAGS_TO_SKIP_TAG_ONLY$1 = new Set(['html', 'head']);
2482
+ const TAGS_TO_SKIP_TAG_ONLY = new Set(['html', 'head']);
2241
2483
 
2242
2484
  // Tags where we capture content as CSS
2243
2485
  const TAGS_TO_CAPTURE_AS_CSS = new Set(['style']);
@@ -2334,7 +2576,7 @@ const parseHtml = (html, allowedAttributes = [], defaultAllowedAttributes = [])
2334
2576
  } else if (TAGS_TO_SKIP_COMPLETELY.has(tagNameToClose)) {
2335
2577
  // We were skipping this content, so decrement skipDepth
2336
2578
  skipDepth--;
2337
- } else if (TAGS_TO_SKIP_TAG_ONLY$1.has(tagNameToClose)) ; else {
2579
+ } else if (TAGS_TO_SKIP_TAG_ONLY.has(tagNameToClose)) ; else {
2338
2580
  // Normal tag - pop from stack
2339
2581
  if (stack.length > 1) {
2340
2582
  stack.pop();
@@ -2368,7 +2610,7 @@ const parseHtml = (html, allowedAttributes = [], defaultAllowedAttributes = [])
2368
2610
  // For self-closing tags like meta, we just skip them without tracking
2369
2611
  }
2370
2612
  // Check if this tag should have its opening/closing tags skipped (html, head)
2371
- else if (TAGS_TO_SKIP_TAG_ONLY$1.has(tagNameLower)) {
2613
+ else if (TAGS_TO_SKIP_TAG_ONLY.has(tagNameLower)) {
2372
2614
  if (!lastTagWasSelfClosing) {
2373
2615
  // Track the tag name for matching the closing tag
2374
2616
  tagStack.push(token.text);
@@ -2428,7 +2670,7 @@ const handleEditorChanged = async () => {
2428
2670
  for (const previewUid of previewKeys) {
2429
2671
  const {
2430
2672
  newState: state
2431
- } = get(previewUid);
2673
+ } = get$1(previewUid);
2432
2674
 
2433
2675
  // Skip if no URI is set
2434
2676
  if (!state.uri) {
@@ -2459,7 +2701,7 @@ const handleEditorChanged = async () => {
2459
2701
  parsedDom: parseResult.dom,
2460
2702
  scripts: parseResult.scripts
2461
2703
  };
2462
- set(previewUid, state, updatedState);
2704
+ set$1(previewUid, state, updatedState);
2463
2705
  } catch (error) {
2464
2706
  // If getting text fails, update with error message
2465
2707
  const errorMessage = error instanceof Error ? error.message : 'Unknown error';
@@ -2471,7 +2713,7 @@ const handleEditorChanged = async () => {
2471
2713
  parsedDom: [],
2472
2714
  scripts: []
2473
2715
  };
2474
- set(previewUid, state, updatedState);
2716
+ set$1(previewUid, state, updatedState);
2475
2717
  }
2476
2718
  }
2477
2719
  }
@@ -90049,8 +90291,7 @@ class Window extends BrowserWindow {
90049
90291
  }
90050
90292
  }
90051
90293
 
90052
- /* eslint-disable @typescript-eslint/no-implied-eval */
90053
- const executeScripts = (rawHtml, scripts) => {
90294
+ const createWindow = rawHtml => {
90054
90295
  const window = new Window({
90055
90296
  url: 'https://localhost:3000'
90056
90297
  });
@@ -90060,167 +90301,45 @@ const executeScripts = (rawHtml, scripts) => {
90060
90301
 
90061
90302
  // Parse the raw HTML into the happy-dom document
90062
90303
  document.documentElement.innerHTML = rawHtml;
90304
+ return {
90305
+ document,
90306
+ window
90307
+ };
90308
+ };
90063
90309
 
90310
+ const alert = message => {
90311
+ void invoke('ConfirmPrompt.prompt', message);
90312
+ };
90313
+
90314
+ const getTopLevelFunctionNames = script => {
90315
+ const names = [];
90316
+ const regex = /(?:^|[\n;])\s*function\s+([a-zA-Z_$][\w$]*)/g;
90317
+ let match;
90318
+ while ((match = regex.exec(script)) !== null) {
90319
+ names.push(match[1]);
90320
+ }
90321
+ return names;
90322
+ };
90323
+
90324
+ /* eslint-disable @typescript-eslint/no-implied-eval */
90325
+ const executeScripts = (window, document, scripts) => {
90326
+ window.alert = alert;
90327
+ // @ts-ignore
90328
+ globalThis.alert = alert;
90064
90329
  // Execute each script with the happy-dom window and document as context
90065
90330
  for (const scriptContent of scripts) {
90066
90331
  try {
90067
- const fn = new Function('window', 'document', 'console', scriptContent);
90332
+ // In a browser, top-level function declarations in <script> tags become
90333
+ // properties on window. Since new Function() creates a local scope, we
90334
+ // extract function names and explicitly assign them to window after execution.
90335
+ const functionNames = getTopLevelFunctionNames(scriptContent);
90336
+ const suffix = functionNames.map(name => `\nwindow['${name}'] = ${name};`).join('');
90337
+ const fn = new Function('window', 'document', 'console', scriptContent + suffix);
90068
90338
  fn(window, document, console);
90069
90339
  } catch (error) {
90070
90340
  console.warn('[preview-worker] Script execution error:', error);
90071
90341
  }
90072
90342
  }
90073
- return document;
90074
- };
90075
-
90076
- const getParsedNodesChildNodeCount = parsedDom => {
90077
- array(parsedDom);
90078
- const rootCountFromParse = parsedDom.rootChildCount;
90079
- if (typeof rootCountFromParse === 'number') {
90080
- return rootCountFromParse;
90081
- }
90082
- let rootChildCount = 0;
90083
- let i = 0;
90084
- while (i < parsedDom.length) {
90085
- rootChildCount++;
90086
-
90087
- // skip the entire subtree of the current node
90088
- let toSkip = parsedDom[i].childCount;
90089
- i++;
90090
- while (toSkip > 0 && i < parsedDom.length) {
90091
- toSkip -= 1;
90092
- toSkip += parsedDom[i].childCount;
90093
- i++;
90094
- }
90095
- }
90096
- return rootChildCount;
90097
- };
90098
-
90099
- // Tags to skip entirely during serialization
90100
- const TAGS_TO_SKIP = new Set(['script', 'meta', 'title']);
90101
-
90102
- // Tags to skip but process children
90103
- const TAGS_TO_SKIP_TAG_ONLY = new Set(['html', 'body', 'head']);
90104
-
90105
- // Tags where we extract content as CSS
90106
- const CSS_TAGS = new Set(['style']);
90107
- const serializeNode = (node, dom, css) => {
90108
- const {
90109
- nodeType
90110
- } = node;
90111
-
90112
- // Text node
90113
- if (nodeType === 3) {
90114
- const textContent = node.textContent || '';
90115
- if (textContent) {
90116
- dom.push(text(textContent));
90117
- return 1;
90118
- }
90119
- return 0;
90120
- }
90121
-
90122
- // Not an element node — skip
90123
- if (nodeType !== 1) {
90124
- return 0;
90125
- }
90126
- const tagName = (node.tagName || '').toLowerCase();
90127
-
90128
- // Extract CSS from style elements
90129
- if (CSS_TAGS.has(tagName)) {
90130
- const styleContent = node.textContent || '';
90131
- if (styleContent.trim()) {
90132
- css.push(styleContent);
90133
- }
90134
- return 0;
90135
- }
90136
-
90137
- // Skip certain tags entirely
90138
- if (TAGS_TO_SKIP.has(tagName)) {
90139
- return 0;
90140
- }
90141
-
90142
- // For html/body tags, serialize children only
90143
- if (TAGS_TO_SKIP_TAG_ONLY.has(tagName)) {
90144
- let childCount = 0;
90145
- const {
90146
- childNodes
90147
- } = node;
90148
- for (let i = 0; i < childNodes.length; i++) {
90149
- childCount += serializeNode(childNodes[i], dom, css);
90150
- }
90151
- return childCount;
90152
- }
90153
-
90154
- // Normal element - create a VirtualDomNode
90155
- const newNode = {
90156
- childCount: 0,
90157
- type: getVirtualDomTag(tagName)
90158
- };
90159
-
90160
- // Copy allowed attributes
90161
- const {
90162
- attributes
90163
- } = node;
90164
- if (attributes) {
90165
- for (let i = 0; i < attributes.length; i++) {
90166
- const attr = attributes[i];
90167
- const attrName = attr.name;
90168
- if (isDefaultAllowedAttribute(attrName, [])) {
90169
- let finalName = attrName;
90170
- if (attrName === 'class') {
90171
- finalName = 'className';
90172
- } else if (attrName === 'type') {
90173
- finalName = 'inputType';
90174
- }
90175
- newNode[finalName] = attr.value;
90176
- }
90177
- }
90178
- }
90179
- dom.push(newNode);
90180
-
90181
- // Serialize children
90182
- let childCount = 0;
90183
- const {
90184
- childNodes
90185
- } = node;
90186
- for (let i = 0; i < childNodes.length; i++) {
90187
- childCount += serializeNode(childNodes[i], dom, css);
90188
- }
90189
- newNode.childCount = childCount;
90190
- return 1;
90191
- };
90192
- const serialize = document => {
90193
- const dom = [];
90194
- const css = [];
90195
-
90196
- // Start from document.documentElement (the <html> element)
90197
- const root = document.documentElement || document.body;
90198
- if (!root) {
90199
- return {
90200
- css,
90201
- dom
90202
- };
90203
- }
90204
- let rootChildCount = 0;
90205
- const {
90206
- childNodes
90207
- } = root;
90208
- for (let i = 0; i < childNodes.length; i++) {
90209
- rootChildCount += serializeNode(childNodes[i], dom, css);
90210
- }
90211
- try {
90212
- Object.defineProperty(dom, 'rootChildCount', {
90213
- configurable: true,
90214
- enumerable: false,
90215
- value: rootChildCount
90216
- });
90217
- } catch {
90218
- dom.rootChildCount = rootChildCount;
90219
- }
90220
- return {
90221
- css,
90222
- dom
90223
- };
90224
90343
  };
90225
90344
 
90226
90345
  /* eslint-disable prefer-destructuring */
@@ -90244,10 +90363,20 @@ const updateContent = async (state, uri) => {
90244
90363
  // If scripts are present, execute them via happy-dom and re-serialize the DOM
90245
90364
  if (scripts.length > 0) {
90246
90365
  try {
90247
- const happyDomDocument = executeScripts(content, scripts);
90248
- const serialized = serialize(happyDomDocument);
90366
+ const {
90367
+ document: happyDomDocument,
90368
+ window: happyDomWindow
90369
+ } = createWindow(content);
90370
+ executeScripts(happyDomWindow, happyDomDocument, scripts);
90371
+ const elementMap = new Map();
90372
+ const serialized = serialize(happyDomDocument, elementMap);
90249
90373
  parsedDom = serialized.dom;
90250
90374
  css = serialized.css;
90375
+ set(state.uid, {
90376
+ document: happyDomDocument,
90377
+ elementMap,
90378
+ window: happyDomWindow
90379
+ });
90251
90380
  } catch {
90252
90381
  // If script execution fails, fall back to static HTML parsing
90253
90382
  }
@@ -90295,6 +90424,56 @@ const handleFileEdited = async state => {
90295
90424
  };
90296
90425
  };
90297
90426
 
90427
+ const dispatchInputEvent = (element, window) => {
90428
+ const inputEvent = new window.Event('input', {
90429
+ bubbles: true
90430
+ });
90431
+ dispatchEvent(element, inputEvent);
90432
+ };
90433
+
90434
+ const handleInput = (state, hdId, value) => {
90435
+ // console.log('input,', hdId, value)
90436
+ if (!hdId) {
90437
+ return state;
90438
+ }
90439
+ const happyDomInstance = get(state.uid);
90440
+ if (!happyDomInstance) {
90441
+ return state;
90442
+ }
90443
+ const element = happyDomInstance.elementMap.get(hdId);
90444
+ if (!element) {
90445
+ return state;
90446
+ }
90447
+
90448
+ // console.log({ element })
90449
+ // Update the element's value from the preview
90450
+ element.value = value;
90451
+ // Dispatch input event in happy-dom so event listeners fire
90452
+ dispatchInputEvent(element, happyDomInstance.window);
90453
+
90454
+ // Re-serialize the (potentially mutated) DOM
90455
+ const elementMap = new Map();
90456
+ const serialized = serialize(happyDomInstance.document, elementMap);
90457
+
90458
+ // Update happy-dom state with new element map
90459
+ set(state.uid, {
90460
+ document: happyDomInstance.document,
90461
+ elementMap,
90462
+ window: happyDomInstance.window
90463
+ });
90464
+ const parsedDom = serialized.dom;
90465
+ const {
90466
+ css
90467
+ } = serialized;
90468
+ const parsedNodesChildNodeCount = getParsedNodesChildNodeCount(parsedDom);
90469
+ return {
90470
+ ...state,
90471
+ css,
90472
+ parsedDom,
90473
+ parsedNodesChildNodeCount
90474
+ };
90475
+ };
90476
+
90298
90477
  const loadContent = async state => {
90299
90478
  // Try to register to receive editor change notifications from the editor worker.
90300
90479
  // Use dynamic access and ignore errors so this is safe in environments where
@@ -90382,6 +90561,9 @@ const renderCss = (oldState, newState) => {
90382
90561
  return ['Viewlet.setCss', uid, cssString];
90383
90562
  };
90384
90563
 
90564
+ const HandleInput = 4;
90565
+ const HandleClick = 11;
90566
+
90385
90567
  const getEmptyPreviewDom = () => {
90386
90568
  return [{
90387
90569
  childCount: 1,
@@ -90411,12 +90593,16 @@ const getPreviewDom = state => {
90411
90593
  return [{
90412
90594
  childCount: parsedNodesChildNodeCount,
90413
90595
  className: 'Viewlet Preview',
90596
+ onClick: HandleClick,
90597
+ onInput: HandleInput,
90414
90598
  type: Div$1
90415
90599
  }, ...parsedDom];
90416
90600
  }
90417
90601
  return [{
90418
90602
  childCount: 1,
90419
90603
  className: 'Viewlet Preview',
90604
+ onClick: HandleClick,
90605
+ onInput: HandleInput,
90420
90606
  type: Div$1
90421
90607
  }, {
90422
90608
  childCount: 1,
@@ -90475,18 +90661,21 @@ const render2 = (uid, diffResult) => {
90475
90661
  const {
90476
90662
  newState,
90477
90663
  oldState
90478
- } = get(uid);
90479
- set(uid, newState, newState);
90664
+ } = get$1(uid);
90665
+ set$1(uid, newState, newState);
90480
90666
  const commands = applyRender(oldState, newState, diffResult);
90481
90667
  return commands;
90482
90668
  };
90483
90669
 
90484
- const HandleClick = 11;
90485
-
90486
90670
  const renderEventListeners = () => {
90487
90671
  return [{
90672
+ capture: true,
90488
90673
  name: HandleClick,
90489
- params: ['handleClick', TargetName]
90674
+ params: ['handleClick', 'event.target.dataset.id']
90675
+ }, {
90676
+ capture: true,
90677
+ name: HandleInput,
90678
+ params: ['handleInput', 'event.target.dataset.id', 'event.target.value']
90490
90679
  }];
90491
90680
  };
90492
90681
 
@@ -90541,7 +90730,9 @@ const commandMap = {
90541
90730
  'Preview.create': create,
90542
90731
  'Preview.diff2': diff2,
90543
90732
  'Preview.getCommandIds': getCommandIds,
90733
+ 'Preview.handleClick': wrapCommand(handleClick),
90544
90734
  'Preview.handleFileEdited': wrapCommand(handleFileEdited),
90735
+ 'Preview.handleInput': wrapCommand(handleInput),
90545
90736
  'Preview.loadContent': wrapCommand(loadContent),
90546
90737
  'Preview.render2': render2,
90547
90738
  'Preview.renderEventListeners': renderEventListeners,
@@ -90557,12 +90748,12 @@ const listen = async () => {
90557
90748
  const rpc = await WebWorkerRpcClient.create({
90558
90749
  commandMap: commandMap
90559
90750
  });
90560
- set$1(rpc);
90751
+ set$2(rpc);
90561
90752
  const editorRpc = await LazyTransferMessagePortRpcParent.create({
90562
90753
  commandMap: {},
90563
90754
  send: port => sendMessagePortToEditorWorker(port, 9112)
90564
90755
  });
90565
- set$2(editorRpc);
90756
+ set$3(editorRpc);
90566
90757
  };
90567
90758
 
90568
90759
  const main = async () => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lvce-editor/preview-worker",
3
- "version": "2.0.0",
3
+ "version": "2.2.0",
4
4
  "description": "Preview Worker",
5
5
  "repository": {
6
6
  "type": "git",