@fluid-app/widget-runtime 0.1.4 → 0.1.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. package/README.md +36 -0
  2. package/dist/index.cjs +3 -2
  3. package/dist/index.d.cts +3 -2
  4. package/dist/index.d.mts +3 -2
  5. package/dist/index.mjs +3 -3
  6. package/dist/{remote-dom-renderer-CKvnUH8G.d.mts → remote-dom-renderer-BGIZ0FRi.d.mts} +35 -1
  7. package/dist/remote-dom-renderer-BGIZ0FRi.d.mts.map +1 -0
  8. package/dist/{remote-dom-renderer-f7OWlvvv.cjs → remote-dom-renderer-BlZKfMAA.cjs} +340 -172
  9. package/dist/remote-dom-renderer-BlZKfMAA.cjs.map +1 -0
  10. package/dist/{remote-dom-renderer-CeYSItkU.d.cts → remote-dom-renderer-BoB9jd3B.d.cts} +35 -1
  11. package/dist/remote-dom-renderer-BoB9jd3B.d.cts.map +1 -0
  12. package/dist/{remote-dom-renderer-CpdKWANy.mjs → remote-dom-renderer-Dn3pfVsl.mjs} +340 -172
  13. package/dist/remote-dom-renderer-Dn3pfVsl.mjs.map +1 -0
  14. package/dist/remote-dom-ui-contract-B140dMly.d.mts +632 -0
  15. package/dist/remote-dom-ui-contract-B140dMly.d.mts.map +1 -0
  16. package/dist/remote-dom-ui-contract-BLOyWbT2.d.cts +632 -0
  17. package/dist/remote-dom-ui-contract-BLOyWbT2.d.cts.map +1 -0
  18. package/dist/remote-dom-values-B5UZPDRy.cjs +849 -0
  19. package/dist/remote-dom-values-B5UZPDRy.cjs.map +1 -0
  20. package/dist/remote-dom-values-D4AgBNWr.d.cts.map +1 -1
  21. package/dist/remote-dom-values-EwoLXnSn.d.mts.map +1 -1
  22. package/dist/remote-dom-values-wEbrw5iW.mjs +790 -0
  23. package/dist/remote-dom-values-wEbrw5iW.mjs.map +1 -0
  24. package/dist/remote-dom.cjs +2 -2
  25. package/dist/remote-dom.d.cts +1 -1
  26. package/dist/remote-dom.d.mts +1 -1
  27. package/dist/remote-dom.mjs +2 -2
  28. package/dist/{worker-react-renderer-BKzig_Vy.mjs → worker-react-renderer-BV6ExKCS.mjs} +17 -40
  29. package/dist/worker-react-renderer-BV6ExKCS.mjs.map +1 -0
  30. package/dist/{worker-react-renderer-BvHZ2o0B.cjs → worker-react-renderer-CR-TvErh.cjs} +18 -41
  31. package/dist/worker-react-renderer-CR-TvErh.cjs.map +1 -0
  32. package/dist/worker.cjs +18 -3
  33. package/dist/worker.cjs.map +1 -1
  34. package/dist/worker.d.cts +17 -2
  35. package/dist/worker.d.cts.map +1 -1
  36. package/dist/worker.d.mts +17 -1
  37. package/dist/worker.d.mts.map +1 -1
  38. package/dist/worker.mjs +17 -3
  39. package/dist/worker.mjs.map +1 -1
  40. package/package.json +1 -1
  41. package/dist/remote-dom-renderer-CKvnUH8G.d.mts.map +0 -1
  42. package/dist/remote-dom-renderer-CeYSItkU.d.cts.map +0 -1
  43. package/dist/remote-dom-renderer-CpdKWANy.mjs.map +0 -1
  44. package/dist/remote-dom-renderer-f7OWlvvv.cjs.map +0 -1
  45. package/dist/remote-dom-values-BSAxrOE7.mjs +0 -238
  46. package/dist/remote-dom-values-BSAxrOE7.mjs.map +0 -1
  47. package/dist/remote-dom-values-DbgIedSg.cjs +0 -297
  48. package/dist/remote-dom-values-DbgIedSg.cjs.map +0 -1
  49. package/dist/worker-react-renderer-BKzig_Vy.mjs.map +0 -1
  50. package/dist/worker-react-renderer-BvHZ2o0B.cjs.map +0 -1
@@ -21,10 +21,12 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
21
21
  }) : target, mod));
22
22
  //#endregion
23
23
  const require_Thread = require("./Thread-0eUyc0Hw.cjs");
24
- const require_remote_dom_values = require("./remote-dom-values-DbgIedSg.cjs");
24
+ const require_remote_dom_values = require("./remote-dom-values-B5UZPDRy.cjs");
25
25
  let _remote_dom_core = require("@remote-dom/core");
26
- let _remote_dom_core_elements = require("@remote-dom/core/elements");
27
- let _remote_dom_core_receivers = require("@remote-dom/core/receivers");
26
+ let _remote_dom_react_host = require("@remote-dom/react/host");
27
+ let react = require("react");
28
+ let react_dom = require("react-dom");
29
+ let react_dom_client = require("react-dom/client");
28
30
  let postcss_lib_parse = require("postcss/lib/parse");
29
31
  postcss_lib_parse = __toESM(postcss_lib_parse);
30
32
  let postcss_value_parser = require("postcss-value-parser");
@@ -330,7 +332,6 @@ const MEDIA_SOURCE_TAGS = new Set([
330
332
  "video"
331
333
  ]);
332
334
  const SUPPORTED_REMOTE_DOM_EVENT_SET = new Set(require_remote_dom_values.SUPPORTED_REMOTE_DOM_EVENTS);
