@pie-players/pie-players-shared 0.3.73 → 0.3.75

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 (137) hide show
  1. package/README.md +30 -2
  2. package/dist/components/vendor/nds/README.md +7 -5
  3. package/dist/config/profile.d.ts +0 -2
  4. package/dist/config/profile.js +2 -8
  5. package/dist/i18n/index.d.ts +2 -3
  6. package/dist/i18n/index.js +2 -3
  7. package/dist/i18n/messages/en-US.d.ts +11 -24
  8. package/dist/i18n/messages/en-US.js +13 -26
  9. package/dist/i18n/messages/nl-NL.d.ts +7 -24
  10. package/dist/i18n/messages/nl-NL.js +9 -26
  11. package/dist/i18n/provider.d.ts +4 -5
  12. package/dist/i18n/provider.js +4 -5
  13. package/dist/i18n/types.d.ts +4 -5
  14. package/dist/index.d.ts +4 -2
  15. package/dist/index.js +3 -1
  16. package/dist/instrumentation/index.d.ts +5 -4
  17. package/dist/instrumentation/index.js +5 -4
  18. package/dist/instrumentation/new-relic-agent.d.ts +20 -0
  19. package/dist/instrumentation/new-relic-agent.js +24 -0
  20. package/dist/instrumentation/providers/BaseInstrumentationProvider.d.ts +5 -4
  21. package/dist/instrumentation/providers/NewRelicInstrumentationProvider.d.ts +7 -7
  22. package/dist/instrumentation/providers/NewRelicInstrumentationProvider.js +21 -32
  23. package/dist/instrumentation/types.d.ts +7 -3
  24. package/dist/loader-config.d.ts +16 -6
  25. package/dist/loaders/controller-loading.d.ts +14 -0
  26. package/dist/loaders/controller-loading.js +12 -0
  27. package/dist/loaders/defaults.d.ts +10 -0
  28. package/dist/loaders/defaults.js +10 -0
  29. package/dist/loaders/element-loader.d.ts +20 -8
  30. package/dist/loaders/element-loader.js +102 -36
  31. package/dist/loaders/element-package-policy.d.ts +2 -0
  32. package/dist/loaders/element-package-policy.js +2 -1
  33. package/dist/loaders/esm-adapter.d.ts +29 -0
  34. package/dist/loaders/esm-adapter.js +635 -102
  35. package/dist/loaders/iife-adapter.d.ts +0 -5
  36. package/dist/loaders/iife-adapter.js +3 -9
  37. package/dist/loaders/index.d.ts +12 -3
  38. package/dist/loaders/index.js +9 -3
  39. package/dist/loaders/mathjax-events.d.ts +22 -0
  40. package/dist/loaders/mathjax-events.js +65 -0
  41. package/dist/loaders/module-shim.d.ts +12 -0
  42. package/dist/loaders/module-shim.js +12 -0
  43. package/dist/loaders/preloaded-alignment.d.ts +24 -0
  44. package/dist/loaders/preloaded-alignment.js +57 -0
  45. package/dist/loaders/preloaded-authored-tags.d.ts +26 -0
  46. package/dist/loaders/preloaded-authored-tags.js +69 -0
  47. package/dist/loaders/preloaded-registration.d.ts +72 -0
  48. package/dist/loaders/preloaded-registration.js +141 -0
  49. package/dist/media/index.d.ts +69 -0
  50. package/dist/media/index.js +194 -0
  51. package/dist/pie/authoring.d.ts +1 -1
  52. package/dist/pie/authoring.js +25 -18
  53. package/dist/pie/element-announcements.d.ts +39 -0
  54. package/dist/pie/element-announcements.js +67 -0
  55. package/dist/pie/element-observer.d.ts +3 -1
  56. package/dist/pie/element-observer.js +1 -0
  57. package/dist/pie/elements-rendered.d.ts +25 -0
  58. package/dist/pie/elements-rendered.js +96 -0
  59. package/dist/pie/index.d.ts +8 -10
  60. package/dist/pie/index.js +9 -10
  61. package/dist/pie/initialization.js +13 -22
  62. package/dist/pie/initialize-element.d.ts +3 -0
  63. package/dist/pie/initialize-element.js +3 -4
  64. package/dist/pie/instrumentation-event-bridge.js +23 -1
  65. package/dist/pie/instrumentation-event-map.js +11 -25
  66. package/dist/pie/instrumentation-provider-resolution.d.ts +2 -1
  67. package/dist/pie/instrumentation-provider-resolution.js +0 -1
  68. package/dist/pie/item-controller.js +4 -0
  69. package/dist/pie/item-session-contract.d.ts +8 -0
  70. package/dist/pie/item-session-contract.js +30 -9
  71. package/dist/pie/logger.d.ts +6 -0
  72. package/dist/pie/logger.js +10 -0
  73. package/dist/pie/markup-math.d.ts +20 -0
  74. package/dist/pie/markup-math.js +118 -0
  75. package/dist/pie/math-assets.d.ts +48 -0
  76. package/dist/pie/math-assets.js +81 -0
  77. package/dist/pie/math-rendering.d.ts +10 -21
  78. package/dist/pie/math-rendering.js +50 -35
  79. package/dist/pie/registry.d.ts +13 -1
  80. package/dist/pie/registry.js +39 -1
  81. package/dist/pie/resource-monitor.d.ts +6 -33
  82. package/dist/pie/resource-monitor.js +14 -213
  83. package/dist/pie/scoring.d.ts +12 -8
  84. package/dist/pie/scoring.js +21 -24
  85. package/dist/pie/session-commit.d.ts +16 -0
  86. package/dist/pie/session-commit.js +34 -6
  87. package/dist/pie/session-snapshot.d.ts +80 -0
  88. package/dist/pie/session-snapshot.js +166 -0
  89. package/dist/pie/stages.d.ts +10 -47
  90. package/dist/pie/stages.js +10 -47
  91. package/dist/pie/tag-names.d.ts +1 -0
  92. package/dist/pie/tag-names.js +1 -0
  93. package/dist/pie/types.d.ts +4 -5
  94. package/dist/pie/types.js +1 -0
  95. package/dist/pie/updates.d.ts +2 -2
  96. package/dist/pie/updates.js +33 -15
  97. package/dist/pie/versioned-tag.d.ts +4 -0
  98. package/dist/pie/versioned-tag.js +4 -0
  99. package/dist/security/index.d.ts +2 -3
  100. package/dist/security/index.js +2 -3
  101. package/dist/security/sanitize-item-markup.d.ts +4 -2
  102. package/dist/security/sanitize-item-markup.js +48 -6
  103. package/dist/security/sanitize-svg-icon.d.ts +0 -2
  104. package/dist/security/sanitize-svg-icon.js +0 -5
  105. package/dist/security/wrap-overwide-images.js +19 -0
  106. package/dist/security/wrap-overwide.d.ts +13 -0
  107. package/dist/security/wrap-overwide.js +63 -3
  108. package/dist/timed-media/policy.js +5 -16
  109. package/dist/types/index.d.ts +67 -423
  110. package/dist/types/index.js +0 -68
  111. package/dist/ui/attribute-coercion.d.ts +12 -0
  112. package/dist/ui/attribute-coercion.js +33 -0
  113. package/dist/ui/content-styles.d.ts +18 -16
  114. package/dist/ui/content-styles.js +67 -36
  115. package/dist/ui/overlay-containment.d.ts +31 -0
  116. package/dist/ui/overlay-containment.js +63 -0
  117. package/dist/ui/overlay-placement.d.ts +110 -0
  118. package/dist/ui/overlay-placement.js +222 -0
  119. package/dist/ui/pointer-gesture.d.ts +32 -0
  120. package/dist/ui/pointer-gesture.js +56 -0
  121. package/dist/ui/pointer-rotate.d.ts +29 -0
  122. package/dist/ui/pointer-rotate.js +39 -0
  123. package/package.json +23 -18
  124. package/dist/pie/component-context.d.ts +0 -21
  125. package/dist/pie/component-context.js +0 -29
  126. package/dist/pie/player-initializer.d.ts +0 -54
  127. package/dist/pie/player-initializer.js +0 -122
  128. package/dist/pie/stage-tracker.d.ts +0 -50
  129. package/dist/pie/stage-tracker.js +0 -105
  130. package/dist/security/wrap-model-rich-content.d.ts +0 -1
  131. package/dist/security/wrap-model-rich-content.js +0 -40
  132. package/dist/server/npm-auth-env.d.ts +0 -13
  133. package/dist/server/npm-auth-env.js +0 -64
  134. package/dist/server/npm-registry.d.ts +0 -7
  135. package/dist/server/npm-registry.js +0 -59
  136. package/dist/ui/debug-panel-persistence.d.ts +0 -17
  137. package/dist/ui/debug-panel-persistence.js +0 -37