333
- const REMOTE_DOM_SEARCH_SORT_EVENT_SET = new Set(require_remote_dom_values.REMOTE_DOM_SEARCH_SORT_EVENTS);
334
335
  function sanitizeRemoteDomTagName(tagName) {
335
336
  if (typeof tagName !== "string") return void 0;
336
337
  const normalizedTagName = tagName.trim().toLowerCase();
@@ -346,7 +347,7 @@ function sanitizeRemoteDomAttributes(tagName, attributes, options = {}) {
346
347
  }
347
348
  return sanitizedAttributes;
348
349
  }
349
- function sanitizeRemoteDomProperty(tagName, rawName, rawValue) {
350
+ function sanitizeRemoteDomProperty(tagName, rawName, rawValue, options = {}) {
350
351
  if (typeof rawName !== "string") return void 0;
351
352
  const normalizedTagName = tagName.trim().toLowerCase();
352
353
  const name = rawName.trim().toLowerCase();
@@ -361,45 +362,54 @@ function sanitizeRemoteDomProperty(tagName, rawName, rawValue) {
361
362
  value: rawValue
362
363
  };
363
364
  }
364
- if (normalizedTagName === "fluid-ui-search-sort") {
365
- if (name === "searchvalue" && typeof rawValue === "string") return {
366
- name: "searchValue",
367
- value: rawValue
368
- };
369
- if (name === "placeholder") return typeof rawValue === "string" || rawValue === void 0 ? {
370
- name: "placeholder",
371
- value: rawValue
372
- } : void 0;
373
- if (name === "sortoptions") {
374
- const value = sanitizeSearchSortOptions(rawValue);
375
- return value === void 0 && rawValue !== void 0 ? void 0 : {
376
- name: "sortOptions",
365
+ const definition = require_remote_dom_values.getRemoteDomUiComponent(normalizedTagName);
366
+ if (definition) {
367
+ const propertyEntry = Object.entries(definition.properties).find(([propertyName]) => propertyName.toLowerCase() === name);
368
+ if (!propertyEntry) return void 0;
369
+ const [propertyName, codec] = propertyEntry;
370
+ if (codec === "navigation-url" && rawValue !== void 0) {
371
+ const value = sanitizeRemoteDomUrl(rawValue, {
372
+ allowHashOnly: true,
373
+ allowMailTo: false,
374
+ allowRelative: true,
375
+ allowTel: false,
376
+ allowedHttpOriginGroups: ["navigation"],
377
+ ...options.allowedAnchorUrls ? { allowedHttpUrls: options.allowedAnchorUrls } : {},
378
+ originPolicy: options.urlOriginPolicy,
379
+ relativeUrlBase: "navigation"
380
+ });
381
+ return value === void 0 ? void 0 : {
382
+ name: propertyName,
377
383
  value
378
384
  };
379
385
  }
380
- if (name === "sortvalue") return typeof rawValue === "string" || rawValue === void 0 ? {
381
- name: "sortValue",
382
- value: rawValue
383
- } : void 0;
384
- }
385
- if (normalizedTagName === "fluid-spacer-widget") {
386
- if (name === "customheight") return typeof rawValue === "string" || rawValue === void 0 ? {
387
- name: "customHeight",
386
+ if (codec === "url" && rawValue !== void 0) {
387
+ const value = sanitizeRemoteDomUrl(rawValue, {
388
+ allowHashOnly: false,
389
+ allowMailTo: false,
390
+ allowRelative: true,
391
+ allowTel: false,
392
+ allowedHttpOriginGroups: ["artifact", "media"],
393
+ originPolicy: options.urlOriginPolicy,
394
+ relativeUrlBase: "artifact"
395
+ });
396
+ return value === void 0 ? void 0 : {
397
+ name: propertyName,
398
+ value
399
+ };
400
+ }
401
+ return isRemoteDomUiPropertyValue(rawValue, codec) ? {
402
+ name: propertyName,
388
403
  value: rawValue
389
404
  } : void 0;
390
- if (name === "previewmode" && typeof rawValue === "boolean") return {
391
- name: "previewMode",
392
- value: rawValue
393
- };
394
405
  }
395
406
  }
396
407
  function isRemoteDomPropertyName(tagName, rawName) {
397
408
  const normalizedTagName = tagName.trim().toLowerCase();
398
409
  const name = rawName.trim().toLowerCase();
399
410
  if (normalizedTagName === "input") return name === "checked" || name === "value";
400
- if (normalizedTagName === "fluid-ui-search-sort") return name === "searchvalue" || name === "placeholder" || name === "sortoptions" || name === "sortvalue";
401
- if (normalizedTagName === "fluid-spacer-widget") return name === "customheight" || name === "previewmode";
402
- return false;
411
+ const definition = require_remote_dom_values.getRemoteDomUiComponent(normalizedTagName);
412
+ return Boolean(definition && Object.keys(definition.properties).some((propertyName) => propertyName.toLowerCase() === name));
403
413
  }
404
414
  function sanitizeRemoteDomAttribute(tagName, rawName, rawValue, options = {}) {
405
415
  if (typeof rawName !== "string") return void 0;
@@ -489,7 +499,8 @@ function sanitizeRemoteDomCssUrlReferences(value, options = {}) {
489
499
  }
490
500
  function isSupportedRemoteDomEventName(eventName, tagName) {
491
501
  if (SUPPORTED_REMOTE_DOM_EVENT_SET.has(eventName)) return true;
492
- return tagName?.trim().toLowerCase() === "fluid-ui-search-sort" && REMOTE_DOM_SEARCH_SORT_EVENT_SET.has(eventName);
502
+ const definition = tagName ? require_remote_dom_values.getRemoteDomUiComponent(tagName.trim().toLowerCase()) : void 0;
503
+ return Boolean(definition?.events.some((event) => event.eventName === eventName));
493
504
  }
494
505
  function serializeRemoteDomEvent(event, targetId, currentTargetId, currentTargetTagName) {
495
506
  if (!isSupportedRemoteDomEventName(event.type, currentTargetTagName)) return;
@@ -508,20 +519,22 @@ function serializeRemoteDomEvent(event, targetId, currentTargetId, currentTarget
508
519
  ...readCustomEventDetail(event)
509
520
  };
510
521
  }
511
- function sanitizeSearchSortOptions(value) {
512
- if (!Array.isArray(value)) return void 0;
513
- const options = [];
514
- for (const option of value) {
515
- if (!isObjectRecord$1(option) || typeof option.label !== "string" || typeof option.value !== "string") return;
516
- options.push({
517
- label: option.label,
518
- value: option.value
519
- });
522
+ function isRemoteDomUiPropertyValue(value, codec) {
523
+ if (value === void 0) return true;
524
+ switch (codec) {
525
+ case "boolean": return typeof value === "boolean";
526
+ case "finite-number-array": return Array.isArray(value) && value.every((item) => typeof item === "number" && Number.isFinite(item));
527
+ case "finite-number": return typeof value === "number" && Number.isFinite(value);
528
+ case "navigation-url": return false;
529
+ case "option-array": return Array.isArray(value) && value.every((item) => isObjectRecord$1(item) && typeof item.label === "string" && typeof item.value === "string" && Object.keys(item).every((key) => key === "label" || key === "value"));
530
+ case "string": return typeof value === "string";
531
+ case "string-array": return Array.isArray(value) && value.every((item) => typeof item === "string");
532
+ case "string-or-string-array": return typeof value === "string" || Array.isArray(value) && value.every((item) => typeof item === "string");
533
+ case "url": return false;
520
534
  }
521
- return options;
522
535
  }
523
536
  function readCustomEventDetail(event) {
524
- if (!(event instanceof CustomEvent) || !REMOTE_DOM_SEARCH_SORT_EVENT_SET.has(event.type)) return {};
537
+ if (!(event instanceof CustomEvent)) return {};
525
538
  if (!require_remote_dom_values.isRemoteDomSerializableValue(event.detail)) return {};
526
539
  try {
527
540
  const detailJson = JSON.stringify(event.detail);
@@ -585,6 +598,7 @@ function hasControlCharacter(value) {
585
598
  }
586
599
  function isAllowedAttributeName(tagName, name) {
587
600
  if (name.length === 0 || name.startsWith("on") || name === "style" || name === "srcdoc" || name.includes(":") || !/^[a-z][a-z0-9_.-]*$/u.test(name)) return false;
601
+ if (name === "data-fluid-remote-dom-node-id") return false;
588
602
  if (name.startsWith("data-")) return true;
589
603
  if (GLOBAL_ALLOWED_ATTRIBUTES.has(name)) return true;
590
604
  return TAG_ALLOWED_ATTRIBUTES[tagName]?.has(name) === true;
@@ -1019,14 +1033,26 @@ function normalizeUpdatePropertyRecordForHost(options, state, mutation, pendingE
1019
1033
  eventName: property,
1020
1034
  listener
1021
1035
  });
1022
- return { ok: true };
1036
+ return {
1037
+ ok: true,
1038
+ record: [
1039
+ _remote_dom_core.MUTATION_TYPE_UPDATE_PROPERTY,
1040
+ id,
1041
+ property,
1042
+ listener,
1043
+ _remote_dom_core.UPDATE_PROPERTY_TYPE_EVENT_LISTENER
1044
+ ]
1045
+ };
1023
1046
  }
1024
1047
  if (value !== void 0 && !require_remote_dom_values.isRemoteDomSerializableValue(value)) return {
1025
1048
  ok: false,
1026
1049
  message: `Remote DOM mutation property "${property}" was not serializable.`
1027
1050
  };
1028
1051
  if (updateType === _remote_dom_core.UPDATE_PROPERTY_TYPE_PROPERTY) {
1029
- const sanitizedProperty = sanitizeRemoteDomProperty(node.localName, property, value);
1052
+ const sanitizedProperty = sanitizeRemoteDomProperty(node.localName, property, value, {
1053
+ ...options.allowedAnchorUrls ? { allowedAnchorUrls: options.allowedAnchorUrls } : {},
1054
+ urlOriginPolicy: options.urlOriginPolicy
1055
+ });
1030
1056
  if (sanitizedProperty) return {
1031
1057
  ok: true,
1032
1058
  record: [
@@ -1106,7 +1132,7 @@ function normalizeRemoteNodeForHost(options, state, value, seen, pendingEventLis
1106
1132
  ok: false,
1107
1133
  message: `Remote DOM element "${value.id}" used an unsupported tag name.`
1108
1134
  };
1109
- queueRemoteNodeEventListeners(value.id, tagName, value.eventListeners, pendingEventListenerUpdates);
1135
+ const eventListeners = sanitizeRemoteNodeEventListeners(value.id, tagName, value.eventListeners, pendingEventListenerUpdates);
1110
1136
  if (!Array.isArray(value.children)) return {
1111
1137
  ok: false,
1112
1138
  message: `Remote DOM element "${value.id}" did not contain a valid children array.`
@@ -1125,7 +1151,10 @@ function normalizeRemoteNodeForHost(options, state, value, seen, pendingEventLis
1125
1151
  const mutableAttributeProperties = { ...properties };
1126
1152
  attributeProperties = mutableAttributeProperties;
1127
1153
  for (const [rawName, rawValue] of Object.entries(value.properties)) {
1128
- const property = sanitizeRemoteDomProperty(tagName, rawName, rawValue);
1154
+ const property = sanitizeRemoteDomProperty(tagName, rawName, rawValue, {
1155
+ ...options.allowedAnchorUrls ? { allowedAnchorUrls: options.allowedAnchorUrls } : {},
1156
+ urlOriginPolicy: options.urlOriginPolicy
1157
+ });
1129
1158
  if (property) {
1130
1159
  remoteProperties[property.name] = property.value;
1131
1160
  delete mutableAttributeProperties[rawName];
@@ -1152,13 +1181,15 @@ function normalizeRemoteNodeForHost(options, state, value, seen, pendingEventLis
1152
1181
  element: tagName,
1153
1182
  attributes: remoteAttributes,
1154
1183
  properties: remoteProperties,
1184
+ eventListeners,
1155
1185
  children
1156
1186
  },
1157
1187
  nodeCount
1158
1188
  };
1159
1189
  }
1160
- function queueRemoteNodeEventListeners(id, tagName, eventListeners, pendingEventListenerUpdates) {
1161
- if (!isObjectRecord(eventListeners)) return;
1190
+ function sanitizeRemoteNodeEventListeners(id, tagName, eventListeners, pendingEventListenerUpdates) {
1191
+ const sanitized = {};
1192
+ if (!isObjectRecord(eventListeners)) return sanitized;
1162
1193
  for (const [eventName, listener] of Object.entries(eventListeners)) {
1163
1194
  if (!require_remote_dom_values.isSafeRemoteDomPropertyKey(eventName) || !isSupportedRemoteDomEventName(eventName, tagName)) continue;
1164
1195
  pendingEventListenerUpdates.push({
@@ -1166,7 +1197,9 @@ function queueRemoteNodeEventListeners(id, tagName, eventListeners, pendingEvent
1166
1197
  eventName,
1167
1198
  listener: typeof listener === "function" ? listener : null
1168
1199
  });
1200
+ if (typeof listener === "function") sanitized[eventName] = listener;
1169
1201
  }
1202
+ return sanitized;
1170
1203
  }
1171
1204
  function validateRemoteNodeId(id, seen) {
1172
1205
  if (id === _remote_dom_core.ROOT_ID) return {
@@ -1704,8 +1737,207 @@ function mirrorClasses({ host, sourceElement, classNames, managedClasses, origin
1704
1737
  }
1705
1738
  //#endregion
1706
1739
  //#region src/remote-dom-renderer.ts
1707
- const DEFAULT_MAX_MUTATION_BATCH_SIZE = 100;
1740
+ const DEFAULT_MAX_MUTATION_BATCH_SIZE = 1e3;
1708
1741
  const DEFAULT_MAX_DOM_NODE_COUNT = 1e3;
1742
+ const BOOLEAN_ATTRIBUTE_REACT_PROPERTIES = new Map([
1743
+ ["allowfullscreen", "allowFullScreen"],
1744
+ ["async", "async"],
1745
+ ["autofocus", "autoFocus"],
1746
+ ["autoplay", "autoPlay"],
1747
+ ["checked", "checked"],
1748
+ ["controls", "controls"],
1749
+ ["default", "default"],
1750
+ ["defer", "defer"],
1751
+ ["disabled", "disabled"],
1752
+ ["disablepictureinpicture", "disablePictureInPicture"],
1753
+ ["disableremoteplayback", "disableRemotePlayback"],
1754
+ ["formnovalidate", "formNoValidate"],
1755
+ ["hidden", "hidden"],
1756
+ ["inert", "inert"],
1757
+ ["itemscope", "itemScope"],
1758
+ ["loop", "loop"],
1759
+ ["multiple", "multiple"],
1760
+ ["muted", "muted"],
1761
+ ["nomodule", "noModule"],
1762
+ ["novalidate", "noValidate"],
1763
+ ["open", "open"],
1764
+ ["playsinline", "playsInline"],
1765
+ ["readonly", "readOnly"],
1766
+ ["required", "required"],
1767
+ ["reversed", "reversed"],
1768
+ ["selected", "selected"]
1769
+ ]);
1770
+ function createRemoteComponentRenderers(options, isDisposed) {
1771
+ const renderers = /* @__PURE__ */ new Map();
1772
+ const fluidTags = new Set(require_remote_dom_values.REMOTE_DOM_UI_COMPONENTS.map(({ tagName }) => tagName));
1773
+ for (const tagName of require_remote_dom_values.REMOTE_DOM_ALLOWED_TAG_NAMES) {
1774
+ if (fluidTags.has(tagName)) continue;
1775
+ renderers.set(tagName, createNativeRemoteComponentRenderer(tagName, options, isDisposed));
1776
+ }
1777
+ for (const definition of require_remote_dom_values.REMOTE_DOM_UI_COMPONENTS) {
1778
+ if (definition.contractVersion !== "1") continue;
1779
+ renderers.set(definition.tagName, createNativeRemoteComponentRenderer(definition.tagName, options, isDisposed));
1780
+ }
1781
+ renderers.set("remote-fragment", _remote_dom_react_host.RemoteFragmentRenderer);
1782
+ for (const [tagName, renderer] of options.componentRenderers ?? []) {
1783
+ if (!fluidTags.has(tagName)) continue;
1784
+ renderers.set(tagName, renderer);
1785
+ }
1786
+ return renderers;
1787
+ }
1788
+ function createNativeRemoteComponentRenderer(tagName, options, isDisposed) {
1789
+ function NativeRemoteComponent({ element, receiver, components }) {
1790
+ const attachedElement = (0, _remote_dom_react_host.useRemoteReceived)(element, receiver);
1791
+ const elementRef = (0, react.useRef)(null);
1792
+ (0, react.useEffect)(() => {
1793
+ const hostElement = elementRef.current;
1794
+ if (!hostElement || !attachedElement) return;
1795
+ const cleanup = [];
1796
+ for (const [eventName, listener] of Object.entries(attachedElement.eventListeners)) {
1797
+ if (typeof listener !== "function") continue;
1798
+ const handleEvent = (event) => {
1799
+ if (hasCloserRemoteEventListener(receiver, event, attachedElement.id, eventName)) return;
1800
+ deliverRemoteDomEvent({
1801
+ event,
1802
+ eventName,
1803
+ currentTargetId: attachedElement.id,
1804
+ currentTargetTagName: tagName,
1805
+ listener,
1806
+ options,
1807
+ isDisposed
1808
+ });
1809
+ };
1810
+ hostElement.addEventListener(eventName, handleEvent);
1811
+ cleanup.push(() => hostElement.removeEventListener(eventName, handleEvent));
1812
+ }
1813
+ return () => {
1814
+ for (const dispose of cleanup) dispose();
1815
+ };
1816
+ }, [
1817
+ attachedElement,
1818
+ attachedElement?.version,
1819
+ receiver
1820
+ ]);
1821
+ (0, react.useLayoutEffect)(() => {
1822
+ const hostElement = elementRef.current;
1823
+ if (!(hostElement instanceof HTMLInputElement) || !attachedElement) return;
1824
+ const value = attachedElement.properties.value;
1825
+ if (typeof value === "string" && hostElement.value !== value) hostElement.value = value;
1826
+ const checked = attachedElement.properties.checked;
1827
+ if (typeof checked === "boolean" && hostElement.checked !== checked) hostElement.checked = checked;
1828
+ }, [attachedElement, attachedElement?.version]);
1829
+ if (!attachedElement) return null;
1830
+ const domProps = {};
1831
+ for (const [name, value] of Object.entries({
1832
+ ...attachedElement.attributes,
1833
+ ...attachedElement.properties
1834
+ })) {
1835
+ if (tagName === "input" && (name === "checked" || name === "value")) continue;
1836
+ const booleanProperty = BOOLEAN_ATTRIBUTE_REACT_PROPERTIES.get(name);
1837
+ domProps[booleanProperty ?? toReactDomPropertyName(name)] = booleanProperty && typeof value === "string" ? true : value;
1838
+ }
1839
+ domProps["data-fluid-remote-dom-node-id"] = attachedElement.id;
1840
+ domProps.ref = elementRef;
1841
+ const renderOptions = {
1842
+ receiver,
1843
+ components
1844
+ };
1845
+ return (0, react.createElement)(tagName, domProps, ...attachedElement.children.map((child) => (0, _remote_dom_react_host.renderRemoteNode)(child, renderOptions)));
1846
+ }
1847
+ NativeRemoteComponent.displayName = `RemoteDomNative(${tagName})`;
1848
+ return NativeRemoteComponent;
1849
+ }
1850
+ function RemoteRootEventBoundary({ receiver, container, options, isDisposed, children }) {
1851
+ const root = (0, _remote_dom_react_host.useRemoteReceived)(receiver.root, receiver);
1852
+ (0, react.useEffect)(() => {
1853
+ if (!root) return;
1854
+ const cleanup = [];
1855
+ for (const [eventName, listener] of Object.entries(root.eventListeners)) {
1856
+ if (typeof listener !== "function") continue;
1857
+ const handleEvent = (event) => {
1858
+ if (hasCloserRemoteEventListener(receiver, event, _remote_dom_core.ROOT_ID, eventName)) return;
1859
+ deliverRemoteDomEvent({
1860
+ event,
1861
+ eventName,
1862
+ currentTargetId: _remote_dom_core.ROOT_ID,
1863
+ listener,
1864
+ options,
1865
+ isDisposed
1866
+ });
1867
+ };
1868
+ container.addEventListener(eventName, handleEvent, { capture: true });
1869
+ cleanup.push(() => container.removeEventListener(eventName, handleEvent, { capture: true }));
1870
+ }
1871
+ return () => {
1872
+ for (const dispose of cleanup) dispose();
1873
+ };
1874
+ }, [
1875
+ container,
1876
+ isDisposed,
1877
+ options,
1878
+ receiver,
1879
+ root,
1880
+ root?.version
1881
+ ]);
1882
+ return children;
1883
+ }
1884
+ function deliverRemoteDomEvent({ event, eventName, currentTargetId, currentTargetTagName, listener, options, isDisposed }) {
1885
+ if (eventName === "submit" && event.cancelable) event.preventDefault();
1886
+ const targetId = resolveRenderedRemoteNodeId(event) ?? currentTargetId;
1887
+ const payload = serializeRemoteDomEvent(event, targetId, currentTargetId, currentTargetTagName);
1888
+ if (!payload) return;
1889
+ try {
1890
+ const result = listener({
1891
+ targetId,
1892
+ detail: toSerializableEventPayload(payload)
1893
+ });
1894
+ if (isPromiseLike(result)) result.catch((error) => {
1895
+ if (!isDisposed()) reportError(options, "Remote DOM event delivery failed", error);
1896
+ });
1897
+ } catch (error) {
1898
+ if (!isDisposed()) reportError(options, "Remote DOM event delivery failed", error);
1899
+ }
1900
+ }
1901
+ function hasCloserRemoteEventListener(receiver, event, currentTargetId, eventName) {
1902
+ for (const pathTarget of event.composedPath()) {
1903
+ if (!(pathTarget instanceof Element)) continue;
1904
+ const id = pathTarget.getAttribute("data-fluid-remote-dom-node-id");
1905
+ if (!id) continue;
1906
+ if (id === currentTargetId) return false;
1907
+ const remoteNode = receiver.get({ id });
1908
+ if (remoteNode && "eventListeners" in remoteNode && typeof remoteNode.eventListeners[eventName] === "function") return true;
1909
+ }
1910
+ return false;
1911
+ }
1912
+ function resolveRenderedRemoteNodeId(event) {
1913
+ for (const pathTarget of event.composedPath()) {
1914
+ if (!(pathTarget instanceof Element)) continue;
1915
+ const id = pathTarget.getAttribute("data-fluid-remote-dom-node-id");
1916
+ if (id) return id;
1917
+ }
1918
+ }
1919
+ function toReactDomPropertyName(name) {
1920
+ switch (name) {
1921
+ case "class": return "className";
1922
+ case "for": return "htmlFor";
1923
+ case "readonly": return "readOnly";
1924
+ case "tabindex": return "tabIndex";
1925
+ default: return name;
1926
+ }
1927
+ }
1928
+ function getSupportedUiContractVersions(componentRenderers) {
1929
+ return require_remote_dom_values.REMOTE_DOM_UI_COMPONENTS.every(({ contractVersion, tagName }) => contractVersion !== "2" || componentRenderers.has(tagName)) ? require_remote_dom_values.REMOTE_DOM_SUPPORTED_UI_CONTRACT_VERSIONS : require_remote_dom_values.REMOTE_DOM_SUPPORTED_UI_CONTRACT_VERSIONS.filter((version) => version !== "2");
1930
+ }
1931
+ /**
1932
+ * Mounts one Remote DOM widget worker into an isolated shadow root.
1933
+ *
1934
+ * The low-level runtime supports native Remote DOM elements by default. A host
1935
+ * that supports worker-facing Fluid UI components must also provide the full
1936
+ * reviewed {@link MountRemoteDomWidgetOptions.componentRenderers} map, a
1937
+ * containment-aware {@link MountRemoteDomWidgetOptions.wrapRemoteRoot}, and
1938
+ * the matching Fluid component CSS. The runtime advertises Fluid UI contract
1939
+ * v2 only when every v2 renderer is present.
1940
+ */
1709
1941
  function mountRemoteDomWidget(options) {
1710
1942
  const initialProps = options.props ?? null;
1711
1943
  assertSerializable(initialProps, "Initial widget props");
@@ -1752,35 +1984,25 @@ function mountRemoteDomWidget(options) {
1752
1984
  throw error;
1753
1985
  }
1754
1986
  let state;
1755
- const pendingListenerUpdates = /* @__PURE__ */ new Map();
1756
- const connection = createSanitizingRemoteConnection({
1757
- connection: new _remote_dom_core_receivers.DOMRemoteReceiver({
1758
- root: shadowRoot,
1759
- call: () => {
1760
- throw new RemoteDomConnectionCallDeniedError("remote element method");
1761
- }
1762
- }).connection,
1763
- ...options.allowedAnchorUrls ? { allowedAnchorUrls: options.allowedAnchorUrls } : {},
1764
- urlOriginPolicy,
1765
- maxMutationBatchSize: options.maxMutationBatchSize ?? DEFAULT_MAX_MUTATION_BATCH_SIZE,
1766
- maxDomNodeCount: options.maxDomNodeCount ?? DEFAULT_MAX_DOM_NODE_COUNT,
1767
- onEventListenerUpdate: (targetId, eventName, listener) => {
1768
- pendingListenerUpdates.set(subscriptionKey(targetId, eventName), {
1769
- targetId,
1770
- eventName,
1771
- listener
1772
- });
1773
- },
1774
- onNodeLimitExceeded: () => {
1775
- reportError(options, "Remote DOM widget exceeded its DOM node limit");
1776
- },
1777
- onInvalidRecord: (message) => reportError(options, message),
1778
- onMutateError: (error) => reportError(options, "Remote DOM mutation could not be applied", error)
1987
+ const receiver = new _remote_dom_react_host.RemoteReceiver({
1988
+ retain: require_Thread.retain,
1989
+ release: require_Thread.release
1779
1990
  });
1991
+ const renderContainer = document.createElement("div");
1992
+ renderContainer.setAttribute("data-fluid-remote-dom-render-root", "");
1993
+ const portalContainer = document.createElement("div");
1994
+ portalContainer.setAttribute("data-fluid-remote-dom-portal-container", "");
1995
+ shadowRoot.append(renderContainer, portalContainer);
1780
1996
  const submitListener = createSubmitPreventionListener();
1781
1997
  const navigationListener = (event) => {
1782
1998
  handleNavigationIntent(state, event);
1783
1999
  };
2000
+ const reactRoot = (0, react_dom_client.createRoot)(renderContainer, { onUncaughtError(error) {
2001
+ reportError(options, "Remote DOM host rendering failed", error);
2002
+ queueMicrotask(() => {
2003
+ if (!state.disposed) disposeAfterFailure(state, false);
2004
+ });
2005
+ } });
1784
2006
  state = {
1785
2007
  options,
1786
2008
  shadowRoot,
@@ -1789,14 +2011,40 @@ function mountRemoteDomWidget(options) {
1789
2011
  styles,
1790
2012
  urlOriginPolicy,
1791
2013
  navigationOrigin,
1792
- nodesById: new Map([[_remote_dom_core.ROOT_ID, shadowRoot]]),
1793
- subscriptions: /* @__PURE__ */ new Map(),
1794
- pendingListenerUpdates,
2014
+ receiver,
2015
+ reactRoot,
2016
+ renderContainer,
2017
+ portalContainer,
1795
2018
  submitListener,
1796
2019
  navigationListener,
1797
2020
  disposed: false,
1798
2021
  cleanupStarted: false
1799
2022
  };
2023
+ const componentRenderers = createRemoteComponentRenderers(options, () => state.disposed);
2024
+ const remoteRoot = (0, react.createElement)(RemoteRootEventBoundary, {
2025
+ receiver,
2026
+ container: renderContainer,
2027
+ options,
2028
+ isDisposed: () => state?.disposed ?? false
2029
+ }, (0, react.createElement)(_remote_dom_react_host.RemoteRootRenderer, {
2030
+ receiver,
2031
+ components: componentRenderers
2032
+ }));
2033
+ (0, react_dom.flushSync)(() => {
2034
+ reactRoot.render(options.wrapRemoteRoot ? options.wrapRemoteRoot(remoteRoot, portalContainer) : remoteRoot);
2035
+ });
2036
+ const connection = createSanitizingRemoteConnection({
2037
+ connection: receiver.connection,
2038
+ ...options.allowedAnchorUrls ? { allowedAnchorUrls: options.allowedAnchorUrls } : {},
2039
+ urlOriginPolicy,
2040
+ maxMutationBatchSize: options.maxMutationBatchSize ?? DEFAULT_MAX_MUTATION_BATCH_SIZE,
2041
+ maxDomNodeCount: options.maxDomNodeCount ?? DEFAULT_MAX_DOM_NODE_COUNT,
2042
+ onNodeLimitExceeded: () => {
2043
+ reportError(options, "Remote DOM widget exceeded its DOM node limit");
2044
+ },
2045
+ onInvalidRecord: (message) => reportError(options, message),
2046
+ onMutateError: (error) => reportError(options, "Remote DOM mutation could not be applied", error)
2047
+ });
1800
2048
  shadowRoot.addEventListener("submit", submitListener, { capture: true });
1801
2049
  shadowRoot.addEventListener("click", navigationListener, { capture: true });
1802
2050
  worker.onerror = (event) => {
@@ -1808,10 +2056,8 @@ function mountRemoteDomWidget(options) {
1808
2056
  const hostApi = {
1809
2057
  mutate(records) {
1810
2058
  if (state.disposed) return;
1811
- connection.mutate(records);
1812
- rebuildNodeIndex(state);
2059
+ (0, react_dom.flushSync)(() => connection.mutate(records));
1813
2060
  enforceAllowedAnchorSafety(state);
1814
- flushPendingListenerUpdates(state);
1815
2061
  },
1816
2062
  callCapability(capability, method, payload) {
1817
2063
  return callCapability(state, capability, method, payload);
@@ -1823,6 +2069,7 @@ function mountRemoteDomWidget(options) {
1823
2069
  const ready = Promise.all([styles.ready, thread.imports.mount({
1824
2070
  widgetType: options.artifact.widgetType,
1825
2071
  props: initialProps,
2072
+ supportedUiContractVersions: getSupportedUiContractVersions(componentRenderers),
1826
2073
  ...options.artifact.networkAccessGranted === true ? {
1827
2074
  networkAccessGranted: true,
1828
2075
  hostOrigin: options.hostOrigin
@@ -1872,82 +2119,6 @@ function assertSerializableCapabilityResult(result) {
1872
2119
  if (!require_remote_dom_values.isRemoteDomSerializableValue(result)) throw new Error("Remote DOM capability result was not serializable");
1873
2120
  return result;
1874
2121
  }
1875
- function rebuildNodeIndex(state) {
1876
- state.nodesById.clear();
1877
- state.nodesById.set(_remote_dom_core.ROOT_ID, state.shadowRoot);
1878
- const visit = (node) => {
1879
- state.nodesById.set((0, _remote_dom_core_elements.remoteId)(node), node);
1880
- for (const child of node.childNodes) visit(child);
1881
- };
1882
- for (const child of state.shadowRoot.childNodes) if (!isWidgetStyleNode(child)) visit(child);
1883
- for (const subscription of Array.from(state.subscriptions.values())) if (!state.nodesById.has(subscription.targetId)) unsubscribe(state, subscription.targetId, subscription.eventName);
1884
- }
1885
- function flushPendingListenerUpdates(state) {
1886
- for (const update of state.pendingListenerUpdates.values()) if (update.listener === null) unsubscribe(state, update.targetId, update.eventName);
1887
- else subscribe(state, update.targetId, update.eventName, update.listener);
1888
- state.pendingListenerUpdates.clear();
1889
- }
1890
- function subscribe(state, targetId, eventName, proxy) {
1891
- const target = state.nodesById.get(targetId);
1892
- const tagName = target instanceof Element ? target.localName : void 0;
1893
- if (!target || !isSupportedRemoteDomEventName(eventName, tagName)) return;
1894
- unsubscribe(state, targetId, eventName);
1895
- const capture = targetId === _remote_dom_core.ROOT_ID;
1896
- const listener = (event) => {
1897
- if (!capture && hasCloserEventSubscription(state, event, target, eventName)) return;
1898
- if (eventName === "submit" && event.cancelable) event.preventDefault();
1899
- const actualTargetId = resolveEventTargetId(state, event);
1900
- if (!actualTargetId) return;
1901
- const eventPayload = serializeRemoteDomEvent(event, actualTargetId, targetId, tagName);
1902
- if (!eventPayload) return;
1903
- try {
1904
- const result = proxy({
1905
- targetId: actualTargetId,
1906
- detail: toSerializableEventPayload(eventPayload)
1907
- });
1908
- if (isPromiseLike(result)) result.catch((error) => {
1909
- reportEventDeliveryFailure(state, error);
1910
- });
1911
- } catch (error) {
1912
- reportEventDeliveryFailure(state, error);
1913
- }
1914
- };
1915
- target.addEventListener(eventName, listener, { capture });
1916
- require_Thread.retain(proxy);
1917
- state.subscriptions.set(subscriptionKey(targetId, eventName), {
1918
- targetId,
1919
- eventName,
1920
- target,
1921
- listener,
1922
- capture,
1923
- proxy
1924
- });
1925
- }
1926
- function hasCloserEventSubscription(state, event, subscriptionTarget, eventName) {
1927
- for (const pathTarget of event.composedPath()) {
1928
- if (pathTarget === subscriptionTarget) return false;
1929
- if (!(pathTarget instanceof Node)) continue;
1930
- const pathTargetId = (0, _remote_dom_core_elements.remoteId)(pathTarget);
1931
- if (state.nodesById.get(pathTargetId) === pathTarget && state.subscriptions.has(subscriptionKey(pathTargetId, eventName))) return true;
1932
- }
1933
- return false;
1934
- }
1935
- function unsubscribe(state, targetId, eventName) {
1936
- const key = subscriptionKey(targetId, eventName);
1937
- const subscription = state.subscriptions.get(key);
1938
- if (!subscription) return;
1939
- subscription.target.removeEventListener(eventName, subscription.listener, { capture: subscription.capture });
1940
- require_Thread.release(subscription.proxy);
1941
- state.subscriptions.delete(key);
1942
- }
1943
- function resolveEventTargetId(state, event) {
1944
- for (const target of event.composedPath()) {
1945
- if (target === state.shadowRoot) return _remote_dom_core.ROOT_ID;
1946
- if (!(target instanceof Node)) continue;
1947
- const id = (0, _remote_dom_core_elements.remoteId)(target);
1948
- if (state.nodesById.get(id) === target) return id;
1949
- }
1950
- }
1951
2122
  function handleNavigationIntent(state, event) {
1952
2123
  if (!(event instanceof MouseEvent)) return;
1953
2124
  const anchor = (event.target instanceof Element ? event.target : event.target instanceof Text ? event.target.parentElement : null)?.closest("a[href]");
@@ -2002,10 +2173,7 @@ function disposeMount(state, notifyWorker = true) {
2002
2173
  state.shadowRoot.removeEventListener("click", state.navigationListener, { capture: true });
2003
2174
  }
2004
2175
  ];
2005
- for (const subscription of Array.from(state.subscriptions.values())) cleanupSteps.push(() => {
2006
- unsubscribe(state, subscription.targetId, subscription.eventName);
2007
- });
2008
- cleanupSteps.push(() => state.pendingListenerUpdates.clear(), () => state.styles.dispose(), () => clearRenderedRemoteDomNodes(state.shadowRoot), () => state.nodesById.clear());
2176
+ cleanupSteps.push(() => (0, react_dom.flushSync)(() => state.reactRoot.unmount()), () => clearRemoteReceiver(state.receiver), () => state.styles.dispose(), () => clearRenderedRemoteDomNodes(state.shadowRoot));
2009
2177
  const cleanupErrors = collectCleanupErrors(cleanupSteps);
2010
2178
  if (notifyWorker) Promise.resolve(state.thread.imports.dispose()).catch(() => void 0);
2011
2179
  queueMicrotask(() => {
@@ -2020,10 +2188,6 @@ function disposeAfterFailure(state, notifyWorker = true) {
2020
2188
  reportError(state.options, "Remote DOM widget cleanup failed after an earlier error", cleanupError);
2021
2189
  }
2022
2190
  }
2023
- function reportEventDeliveryFailure(state, error) {
2024
- if (state.disposed) return;
2025
- reportError(state.options, "Remote DOM event delivery failed", error);
2026
- }
2027
2191
  function createShadowRoot(host) {
2028
2192
  if (host.shadowRoot) {
2029
2193
  host.shadowRoot.replaceChildren();
@@ -2089,8 +2253,8 @@ function resolveInternalNavigationUrl(href, navigationOrigin) {
2089
2253
  return;
2090
2254
  }
2091
2255
  }
2092
- function findNodeId(state, node) {
2093
- for (const [id, candidate] of state.nodesById) if (candidate === node) return id;
2256
+ function findNodeId(_state, node) {
2257
+ return (node instanceof Element ? node : node.parentElement)?.closest("[data-fluid-remote-dom-node-id]")?.getAttribute("data-fluid-remote-dom-node-id") ?? void 0;
2094
2258
  }
2095
2259
  function isWidgetStyleNode(node) {
2096
2260
  return node instanceof HTMLStyleElement && node.hasAttribute("data-fluid-widget-style");
@@ -2098,6 +2262,13 @@ function isWidgetStyleNode(node) {
2098
2262
  function clearRenderedRemoteDomNodes(shadowRoot) {
2099
2263
  for (const child of Array.from(shadowRoot.childNodes)) if (!isWidgetStyleNode(child)) child.remove();
2100
2264
  }
2265
+ function clearRemoteReceiver(receiver) {
2266
+ while (receiver.root.children.length > 0) receiver.connection.mutate([[
2267
+ _remote_dom_core.MUTATION_TYPE_REMOVE_CHILD,
2268
+ _remote_dom_core.ROOT_ID,
2269
+ 0
2270
+ ]]);
2271
+ }
2101
2272
  function toSerializableEventPayload(payload) {
2102
2273
  const result = {
2103
2274
  bubbles: payload.bubbles,
@@ -2202,9 +2373,6 @@ function reportCleanupErrors(options, message, errors) {
2202
2373
  if (errors.length === 0) return;
2203
2374
  reportError(options, message, errors.length === 1 ? errors[0] : new AggregateError(errors, "Multiple cleanup operations failed"));
2204
2375
  }
2205
- function subscriptionKey(targetId, eventName) {
2206
- return `${targetId}:${eventName}`;
2207
- }
2208
2376
  function isPromiseLike(value) {
2209
2377
  return typeof value === "object" && value !== null && "then" in value && typeof value.then === "function";
2210
2378
  }
@@ -2222,4 +2390,4 @@ Object.defineProperty(exports, "mountRemoteDomWidget", {
2222
2390
  }
2223
2391
  });
2224
2392
 
2225
- //# sourceMappingURL=remote-dom-renderer-f7OWlvvv.cjs.map
2393
+ //# sourceMappingURL=remote-dom-renderer-BlZKfMAA.cjs.map