@@ -12,12 +12,16 @@
12
12
  * uniformly. Here the adapter's job is narrowly to fetch, extract, and
13
13
  * call `customElements.define`.
14
14
  */
15
+ import { compare as compareSemver, major as semverMajor, minor as semverMinor, } from "semver";
15
16
  import { defineCustomElementSafely } from "../pie/custom-element-define.js";
16
17
  import { isInstrumentationProvider } from "../instrumentation/provider-guards.js";
17
- import { pieRegistry } from "../pie/registry.js";
18
+ import { writeRegistryEntry } from "../pie/registry.js";
18
19
  import { validateCustomElementTag } from "../pie/tag-names.js";
19
- import { isCustomElementConstructor, Status } from "../pie/types.js";
20
+ import { BundleType, isCustomElementConstructor, Status, } from "../pie/types.js";
21
+ import { parsePackageName } from "../pie/utils.js";
20
22
  import { AdapterFailure, } from "./element-loader-types.js";
23
+ import { isExactSemver } from "./element-package-policy.js";
24
+ import { forwardMathjaxEvents } from "./mathjax-events.js";
21
25
  export const BUILT_IN_VIEWS = {
22
26
  delivery: { subpath: "", tagSuffix: "" },
23
27
  author: { subpath: "/author", tagSuffix: "-config", fallback: "delivery" },
@@ -30,12 +34,21 @@ export function createEsmBackend(config) {
30
34
  const view = config.view ?? "delivery";
31
35
  const loadControllers = config.loadControllers ?? true;
32
36
  const viewConfig = resolveEsmViewConfig(view, config.viewConfig);
37
+ const browserView = browserViewName(viewConfig, view);
33
38
  const injectedPackageVersions = new Set();
34
39
  const importMappedPackageVersions = new Map();
40
+ /** Packages whose editor-runtime variant this backend loads. */
41
+ const editorRuntimeVariants = new Map();
42
+ /** Packages that do not publish the browser ESM exports this backend loads. */
43
+ const unpublishedPackages = new Map();
44
+ /** Windows whose MathJax events this backend forwards. */
45
+ const mathjaxEventViews = new WeakSet();
35
46
  let sharedDependencyVersions = {};
36
47
  let importer = defaultImporter;
37
48
  let packageMetadataLoader = defaultPackageMetadataLoader;
38
49
  let importMapObserver;
50
+ let importShimLoader = defaultImportShimLoader;
51
+ let importShimRequest;
39
52
  const __seams = {
40
53
  replaceImporter(fn) {
41
54
  importer = fn;
@@ -46,12 +59,20 @@ export function createEsmBackend(config) {
46
59
  replacePackageMetadataLoader(fn) {
47
60
  packageMetadataLoader = fn;
48
61
  },
62
+ replaceImportShimLoader(fn) {
63
+ importShimLoader = fn;
64
+ importShimRequest = undefined;
65
+ },
49
66
  restore() {
50
67
  importer = defaultImporter;
51
68
  packageMetadataLoader = defaultPackageMetadataLoader;
69
+ importShimLoader = defaultImportShimLoader;
70
+ importShimRequest = undefined;
52
71
  importMapObserver = undefined;
53
72
  injectedPackageVersions.clear();
54
73
  importMappedPackageVersions.clear();
74
+ editorRuntimeVariants.clear();
75
+ unpublishedPackages.clear();
55
76
  sharedDependencyVersions = {};
56
77
  },
57
78
  };
@@ -61,9 +82,10 @@ export function createEsmBackend(config) {
61
82
  if (moduleResolution === "import-map") {
62
83
  assertImportMapSupported();
63
84
  }
85
+ forwardMathjaxEventsOf(context.doc);
64
86
  const newEntries = {};
65
87
  for (const [tag, pkg] of Object.entries(elements)) {
66
- const packageName = extractPackageName(pkg);
88
+ const packageName = parsePackageName(pkg).name;
67
89
  if (moduleResolution === "import-map") {
68
90
  const existingVersion = importMappedPackageVersions.get(packageName);
69
91
  if (existingVersion && existingVersion !== pkg) {
@@ -77,30 +99,51 @@ export function createEsmBackend(config) {
77
99
  if (Object.keys(newEntries).length > 0) {
78
100
  let importMapResult;
79
101
  try {
80
- importMapResult = await buildImportMapJson(newEntries, viewConfig, cdnProvider, loadControllers, moduleResolution === "import-map", packageMetadataLoader, sharedDependencyVersions, reportSharedDependencyConflict);
102
+ importMapResult = await buildImportMap(newEntries, viewConfig, cdnProvider, loadControllers, moduleResolution === "import-map", packageMetadataLoader, sharedDependencyVersions, reportSharedDependencyConflict);
81
103
  }
82
104
  catch (err) {
83
105
  reportSharedDependencyError(err);
84
106
  throw err;
85
107
  }
86
- const json = importMapResult.json;
87
- if (json) {
108
+ for (const [pkg, cause] of importMapResult.unpublished) {
109
+ unpublishedPackages.set(pkg, cause);
110
+ if (typeof console !== "undefined" && console.error) {
111
+ console.error(`[pie-esm] ${cause}`);
112
+ }
113
+ }
114
+ const editorRuntime = moduleResolution === "url"
115
+ ? await prepareEditorRuntime(importMapResult.metadata, context.doc)
116
+ : undefined;
117
+ // Synchronous from here through the injection, so a loader that runs
118
+ // next on this page reads the runtime this one maps.
119
+ const runtimePlan = editorRuntime
120
+ ? planEditorRuntime(editorRuntime, context.doc)
121
+ : undefined;
122
+ const imports = withoutMappedSpecifiers({ ...importMapResult.imports, ...runtimePlan?.imports }, context.doc);
123
+ let injectedMap;
124
+ if (Object.keys(imports).length > 0) {
125
+ const json = JSON.stringify({ imports }, null, 2);
88
126
  assertImportMapSupported();
89
- injectImportMap(json, context.doc);
127
+ injectedMap = injectImportMap(json, context.doc, runtimePlan?.mappedRuntime);
90
128
  importMapObserver?.(json, context.doc);
91
129
  }
130
+ for (const [pkg, variant] of runtimePlan?.served ?? []) {
131
+ editorRuntimeVariants.set(pkg, variant);
132
+ }
92
133
  sharedDependencyVersions = importMapResult.sharedDependencyVersions;
93
134
  for (const pkg of Object.values(newEntries)) {
94
135
  injectedPackageVersions.add(pkg);
95
136
  if (moduleResolution === "import-map") {
96
- importMappedPackageVersions.set(extractPackageName(pkg), pkg);
137
+ importMappedPackageVersions.set(parsePackageName(pkg).name, pkg);
97
138
  }
98
139
  }
140
+ if (injectedMap) {
141
+ await applyImportMap(imports, injectedMap, context.doc);
142
+ }
99
143
  }
100
144
  const reasons = new Map();
101
- const registry = pieRegistry();
102
145
  await Promise.all(Object.entries(elements).map(async ([tag, packageVersion]) => {
103
- const packageName = extractPackageName(packageVersion);
146
+ const packageName = parsePackageName(packageVersion).name;
104
147
  let actualTag;
105
148
  try {
106
149
  actualTag = validateCustomElementTag(tag, `element tag for ${packageName}`);
@@ -114,48 +157,65 @@ export function createEsmBackend(config) {
114
157
  return;
115
158
  }
116
159
  const specifier = resolveElementSpecifier(packageName, packageVersion, viewConfig, moduleResolution, cdnProvider, view);
117
- let module;
118
- try {
119
- module = await importer(specifier);
160
+ const unpublished = unpublishedPackages.get(packageVersion);
161
+ if (unpublished) {
162
+ reasons.set(tag, {
163
+ kind: "module-load-failed",
164
+ tag,
165
+ specifier,
166
+ cause: unpublished,
167
+ });
168
+ return;
120
169
  }
121
- catch (err) {
122
- if (viewConfig.fallback) {
123
- const fallbackConfig = BUILT_IN_VIEWS[viewConfig.fallback] ?? BUILT_IN_VIEWS.delivery;
124
- const fallbackSpecifier = resolveElementSpecifier(packageName, packageVersion, fallbackConfig, moduleResolution, cdnProvider, viewConfig.fallback);
125
- try {
126
- module = await importer(fallbackSpecifier);
170
+ const variant = editorRuntimeVariants.get(packageVersion);
171
+ let ElementClass;
172
+ if (variant) {
173
+ ElementClass = await importVariantElementClass(packageVersion, variant, context.doc);
174
+ }
175
+ if (!ElementClass) {
176
+ let module;
177
+ try {
178
+ module = await importModule(specifier, context.doc);
179
+ }
180
+ catch (err) {
181
+ if (viewConfig.fallback) {
182
+ const fallbackConfig = BUILT_IN_VIEWS[viewConfig.fallback] ?? BUILT_IN_VIEWS.delivery;
183
+ const fallbackSpecifier = resolveElementSpecifier(packageName, packageVersion, fallbackConfig, moduleResolution, cdnProvider, viewConfig.fallback);
184
+ try {
185
+ module = await importModule(fallbackSpecifier, context.doc);
186
+ }
187
+ catch (fallbackErr) {
188
+ reasons.set(tag, {
189
+ kind: "module-load-failed",
190
+ tag,
191
+ specifier: fallbackSpecifier,
192
+ cause: fallbackErr instanceof Error
193
+ ? fallbackErr.message
194
+ : String(fallbackErr),
195
+ });
196
+ return;
197
+ }
127
198
  }
128
- catch (fallbackErr) {
199
+ else {
129
200
  reasons.set(tag, {
130
201
  kind: "module-load-failed",
131
202
  tag,
132
- specifier: fallbackSpecifier,
133
- cause: fallbackErr instanceof Error
134
- ? fallbackErr.message
135
- : String(fallbackErr),
203
+ specifier,
204
+ cause: err instanceof Error ? err.message : String(err),
136
205
  });
137
206
  return;
138
207
  }
139
208
  }
140
- else {
209
+ ElementClass = pickElementClass(module, view);
210
+ if (!ElementClass) {
141
211
  reasons.set(tag, {
142
- kind: "module-load-failed",
212
+ kind: "no-element-class",
143
213
  tag,
144
- specifier,
145
- cause: err instanceof Error ? err.message : String(err),
214
+ packageName,
146
215
  });
147
216
  return;
148
217
  }
149
218
  }
150
- const ElementClass = pickElementClass(module, view);
151
- if (!ElementClass) {
152
- reasons.set(tag, {
153
- kind: "no-element-class",
154
- tag,
155
- packageName,
156
- });
157
- return;
158
- }
159
219
  if (!isCustomElementConstructor(ElementClass)) {
160
220
  reasons.set(tag, {
161
221
  kind: "not-a-constructor",
@@ -178,30 +238,239 @@ export function createEsmBackend(config) {
178
238
  }
179
239
  let controller = null;
180
240
  if (loadControllers) {
181
- const controllerSpecifier = resolveControllerSpecifier(packageName, packageVersion, moduleResolution, cdnProvider);
182
- try {
183
- const controllerModule = await importer(controllerSpecifier);
184
- controller = controllerModule?.default ?? controllerModule;
241
+ const servedVariant = editorRuntimeVariants.get(packageVersion);
242
+ const variantController = servedVariant?.views.controller;
243
+ if (servedVariant && variantController) {
244
+ try {
245
+ const controllerModule = await importModule(cdnProvider.browserViewUrl(packageVersion, variantController), context.doc);
246
+ controller = controllerModule?.default ?? controllerModule;
247
+ }
248
+ catch (err) {
249
+ reportEditorRuntimeFallback(packageVersion, servedVariant.declaration, servedVariant.runtime, `its editor-runtime controller failed to load: ${errorMessage(err)}`);
250
+ }
185
251
  }
186
- catch {
187
- // Controllers are best-effort; element registration is what the
188
- // primitive verifies.
252
+ if (!controller) {
253
+ const controllerSpecifier = resolveControllerSpecifier(packageName, packageVersion, moduleResolution, cdnProvider);
254
+ try {
255
+ const controllerModule = await importModule(controllerSpecifier, context.doc);
256
+ controller = controllerModule?.default ?? controllerModule;
257
+ }
258
+ catch {
259
+ // Controllers are best-effort; element registration is what the
260
+ // primitive verifies.
261
+ }
189
262
  }
190
263
  }
191
- registry[actualTag] = {
264
+ writeRegistryEntry({
192
265
  package: packageVersion,
193
266
  status: Status.loaded,
194
267
  tagName: actualTag,
195
268
  element: ElementClass,
196
269
  controller,
197
270
  config: null,
198
- bundleType: "esm",
199
- };
271
+ bundleType: BundleType.esm,
272
+ });
200
273
  }));
201
274
  if (reasons.size > 0) {
202
275
  throw new AdapterFailure(reasons);
203
276
  }
204
277
  }
278
+ /**
279
+ * The element class of a package's editor-runtime variant, or `undefined`
280
+ * after reporting why it cannot load, so the caller loads `./browser/*`.
281
+ */
282
+ async function importVariantElementClass(packageVersion, variant, doc) {
283
+ const specifier = cdnProvider.browserViewUrl(packageVersion, variant.views[browserView]);
284
+ try {
285
+ const ElementClass = pickElementClass(await importModule(specifier, doc), view);
286
+ if (isCustomElementConstructor(ElementClass))
287
+ return ElementClass;
288
+ throw new Error(`${specifier} exports no custom element class`);
289
+ }
290
+ catch (err) {
291
+ editorRuntimeVariants.delete(packageVersion);
292
+ reportEditorRuntimeFallback(packageVersion, variant.declaration, variant.runtime, `its editor-runtime ${browserView} view failed to load: ${errorMessage(err)}`);
293
+ return undefined;
294
+ }
295
+ }
296
+ /**
297
+ * Imports through the browser, or through es-module-shims once the document
298
+ * has rejected an import map, after every map a backend added has settled.
299
+ */
300
+ async function importModule(specifier, doc) {
301
+ await importMapsSettled(doc);
302
+ if (!documentUsesImportShim(doc))
303
+ return importer(specifier);
304
+ return (await importShim(doc))(specifier);
305
+ }
306
+ /**
307
+ * Settles the map this backend just added: es-module-shims takes it in a
308
+ * document that already uses it, and otherwise the browser has either
309
+ * applied it or rejected it.
310
+ */
311
+ async function applyImportMap(imports, script, doc) {
312
+ let state = "applied";
313
+ try {
314
+ if (script.type === SHIM_IMPORT_MAP_TYPE) {
315
+ (await importShim(doc)).addImportMap({ imports });
316
+ }
317
+ else if (await importMapRejected(imports)) {
318
+ state = "rejected";
319
+ await importShim(doc);
320
+ }
321
+ }
322
+ finally {
323
+ settleImportMap(script, state);
324
+ }
325
+ }
326
+ /**
327
+ * Whether the browser rejected a map this backend added. Firefox rejects
328
+ * every import map added after the page's first module load, so a specifier
329
+ * the map defines fails to resolve while the module it maps to loads. When
330
+ * that module fails too, the map is not the cause, and the element imports
331
+ * report the failure.
332
+ */
333
+ async function importMapRejected(imports) {
334
+ const specifier = "react" in imports ? "react" : Object.keys(imports)[0];
335
+ try {
336
+ await importer(specifier);
337
+ return false;
338
+ }
339
+ catch {
340
+ // Resolved below: the map, or the module it maps to.
341
+ }
342
+ try {
343
+ await importer(imports[specifier]);
344
+ }
345
+ catch {
346
+ return false;
347
+ }
348
+ return true;
349
+ }
350
+ /**
351
+ * es-module-shims, holding every map in `doc`: the rejected ones, and any
352
+ * the browser applied before, so both resolve the same way.
353
+ */
354
+ function importShim(doc) {
355
+ importShimRequest ??= importShimLoader().then((shim) => {
356
+ shim.addImportMap({ imports: documentImports(doc) });
357
+ return shim;
358
+ }, (err) => {
359
+ importShimRequest = undefined;
360
+ throw err;
361
+ });
362
+ return importShimRequest;
363
+ }
364
+ /**
365
+ * Collect the packages whose variant covers this backend's view and, when
366
+ * the page maps no editor runtime yet, fetch the metadata of the one to map.
367
+ */
368
+ async function prepareEditorRuntime(metadataByPackage, doc) {
369
+ const candidates = [];
370
+ for (const [packageVersion, metadata] of metadataByPackage) {
371
+ const declaration = readEditorRuntimeDeclaration(metadata);
372
+ if (declaration === undefined)
373
+ continue;
374
+ if (declaration === null) {
375
+ reportEditorRuntimeFallback(packageVersion, undefined, undefined, "its pie.browserEditorRuntime declaration is invalid");
376
+ continue;
377
+ }
378
+ if (declaration.views[browserView]) {
379
+ candidates.push({ packageVersion, declaration });
380
+ }
381
+ }
382
+ if (candidates.length === 0)
383
+ return { candidates };
384
+ if (!importMapsSupported()) {
385
+ for (const { packageVersion, declaration } of candidates) {
386
+ reportEditorRuntimeFallback(packageVersion, declaration, undefined, "this browser does not support import maps");
387
+ }
388
+ return { candidates: [] };
389
+ }
390
+ if (readMappedEditorRuntime(doc))
391
+ return { candidates };
392
+ const runtime = highestDeclaredEditorRuntime(candidates);
393
+ return {
394
+ candidates,
395
+ target: { runtime, ...(await loadEditorRuntimeModules(runtime)) },
396
+ };
397
+ }
398
+ async function loadEditorRuntimeModules(runtime) {
399
+ const runtimeVersion = `${runtime.name}@${runtime.version}`;
400
+ let metadata;
401
+ try {
402
+ metadata = await packageMetadataLoader(runtimeVersion, cdnProvider.packageJsonUrl(runtimeVersion));
403
+ }
404
+ catch (err) {
405
+ return {
406
+ failure: `the package.json of ${runtimeVersion} could not be loaded: ${errorMessage(err)}`,
407
+ };
408
+ }
409
+ if (!metadata) {
410
+ return {
411
+ failure: `the package.json of ${runtimeVersion} could not be loaded`,
412
+ };
413
+ }
414
+ const modules = readBrowserModules(metadata);
415
+ return modules
416
+ ? { modules }
417
+ : { failure: `${runtimeVersion} declares no valid pie.browserModules` };
418
+ }
419
+ /**
420
+ * Decide which packages the page's editor runtime serves, and the entries
421
+ * that map it when the page maps none yet. Runs synchronously up to the
422
+ * import-map injection, so it reads the runtime any other loader mapped
423
+ * while this one awaited metadata.
424
+ */
425
+ function planEditorRuntime(prepared, doc) {
426
+ const plan = { imports: {}, served: new Map() };
427
+ if (prepared.candidates.length === 0)
428
+ return plan;
429
+ let runtime = readMappedEditorRuntime(doc);
430
+ if (!runtime) {
431
+ const { target } = prepared;
432
+ let failure = target?.failure;
433
+ if (target?.modules) {
434
+ const taken = documentImports(doc);
435
+ const mapped = Object.keys(target.modules).find((specifier) => specifier in taken);
436
+ if (mapped)
437
+ failure = `the page already maps ${mapped}`;
438
+ }
439
+ if (failure || !target?.modules) {
440
+ for (const { packageVersion, declaration } of prepared.candidates) {
441
+ reportEditorRuntimeFallback(packageVersion, declaration, undefined, failure ?? "the page no longer maps its editor runtime");
442
+ }
443
+ return plan;
444
+ }
445
+ runtime = target.runtime;
446
+ const runtimeVersion = `${runtime.name}@${runtime.version}`;
447
+ for (const [specifier, runtimeView] of Object.entries(target.modules)) {
448
+ plan.imports[specifier] = cdnProvider.browserViewUrl(runtimeVersion, runtimeView);
449
+ }
450
+ plan.mappedRuntime = runtimeVersion;
451
+ }
452
+ for (const { packageVersion, declaration } of prepared.candidates) {
453
+ if (!canServeEditorRuntime(runtime, declaration)) {
454
+ reportEditorRuntimeFallback(packageVersion, declaration, runtime, `the page maps ${runtime.name}@${runtime.version}, which cannot serve ${declaration.name}@${declaration.version}`);
455
+ continue;
456
+ }
457
+ plan.served.set(packageVersion, {
458
+ views: declaration.views,
459
+ declaration,
460
+ runtime,
461
+ });
462
+ if (declaration.version !== runtime.version) {
463
+ reportSharedDependencyConflict({
464
+ dependencyName: runtime.name,
465
+ existingVersion: runtime.version,
466
+ requestedVersion: declaration.version,
467
+ resolvedVersion: runtime.version,
468
+ packageVersion,
469
+ });
470
+ }
471
+ }
472
+ return plan;
473
+ }
205
474
  function getInstrumentationProvider() {
206
475
  if (!config.trackPageActions)
207
476
  return undefined;
@@ -212,9 +481,16 @@ export function createEsmBackend(config) {
212
481
  return undefined;
213
482
  return provider;
214
483
  }
215
- function reportSharedDependencyConflict(attributes) {
484
+ function forwardMathjaxEventsOf(doc) {
485
+ const view = doc.defaultView;
486
+ if (!view || mathjaxEventViews.has(view))
487
+ return;
488
+ mathjaxEventViews.add(view);
489
+ forwardMathjaxEvents(view, getInstrumentationProvider);
490
+ }
491
+ function reportSharedDependencyConflict(attributes, message = `[pie-esm] Shared dependency version conflict resolved for ${String(attributes.dependencyName)}`) {
216
492
  if (typeof console !== "undefined" && console.warn) {
217
- console.warn(`[pie-esm] Shared dependency version conflict resolved for ${String(attributes.dependencyName)}`, attributes);
493
+ console.warn(message, attributes);
218
494
  }
219
495
  const provider = getInstrumentationProvider();
220
496
  if (!provider)
@@ -226,6 +502,17 @@ export function createEsmBackend(config) {
226
502
  // Swallow: instrumentation must never break loading.
227
503
  }
228
504
  }
505
+ /** Report a package that loads `./browser/*` although it declares a variant. */
506
+ function reportEditorRuntimeFallback(packageVersion, declaration, runtime, reason) {
507
+ reportSharedDependencyConflict({
508
+ dependencyName: declaration?.name,
509
+ existingVersion: runtime?.version,
510
+ requestedVersion: declaration?.version,
511
+ packageVersion,
512
+ fallback: "./browser/*",
513
+ reason,
514
+ }, `[pie-esm] ${packageVersion} loads ./browser/* in place of its editor-runtime variant: ${reason}`);
515
+ }
229
516
  function reportSharedDependencyError(error) {
230
517
  const err = error instanceof Error ? error : new Error(String(error));
231
518
  if (typeof console !== "undefined" && console.error) {
@@ -261,9 +548,37 @@ export function mapEsmViewElements(elements, view = "delivery", viewConfig) {
261
548
  }
262
549
  function defaultImporter(specifier) {
263
550
  // @vite-ignore — dynamic import resolved at runtime.
264
- return import(/* @vite-ignore */ specifier);
551
+ return import(/* webpackIgnore: true */ /* @vite-ignore */ specifier);
552
+ }
553
+ /**
554
+ * One request per package.json URL, shared by every backend on the page: the
555
+ * players on a page read the metadata of the same element versions and of the
556
+ * same editor runtime. A request that fails or finds no metadata is dropped
557
+ * once it settles, so a later load asks again.
558
+ */
559
+ const packageMetadataRequests = new Map();
560
+ /** @internal Test-only: drops every shared package.json request. */
561
+ export function clearPackageMetadataRequests() {
562
+ packageMetadataRequests.clear();
563
+ }
564
+ function defaultPackageMetadataLoader(packageVersion, packageJsonUrl) {
565
+ const pending = packageMetadataRequests.get(packageJsonUrl);
566
+ if (pending)
567
+ return pending;
568
+ const request = fetchPackageMetadata(packageJsonUrl);
569
+ packageMetadataRequests.set(packageJsonUrl, request);
570
+ const forget = () => {
571
+ if (packageMetadataRequests.get(packageJsonUrl) === request) {
572
+ packageMetadataRequests.delete(packageJsonUrl);
573
+ }
574
+ };
575
+ request.then((metadata) => {
576
+ if (metadata === null)
577
+ forget();
578
+ }, forget);
579
+ return request;
265
580
  }
266
- async function defaultPackageMetadataLoader(packageVersion, packageJsonUrl) {
581
+ async function fetchPackageMetadata(packageJsonUrl) {
267
582
  const browserFetch = typeof window !== "undefined" &&
268
583
  typeof window.fetch === "function"
269
584
  ? window.fetch.bind(window)
@@ -277,6 +592,40 @@ async function defaultPackageMetadataLoader(packageVersion, packageJsonUrl) {
277
592
  }
278
593
  return (await response.json());
279
594
  }
595
+ const RUNTIME_SUPPORT_PATH = "dist/runtime-support.js";
596
+ /**
597
+ * URL of a package's `./runtime-support` module on the ESM CDN the players load
598
+ * elements from, or `undefined` when a custom provider names no package root.
599
+ */
600
+ export function resolveEsmRuntimeSupportUrl(packageVersion, options) {
601
+ const cdnBaseUrl = options.cdnBaseUrl.replace(/\/+$/, "");
602
+ const provider = resolveCdnProvider(options.cdnProvider, cdnBaseUrl);
603
+ if (typeof provider.runtimeSupportUrl === "function") {
604
+ return provider.runtimeSupportUrl(packageVersion);
605
+ }
606
+ const packageJsonUrl = provider.packageJsonUrl(packageVersion);
607
+ return packageJsonUrl.endsWith("/package.json")
608
+ ? `${packageJsonUrl.slice(0, -"package.json".length)}${RUNTIME_SUPPORT_PATH}`
609
+ : undefined;
610
+ }
611
+ /** A package the probe below names, so a provider URL ends in its `<package>@<version>` path. */
612
+ const ROOT_PROBE_PACKAGE = "@pie-element/shared-math-rendering-mathjax@0.0.0";
613
+ /**
614
+ * The npm root the ESM CDN the players load elements from serves raw package
615
+ * files under, `<root>/<package>@<version>/<path>`: the base URL for jsDelivr,
616
+ * raw.esm.sh for esm.sh, and for a custom provider whatever its
617
+ * `packageJsonUrl` puts before the package. `undefined` when that URL names
618
+ * no such root.
619
+ */
620
+ export function resolveEsmAssetRoot(options) {
621
+ const cdnBaseUrl = options.cdnBaseUrl.replace(/\/+$/, "");
622
+ const provider = resolveCdnProvider(options.cdnProvider, cdnBaseUrl);
623
+ const suffix = `/${ROOT_PROBE_PACKAGE}/package.json`;
624
+ const packageJsonUrl = provider.packageJsonUrl(ROOT_PROBE_PACKAGE);
625
+ return packageJsonUrl.endsWith(suffix)
626
+ ? packageJsonUrl.slice(0, -suffix.length) || undefined
627
+ : undefined;
628
+ }
280
629
  function resolveCdnProvider(provider, cdnBaseUrl) {
281
630
  if (isEsmCdnProvider(provider))
282
631
  return provider;
@@ -302,6 +651,7 @@ function createJsDelivrProvider(cdnBaseUrl, name = "jsdelivr") {
302
651
  packageJsonUrl: (packageVersion) => `${cdnBaseUrl}/${packageVersion}/package.json`,
303
652
  browserViewUrl: (packageVersion, view) => `${cdnBaseUrl}/${packageVersion}/dist/browser/${view}/index.js`,
304
653
  browserControllerUrl: (packageVersion) => `${cdnBaseUrl}/${packageVersion}/dist/browser/controller/index.js`,
654
+ runtimeSupportUrl: (packageVersion) => `${cdnBaseUrl}/${packageVersion}/${RUNTIME_SUPPORT_PATH}`,
305
655
  sharedDependencyUrl: (dependencyName, version, subpath) => {
306
656
  const suffix = subpath ? `/${subpath}/+esm` : "/+esm";
307
657
  return `${cdnBaseUrl}/${dependencyName}@${version}${suffix}`;
@@ -316,6 +666,7 @@ function createEsmShProvider(cdnBaseUrl) {
316
666
  packageJsonUrl: (packageVersion) => `${rawBaseUrl}/${packageVersion}/package.json`,
317
667
  browserViewUrl: (packageVersion, view) => `${rawBaseUrl}/${packageVersion}/dist/browser/${view}/index.js`,
318
668
  browserControllerUrl: (packageVersion) => `${rawBaseUrl}/${packageVersion}/dist/browser/controller/index.js`,
669
+ runtimeSupportUrl: (packageVersion) => `${rawBaseUrl}/${packageVersion}/${RUNTIME_SUPPORT_PATH}`,
319
670
  sharedDependencyUrl: (dependencyName, version, subpath) => {
320
671
  const suffix = subpath ? `/${subpath}` : "";
321
672
  return `${esmBaseUrl}/${dependencyName}@${version}${suffix}`;
@@ -340,9 +691,13 @@ function toRawEsmShBaseUrl(cdnBaseUrl) {
340
691
  function resolveEsmViewConfig(view, viewConfig) {
341
692
  return viewConfig ?? BUILT_IN_VIEWS[view] ?? BUILT_IN_VIEWS.delivery;
342
693
  }
343
- function assertBrowserEsmExports(metadata, packageVersion, viewConfig, loadControllers, viewName) {
694
+ /**
695
+ * Why a package cannot load this view as browser ESM, or `undefined` when its
696
+ * metadata publishes every export the view needs or names no exports.
697
+ */
698
+ function missingBrowserEsmExport(metadata, packageVersion, viewConfig, loadControllers, viewName) {
344
699
  if (!metadata?.exports)
345
- return;
700
+ return undefined;
346
701
  const requiredExports = new Set([
347
702
  `./browser/${browserViewName(viewConfig, viewName)}`,
348
703
  ]);
@@ -357,13 +712,10 @@ function assertBrowserEsmExports(metadata, packageVersion, viewConfig, loadContr
357
712
  }
358
713
  for (const exportKey of requiredExports) {
359
714
  if (!(exportKey in metadata.exports)) {
360
- throw new Error(`${packageVersion} does not publish browser ESM export ${exportKey}; use IIFE/preloaded mode or publish browser ESM artifacts first`);
715
+ return `${packageVersion} does not publish browser ESM export ${exportKey}; use IIFE/preloaded mode or publish browser ESM artifacts first`;
361
716
  }
362
717
  }
363
- }
364
- function extractPackageName(packageVersion) {
365
- const parts = packageVersion.split("@");
366
- return parts.length >= 3 ? `@${parts[1]}` : parts[0];
718
+ return undefined;
367
719
  }
368
720
  function cleanViewSubpath(subpath) {
369
721
  const cleanSubpath = subpath.replace(/^\/+|\/+$/g, "");
@@ -394,7 +746,7 @@ function resolveControllerSpecifier(packageName, packageVersion, moduleResolutio
394
746
  }
395
747
  return resolveBrowserControllerUrl(packageVersion, cdnProvider);
396
748
  }
397
- function pickElementClass(module, view) {
749
+ export function pickElementClass(module, view) {
398
750
  if (!module || typeof module !== "object")
399
751
  return undefined;
400
752
  if (view === "author") {
@@ -411,7 +763,7 @@ function declaredSharedDependencyVersion(metadata, dependencyName, packageVersio
411
763
  if (!version) {
412
764
  throw new Error(`${packageVersion} is missing required pie.browserSharedDependencies.${dependencyName}`);
413
765
  }
414
- if (!isExactVersion(version)) {
766
+ if (!isExactSemver(version)) {
415
767
  throw new Error(`${packageVersion} pie.browserSharedDependencies.${dependencyName} must be an exact version; received "${version}"`);
416
768
  }
417
769
  return version;
@@ -448,50 +800,35 @@ function addSharedDependencyImports(imports, selectedVersions, lockedVersions, d
448
800
  imports["react-dom/client"] = cdnProvider.sharedDependencyUrl("react-dom", resolvedVersion, "client");
449
801
  }
450
802
  }
451
- function isExactVersion(version) {
452
- return /^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/.test(version);
453
- }
454
- function parseVersion(version) {
455
- const match = version.match(/^(\d+)\.(\d+)\.(\d+)/);
456
- if (!match) {
457
- throw new Error(`Invalid shared browser dependency version "${version}"`);
458
- }
459
- return {
460
- major: Number(match[1]),
461
- minor: Number(match[2]),
462
- patch: Number(match[3]),
463
- };
464
- }
465
- function compareVersions(a, b) {
466
- const parsedA = parseVersion(a);
467
- const parsedB = parseVersion(b);
468
- if (parsedA.major !== parsedB.major)
469
- return parsedA.major - parsedB.major;
470
- if (parsedA.minor !== parsedB.minor)
471
- return parsedA.minor - parsedB.minor;
472
- return parsedA.patch - parsedB.patch;
473
- }
803
+ /** Versions are exact semver, validated before they are compared. */
474
804
  function resolveSharedDependencyVersion(dependencyName, currentVersion, requestedVersion, packageVersion, isLocked) {
475
- const current = parseVersion(currentVersion);
476
- const requested = parseVersion(requestedVersion);
477
- if (current.major !== requested.major) {
805
+ if (semverMajor(currentVersion) !== semverMajor(requestedVersion)) {
478
806
  throw new Error(`Conflicting shared browser dependency ${dependencyName}: ${currentVersion} vs ${requestedVersion} from ${packageVersion}; different major versions cannot share one browser singleton`);
479
807
  }
480
- if (isLocked && compareVersions(requestedVersion, currentVersion) > 0) {
808
+ if (isLocked && compareSemver(requestedVersion, currentVersion) > 0) {
481
809
  throw new Error(`Conflicting shared browser dependency ${dependencyName}: ${currentVersion} is already selected, but ${packageVersion} requires higher version ${requestedVersion}; the browser singleton cannot be upgraded after import-map injection`);
482
810
  }
483
- return compareVersions(currentVersion, requestedVersion) >= 0
811
+ return compareSemver(currentVersion, requestedVersion) >= 0
484
812
  ? currentVersion
485
813
  : requestedVersion;
486
814
  }
487
- async function buildImportMapJson(elements, viewConfig, cdnProvider, loadControllers, includeElementImports, packageMetadataLoader, currentSharedDependencyVersions, onConflict) {
815
+ async function buildImportMap(elements, viewConfig, cdnProvider, loadControllers, includeElementImports, packageMetadataLoader, currentSharedDependencyVersions, onConflict) {
488
816
  const imports = {};
489
817
  const selectedVersions = {
490
818
  ...currentSharedDependencyVersions,
491
819
  };
492
820
  const lockedVersions = new Set(Object.keys(currentSharedDependencyVersions));
821
+ const metadataByPackage = new Map();
822
+ const unpublished = new Map();
493
823
  for (const [, pkg] of Object.entries(elements)) {
494
- const packageName = extractPackageName(pkg);
824
+ const metadata = await packageMetadataLoader(pkg, cdnProvider.packageJsonUrl(pkg));
825
+ const missingExport = missingBrowserEsmExport(metadata, pkg, viewConfig, loadControllers, cleanViewSubpath(viewConfig.subpath) ?? "delivery");
826
+ if (missingExport) {
827
+ unpublished.set(pkg, missingExport);
828
+ continue;
829
+ }
830
+ metadataByPackage.set(pkg, metadata);
831
+ const packageName = parsePackageName(pkg).name;
495
832
  if (includeElementImports) {
496
833
  imports[packageName] = resolveBrowserViewUrl(pkg, BUILT_IN_VIEWS.delivery, cdnProvider, "delivery");
497
834
  if (viewConfig.subpath) {
@@ -510,8 +847,6 @@ async function buildImportMapJson(elements, viewConfig, cdnProvider, loadControl
510
847
  }
511
848
  }
512
849
  }
513
- const metadata = await packageMetadataLoader(pkg, cdnProvider.packageJsonUrl(pkg));
514
- assertBrowserEsmExports(metadata, pkg, viewConfig, loadControllers, cleanViewSubpath(viewConfig.subpath) ?? "delivery");
515
850
  for (const dependencyName of SHARED_BROWSER_DEPENDENCIES) {
516
851
  if (!packageUsesSharedDependency(metadata, dependencyName)) {
517
852
  continue;
@@ -519,25 +854,223 @@ async function buildImportMapJson(elements, viewConfig, cdnProvider, loadControl
519
854
  addSharedDependencyImports(imports, selectedVersions, lockedVersions, dependencyName, declaredSharedDependencyVersion(metadata, dependencyName, pkg), cdnProvider, pkg, onConflict);
520
855
  }
521
856
  }
522
- if (Object.keys(imports).length === 0) {
523
- return { json: null, sharedDependencyVersions: selectedVersions };
524
- }
525
857
  return {
526
- json: JSON.stringify({ imports }, null, 2),
858
+ imports,
527
859
  sharedDependencyVersions: selectedVersions,
860
+ metadata: metadataByPackage,
861
+ unpublished,
528
862
  };
529
863
  }
530
- function injectImportMap(json, doc) {
864
+ // ─── Shared editor runtime ───────────────────────────────────────────────────
865
+ /**
866
+ * Names the editor runtime an injected import map maps, as `<name>@<version>`.
867
+ * The document holds it because every loader on the page, whichever backend
868
+ * or players-shared copy runs it, shares the one import map.
869
+ */
870
+ const EDITOR_RUNTIME_ATTRIBUTE = "data-pie-editor-runtime";
871
+ const PACKAGE_NAME = /^(?:@[a-z0-9][a-z0-9._~-]*\/)?[a-z0-9][a-z0-9._~-]*$/;
872
+ /** Slash-separated segments, none starting with a dot. */
873
+ const BROWSER_VIEW_PATH = /^[A-Za-z0-9_-][A-Za-z0-9._-]*(?:\/[A-Za-z0-9_-][A-Za-z0-9._-]*)*$/;
874
+ const BARE_SPECIFIER = /^(?:@[A-Za-z0-9_-][A-Za-z0-9._-]*\/)?[A-Za-z0-9_-][A-Za-z0-9._-]*(?:\/[A-Za-z0-9_-][A-Za-z0-9._-]*)*$/;
875
+ function isRecord(value) {
876
+ return typeof value === "object" && value !== null && !Array.isArray(value);
877
+ }
878
+ /**
879
+ * A package's `pie.browserEditorRuntime`: `undefined` when it declares none,
880
+ * `null` when the declaration is malformed.
881
+ */
882
+ function readEditorRuntimeDeclaration(metadata) {
883
+ const declared = metadata?.pie?.browserEditorRuntime;
884
+ if (declared === undefined)
885
+ return undefined;
886
+ if (!isRecord(declared) || !isRecord(declared.views))
887
+ return null;
888
+ const { name, version } = declared;
889
+ if (typeof name !== "string" || !PACKAGE_NAME.test(name))
890
+ return null;
891
+ if (typeof version !== "string" || !isExactSemver(version))
892
+ return null;
893
+ const views = Object.entries(declared.views);
894
+ if (views.length === 0 ||
895
+ !views.every(([, path]) => typeof path === "string" && BROWSER_VIEW_PATH.test(path))) {
896
+ return null;
897
+ }
898
+ return {
899
+ name,
900
+ version,
901
+ views: Object.fromEntries(views),
902
+ };
903
+ }
904
+ /**
905
+ * The editor runtime's `pie.browserModules`, or `null` when it is missing or
906
+ * malformed. A runtime has no React import, so it maps no React specifier.
907
+ */
908
+ function readBrowserModules(metadata) {
909
+ const declared = metadata.pie?.browserModules;
910
+ if (!isRecord(declared))
911
+ return null;
912
+ const modules = Object.entries(declared);
913
+ const valid = modules.length > 0 &&
914
+ modules.every(([specifier, view]) => BARE_SPECIFIER.test(specifier) &&
915
+ !SHARED_BROWSER_DEPENDENCIES.some((dependency) => specifier === dependency || specifier.startsWith(`${dependency}/`)) &&
916
+ typeof view === "string" &&
917
+ BROWSER_VIEW_PATH.test(view));
918
+ return valid ? Object.fromEntries(modules) : null;
919
+ }
920
+ /** The highest runtime version the candidates declare, by semver precedence. */
921
+ function highestDeclaredEditorRuntime(candidates) {
922
+ const { name } = candidates[0].declaration;
923
+ let { version } = candidates[0].declaration;
924
+ for (const { declaration } of candidates) {
925
+ if (declaration.name === name &&
926
+ compareSemver(declaration.version, version) > 0) {
927
+ version = declaration.version;
928
+ }
929
+ }
930
+ return { name, version };
931
+ }
932
+ /**
933
+ * A runtime serves a variant built against a version in its caret range (the
934
+ * same major, and below 1.0.0 the same minor) at or below its own.
935
+ */
936
+ function canServeEditorRuntime(runtime, declaration) {
937
+ if (runtime.name !== declaration.name)
938
+ return false;
939
+ if (semverMajor(runtime.version) !== semverMajor(declaration.version)) {
940
+ return false;
941
+ }
942
+ if (semverMajor(runtime.version) === 0 &&
943
+ semverMinor(runtime.version) !== semverMinor(declaration.version)) {
944
+ return false;
945
+ }
946
+ return compareSemver(runtime.version, declaration.version) >= 0;
947
+ }
948
+ function importMapScripts(doc) {
949
+ return typeof doc.querySelectorAll === "function"
950
+ ? Array.from(doc.querySelectorAll(`script[type="importmap"], script[type="${SHIM_IMPORT_MAP_TYPE}"]`))
951
+ : [];
952
+ }
953
+ /** The editor runtime an import map in the document maps, first map first. */
954
+ function readMappedEditorRuntime(doc) {
955
+ for (const script of importMapScripts(doc)) {
956
+ const mapped = script.getAttribute(EDITOR_RUNTIME_ATTRIBUTE);
957
+ const at = mapped?.lastIndexOf("@") ?? -1;
958
+ if (!mapped || at <= 0)
959
+ continue;
960
+ const name = mapped.slice(0, at);
961
+ const version = mapped.slice(at + 1);
962
+ if (PACKAGE_NAME.test(name) && isExactSemver(version)) {
963
+ return { name, version };
964
+ }
965
+ }
966
+ return null;
967
+ }
968
+ function errorMessage(err) {
969
+ return err instanceof Error ? err.message : String(err);
970
+ }
971
+ /**
972
+ * Drop the specifiers an import map already in the document maps. The browser
973
+ * keeps the first rule for a specifier and discards a later one with a console
974
+ * warning, so injecting them changes no resolution.
975
+ */
976
+ function withoutMappedSpecifiers(imports, doc) {
977
+ const mapped = documentImports(doc);
978
+ return Object.fromEntries(Object.entries(imports).filter(([specifier]) => !(specifier in mapped)));
979
+ }
980
+ /**
981
+ * Whether the browser applied an import map this backend added, recorded on
982
+ * the map so every player and every copy of this module on the page reads the
983
+ * same answer: `pending` until the backend knows, then `applied` or
984
+ * `rejected`. A document with a rejected map loads elements through
985
+ * es-module-shims.
986
+ */
987
+ const IMPORT_MAP_STATE_ATTRIBUTE = "data-pie-import-map";
988
+ const IMPORT_MAP_SETTLED_EVENT = "pie-import-map-settled";
989
+ const SHIM_IMPORT_MAP_TYPE = "importmap-shim";
990
+ function injectImportMap(json, doc, editorRuntime) {
531
991
  const script = doc.createElement("script");
532
- script.type = "importmap";
992
+ script.type = documentUsesImportShim(doc)
993
+ ? SHIM_IMPORT_MAP_TYPE
994
+ : "importmap";
995
+ script.setAttribute(IMPORT_MAP_STATE_ATTRIBUTE, "pending");
996
+ if (editorRuntime)
997
+ script.setAttribute(EDITOR_RUNTIME_ATTRIBUTE, editorRuntime);
533
998
  script.textContent = json;
534
999
  doc.head.appendChild(script);
1000
+ return script;
535
1001
  }
536
- function assertImportMapSupported() {
1002
+ function settleImportMap(script, state) {
1003
+ script.setAttribute(IMPORT_MAP_STATE_ATTRIBUTE, state);
1004
+ script.dispatchEvent(new Event(IMPORT_MAP_SETTLED_EVENT));
1005
+ }
1006
+ function documentUsesImportShim(doc) {
1007
+ return importMapScripts(doc).some((script) => script.getAttribute(IMPORT_MAP_STATE_ATTRIBUTE) === "rejected");
1008
+ }
1009
+ /** Resolves once no import map a PIE backend added to `doc` is pending. */
1010
+ async function importMapsSettled(doc) {
1011
+ const pending = importMapScripts(doc).filter((script) => script.getAttribute(IMPORT_MAP_STATE_ATTRIBUTE) === "pending");
1012
+ await Promise.all(pending.map((script) => new Promise((resolve) => script.addEventListener(IMPORT_MAP_SETTLED_EVENT, () => resolve(), {
1013
+ once: true,
1014
+ }))));
1015
+ }
1016
+ /**
1017
+ * Every mapping the import maps in `doc` define, the first map's rule winning
1018
+ * for a specifier as it does in the browser.
1019
+ */
1020
+ function documentImports(doc) {
1021
+ const imports = Object.create(null);
1022
+ for (const script of importMapScripts(doc)) {
1023
+ try {
1024
+ const mapped = JSON.parse(script.textContent || "{}")?.imports;
1025
+ if (!mapped || typeof mapped !== "object")
1026
+ continue;
1027
+ for (const [specifier, url] of Object.entries(mapped)) {
1028
+ if (typeof url === "string" && !(specifier in imports)) {
1029
+ imports[specifier] = url;
1030
+ }
1031
+ }
1032
+ }
1033
+ catch {
1034
+ // The browser rejects a map it cannot parse, so it maps nothing.
1035
+ }
1036
+ }
1037
+ return imports;
1038
+ }
1039
+ /**
1040
+ * es-module-shims in shim mode: it resolves through the import maps this
1041
+ * backend hands it and leaves the page's own scripts and load events alone.
1042
+ * A page that already runs es-module-shims keeps its instance, which has to be
1043
+ * in shim mode to take maps from this backend.
1044
+ */
1045
+ async function defaultImportShimLoader() {
1046
+ const scope = globalThis;
1047
+ if (!scope.importShim) {
1048
+ scope.esmsInitOptions = {
1049
+ ...scope.esmsInitOptions,
1050
+ shimMode: true,
1051
+ noLoadEventRetriggers: true,
1052
+ };
1053
+ await import("./module-shim.js");
1054
+ }
1055
+ const shim = scope.importShim;
1056
+ if (typeof shim !== "function" || typeof shim.addImportMap !== "function") {
1057
+ throw new Error("es-module-shims did not initialize, so this browser cannot load browser ESM elements after the page's first module load. Use the iife or preloaded strategy.");
1058
+ }
1059
+ try {
1060
+ shim.addImportMap({ imports: {} });
1061
+ }
1062
+ catch {
1063
+ throw new Error("This page runs es-module-shims in polyfill mode, and this browser rejects import maps added after the page's first module load. Load es-module-shims in shim mode, or use the iife or preloaded strategy.");
1064
+ }
1065
+ return shim;
1066
+ }
1067
+ function importMapsSupported() {
537
1068
  const htmlScriptElement = (typeof HTMLScriptElement !== "undefined" ? HTMLScriptElement : undefined);
538
- const supports = typeof htmlScriptElement?.supports === "function" &&
539
- htmlScriptElement.supports("importmap");
540
- if (!supports) {
1069
+ return (typeof htmlScriptElement?.supports === "function" &&
1070
+ htmlScriptElement.supports("importmap"));
1071
+ }
1072
+ function assertImportMapSupported() {
1073
+ if (!importMapsSupported()) {
541
1074
  throw new Error('This browser does not support import maps. Use moduleResolution="url" or switch to iife/preloaded strategy.');
542
1075
  }
543
1076
  }