@pie-players/pie-players-shared 0.3.67 → 0.3.69

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 (87) hide show
  1. package/README.md +59 -0
  2. package/dist/formative/index.d.ts +16 -0
  3. package/dist/formative/index.js +5 -0
  4. package/dist/formative/mastery.d.ts +16 -0
  5. package/dist/formative/mastery.js +43 -0
  6. package/dist/formative/outcome.d.ts +19 -0
  7. package/dist/formative/outcome.js +90 -0
  8. package/dist/formative/policy.d.ts +37 -0
  9. package/dist/formative/policy.js +86 -0
  10. package/dist/formative/session.d.ts +19 -0
  11. package/dist/formative/session.js +113 -0
  12. package/dist/formative/state.d.ts +60 -0
  13. package/dist/formative/state.js +140 -0
  14. package/dist/formative/types.d.ts +164 -0
  15. package/dist/formative/types.js +13 -0
  16. package/dist/i18n/catalogs.d.ts +38 -0
  17. package/dist/i18n/catalogs.js +47 -0
  18. package/dist/i18n/index.d.ts +28 -4
  19. package/dist/i18n/index.js +34 -3
  20. package/dist/i18n/language-tags.d.ts +51 -0
  21. package/dist/i18n/language-tags.js +84 -0
  22. package/dist/i18n/messages/en-US.d.ts +601 -0
  23. package/dist/i18n/messages/en-US.js +617 -0
  24. package/dist/i18n/messages/nl-NL.d.ts +516 -0
  25. package/dist/i18n/messages/nl-NL.js +516 -0
  26. package/dist/i18n/provider.d.ts +168 -0
  27. package/dist/i18n/provider.js +472 -0
  28. package/dist/i18n/types.d.ts +177 -28
  29. package/dist/i18n/types.js +6 -2
  30. package/dist/index.d.ts +3 -1
  31. package/dist/index.js +2 -0
  32. package/dist/pie/custom-element-define.d.ts +24 -0
  33. package/dist/pie/custom-element-define.js +36 -6
  34. package/dist/pie/index.d.ts +1 -1
  35. package/dist/pie/index.js +1 -1
  36. package/dist/pie/versioned-tag.js +3 -2
  37. package/dist/security/sanitize-forbidden-lists.d.ts +8 -0
  38. package/dist/security/sanitize-forbidden-lists.js +41 -0
  39. package/dist/security/sanitize-item-markup.js +3 -36
  40. package/dist/security/sanitize-svg-icon.js +3 -25
  41. package/dist/security/wrap-overwide-images.d.ts +5 -10
  42. package/dist/security/wrap-overwide-images.js +13 -62
  43. package/dist/security/wrap-overwide-tables.d.ts +5 -10
  44. package/dist/security/wrap-overwide-tables.js +13 -62
  45. package/dist/security/wrap-overwide.d.ts +51 -0
  46. package/dist/security/wrap-overwide.js +91 -0
  47. package/dist/server/npm-auth-env.d.ts +13 -0
  48. package/dist/server/npm-auth-env.js +64 -0
  49. package/dist/timed-media/index.d.ts +17 -0
  50. package/dist/timed-media/index.js +16 -0
  51. package/dist/timed-media/media-element-source.d.ts +36 -0
  52. package/dist/timed-media/media-element-source.js +121 -0
  53. package/dist/timed-media/policy.d.ts +36 -0
  54. package/dist/timed-media/policy.js +297 -0
  55. package/dist/timed-media/session.d.ts +21 -0
  56. package/dist/timed-media/session.js +85 -0
  57. package/dist/timed-media/state.d.ts +66 -0
  58. package/dist/timed-media/state.js +357 -0
  59. package/dist/timed-media/types.d.ts +343 -0
  60. package/dist/timed-media/types.js +12 -0
  61. package/dist/tools/term-lookup.d.ts +189 -0
  62. package/dist/tools/term-lookup.js +284 -0
  63. package/dist/types/index.d.ts +105 -1
  64. package/dist/ui/attribute-coercion.d.ts +1 -0
  65. package/dist/ui/attribute-coercion.js +19 -0
  66. package/dist/ui/first-focusable.d.ts +22 -0
  67. package/dist/ui/first-focusable.js +47 -0
  68. package/dist/ui/focus-trap.js +7 -2
  69. package/dist/ui/pointer-drag.d.ts +30 -0
  70. package/dist/ui/pointer-drag.js +34 -0
  71. package/package.json +38 -6
  72. package/dist/i18n/loader.d.ts +0 -35
  73. package/dist/i18n/loader.js +0 -132
  74. package/dist/i18n/simple-i18n.d.ts +0 -68
  75. package/dist/i18n/simple-i18n.js +0 -198
  76. package/dist/i18n/translations/ar/common.json +0 -36
  77. package/dist/i18n/translations/ar/toolkit.json +0 -48
  78. package/dist/i18n/translations/ar/tools.json +0 -103
  79. package/dist/i18n/translations/en/common.json +0 -36
  80. package/dist/i18n/translations/en/toolkit.json +0 -48
  81. package/dist/i18n/translations/en/tools.json +0 -103
  82. package/dist/i18n/translations/es/common.json +0 -36
  83. package/dist/i18n/translations/es/toolkit.json +0 -48
  84. package/dist/i18n/translations/es/tools.json +0 -103
  85. package/dist/i18n/translations/zh/common.json +0 -36
  86. package/dist/i18n/translations/zh/toolkit.json +0 -48
  87. package/dist/i18n/translations/zh/tools.json +0 -103
@@ -1,57 +1,206 @@
1
1
  /**
2
2
  * I18n Type Definitions
3
3
  *
4
- * Shared types for internationalization system.
5
- * Defined in players-shared to avoid circular dependencies.
4
+ * The provider interface and the key/locale types derived from the English
5
+ * catalog. Defined in `players-shared` to avoid circular dependencies, and kept
6
+ * free of any runtime value so a tool can reach the whole contract through
7
+ * `import type` and compile it away entirely.
8
+ *
9
+ * @module @pie-players/pie-players-shared/i18n/types
10
+ */
11
+ import type enUS from "./messages/en-US.js";
12
+ /**
13
+ * Locale tags this repository ships a catalog for.
14
+ *
15
+ * Full BCP-47 rather than bare language subtags. Content producers disagree on
16
+ * syntax — the Learnosity transform emits POSIX `nl_NL`, QTI catalog cards carry
17
+ * `xml:lang` — and the resolver normalizes both onto these, so this list is the
18
+ * set of catalogs, not the set of accepted inputs.
6
19
  */
20
+ export type BundledLocaleCode = "en-US" | "nl-NL";
7
21
  /**
8
- * Plural translation forms
9
- * Follows ICU MessageFormat plural rules
22
+ * Any locale tag. A host may supply a locale we ship no catalog for and provide
23
+ * its own messages through {@link I18nConfig.customMessages}, so this is
24
+ * deliberately open while still autocompleting the bundled set.
25
+ */
26
+ export type LocaleCode = BundledLocaleCode | (string & {});
27
+ /**
28
+ * Plural translation forms, keyed by CLDR plural category.
29
+ *
30
+ * Only `other` is required. `Intl.PluralRules` selects the category for the
31
+ * active locale, so a locale with six forms can carry six and a locale with two
32
+ * carries two; a selected category the catalog lacks resolves to `other`.
10
33
  */
11
34
  export interface PluralTranslation {
12
35
  zero?: string;
13
- one: string;
36
+ one?: string;
37
+ two?: string;
14
38
  few?: string;
15
39
  many?: string;
16
40
  other: string;
17
41
  }
42
+ /** A catalog node: a leaf string, a plural group, or a namespace. */
43
+ export type MessageNode = string | PluralTranslation | {
44
+ [key: string]: MessageNode;
45
+ };
46
+ /** A whole catalog, as one locale module default-exports it. */
47
+ export type MessageCatalog = {
48
+ [key: string]: MessageNode;
49
+ };
50
+ type PluralCategoryKey = keyof PluralTranslation;
18
51
  /**
19
- * Translation bundle structure
52
+ * Dot-notation keys of a catalog shape.
53
+ *
54
+ * A plural group terminates the path at the group itself: `plural()` is called
55
+ * with `tools.textToSpeech.charactersSelected`, never with
56
+ * `…charactersSelected.other`. A group is recognized by every one of its keys
57
+ * being a CLDR plural category — the containment has to run this way round,
58
+ * because a group carrying only `one`/`other` does not contain the whole
59
+ * category union.
60
+ */
61
+ type NestedKeys<T> = {
62
+ [K in keyof T & string]: T[K] extends string ? K : keyof T[K] extends PluralCategoryKey ? K : T[K] extends object ? K | `${K}.${NestedKeys<T[K]>}` : K;
63
+ }[keyof T & string];
64
+ /**
65
+ * Every key the English catalog defines.
66
+ *
67
+ * Derived from the catalog's shape rather than maintained alongside it, so a
68
+ * mistyped key is a compile error instead of a raw key rendered on screen. This
69
+ * is what makes the coverage script a completeness check rather than the only
70
+ * line of defence.
71
+ */
72
+ export type MessageKey = NestedKeys<typeof enUS>;
73
+ /**
74
+ * A key assembled at runtime from an id the catalog cannot enumerate — an
75
+ * element category, a host-authored registration's key. Branded, so the only way
76
+ * to produce one is `dynamicMessageKey` and a mistyped literal stays a compile
77
+ * error at every ordinary call site.
78
+ *
79
+ * The brand is a named property rather than a `unique symbol`: a module-local
80
+ * symbol is not identity-compatible across two emitted copies of this
81
+ * declaration, and packages here resolve `players-shared` to `src` or to `dist`
82
+ * depending on build order.
83
+ */
84
+ export type DynamicMessageKey = string & {
85
+ readonly __pieDynamicMessageKey: true;
86
+ };
87
+ /**
88
+ * A key argument: a catalog key, or an explicitly-asserted computed one. The
89
+ * assertion lives in `provider.ts` as `dynamicMessageKey`, so this module stays
90
+ * type-only.
91
+ */
92
+ export type MessageKeyInput = MessageKey | DynamicMessageKey;
93
+ /** Interpolation values substituted into `{placeholder}` slots. */
94
+ export type InterpolationValues = Record<string, string | number | boolean | null | undefined>;
95
+ /** Options for {@link I18nProvider.plural}; `count` also interpolates. */
96
+ export interface PluralOptions extends InterpolationValues {
97
+ count: number;
98
+ }
99
+ /** Writing direction of a locale. */
100
+ export type TextDirection = "ltr" | "rtl";
101
+ /**
102
+ * Translation bundle structure.
103
+ *
104
+ * Retained as the resolved, in-memory form of one locale's catalog.
20
105
  */
21
106
  export interface TranslationBundle {
22
107
  locale: string;
23
- translations: Record<string, string | PluralTranslation>;
24
- direction: "ltr" | "rtl";
108
+ translations: MessageCatalog;
109
+ direction: TextDirection;
25
110
  }
26
111
  /**
27
- * I18n configuration
112
+ * Provider configuration.
28
113
  */
29
114
  export interface I18nConfig {
30
- /** Initial locale (default: browser language) */
31
- locale?: string;
32
- /** Fallback locale (default: 'en') */
33
- fallbackLocale?: string;
34
- /** Translation loader function */
35
- loadTranslations?: (locale: string) => Promise<TranslationBundle>;
36
- /** Bundled translations (for hybrid loading) */
37
- bundledTranslations?: Record<string, TranslationBundle>;
38
- /** Missing key handler */
115
+ /** Initial locale. Defaults to `en-US`; never auto-detected. */
116
+ locale?: LocaleCode;
117
+ /** Locale every missing key falls back to. Defaults to `en-US`. */
118
+ fallbackLocale?: LocaleCode;
119
+ /**
120
+ * Loader for a locale this repository ships. Omitted, only the statically
121
+ * bundled English catalog and any `customMessages` are available — which is
122
+ * exactly what a tool bundle wants, so no locale chunk is emitted into it.
123
+ */
124
+ loadCatalog?: (locale: string) => Promise<MessageCatalog>;
125
+ /** Locale tags {@link loadCatalog} can serve. */
126
+ availableLocales?: readonly string[];
127
+ /**
128
+ * Host-supplied messages, keyed by locale tag. Consulted ahead of the
129
+ * bundled catalog for the same locale, so a deployment can override one
130
+ * label without forking a catalog, and can supply a locale we do not ship.
131
+ */
132
+ customMessages?: Record<string, MessageCatalog>;
133
+ /** Called once per missing key per lookup. */
39
134
  onMissingKey?: (key: string, locale: string) => void;
40
- /** Debug mode */
135
+ /** Reserved for provider-level diagnostics. */
41
136
  debug?: boolean;
42
137
  }
43
138
  /**
44
- * I18nService API contract
139
+ * The contract components resolve against.
140
+ *
141
+ * Deliberately small and free of Svelte, DOM and framework types: a component
142
+ * declares `i18n?: I18nProvider`, and a host is free to implement it over
143
+ * i18next, ICU MessageFormat or its own catalog rather than take ours.
45
144
  */
46
- export interface I18nServiceApi {
47
- initialize(config: I18nConfig): Promise<void>;
48
- t(key: string, params?: Record<string, any>): string;
49
- tn(key: string, count: number, params?: Record<string, any>): string;
145
+ export interface I18nProvider {
146
+ /** Active locale tag, canonicalized to the resolved catalog's tag. */
50
147
  getLocale(): string;
51
- setLocale(locale: string): Promise<void>;
52
- getDirection(): "ltr" | "rtl";
148
+ /**
149
+ * Switch locale, loading its catalog if a loader is configured.
150
+ *
151
+ * Subscribers are notified once the catalog is in place, so a component's
152
+ * reactive read re-runs against the new locale rather than the old one.
153
+ */
154
+ setLocale(locale: LocaleCode): Promise<void> | void;
155
+ /** Translate a key, interpolating `{placeholder}` slots. */
156
+ t(key: MessageKeyInput, values?: InterpolationValues): string;
157
+ /** Translate a plural group, selecting the category for `options.count`. */
158
+ plural?(key: MessageKeyInput, options: PluralOptions): string;
159
+ /**
160
+ * Writing direction of the active locale.
161
+ *
162
+ * Components stamp this onto their own host's `dir`. `direction` is an
163
+ * inherited CSS property, so one attribute on the host crosses a shadow
164
+ * boundary into the component's content with no per-node wiring.
165
+ */
166
+ getDirection?(): TextDirection;
167
+ /**
168
+ * A view of this provider fixed to another locale.
169
+ *
170
+ * Two players on one page can render different interface locales without either
171
+ * mutating the other's active locale. A provider that cannot produce a view
172
+ * omits this, and callers fall back to the provider itself.
173
+ */
174
+ withLocale?(locale: LocaleCode): I18nProvider;
175
+ /** Observe locale and catalog changes. Returns an unsubscribe function. */
176
+ subscribe?(listener: () => void): () => void;
177
+ /** Whether a key resolves in the active locale or its fallback chain. */
178
+ hasKey?(key: MessageKeyInput): boolean;
179
+ /** Locale tags this provider can serve. */
180
+ getAvailableLocales?(): string[];
181
+ /** Whether a locale's catalog is already resident. */
182
+ isLocaleLoaded?(locale: LocaleCode): boolean;
183
+ /** Locale-aware number formatting. */
184
+ formatNumber?(value: number, options?: Intl.NumberFormatOptions): string;
185
+ /** Locale-aware date formatting. */
186
+ formatDate?(date: Date, options?: Intl.DateTimeFormatOptions): string;
187
+ }
188
+ /**
189
+ * The full service surface of the bundled provider.
190
+ *
191
+ * `I18nProvider` is what components depend on; this is what the provider and
192
+ * the toolkit's `I18nService` wrapper implement, so the optional members above
193
+ * are all present here.
194
+ */
195
+ export interface I18nServiceApi extends I18nProvider {
196
+ initialize(config: I18nConfig): Promise<void>;
197
+ plural(key: MessageKeyInput, options: PluralOptions): string;
198
+ getDirection(): TextDirection;
53
199
  getAvailableLocales(): string[];
54
- isLocaleLoaded(locale: string): boolean;
200
+ isLocaleLoaded(locale: LocaleCode): boolean;
55
201
  subscribe(listener: () => void): () => void;
56
- hasKey(key: string): boolean;
202
+ hasKey(key: MessageKeyInput): boolean;
203
+ addCustomMessages(locale: string, messages: MessageCatalog): void;
204
+ withLocale(locale: LocaleCode): I18nProvider;
57
205
  }
206
+ export {};
@@ -1,7 +1,11 @@
1
1
  /**
2
2
  * I18n Type Definitions
3
3
  *
4
- * Shared types for internationalization system.
5
- * Defined in players-shared to avoid circular dependencies.
4
+ * The provider interface and the key/locale types derived from the English
5
+ * catalog. Defined in `players-shared` to avoid circular dependencies, and kept
6
+ * free of any runtime value so a tool can reach the whole contract through
7
+ * `import type` and compile it away entirely.
8
+ *
9
+ * @module @pie-players/pie-players-shared/i18n/types
6
10
  */
7
11
  export {};
package/dist/index.d.ts CHANGED
@@ -6,9 +6,11 @@ export * from "./security/index.js";
6
6
  export * from "./loaders/index.js";
7
7
  export * from "./object/index.js";
8
8
  export * from "./pie/index.js";
9
- export type { AssessmentEntity, AssessmentItemRef, AssessmentSection, AdvancedItemConfig, ConfigEntity, ConfigResource, Env, ItemConfig, ItemEntity, ItemSession, OutcomeResponse, PassageEntity, PieController, PieContent, PieDefaultModel, PieItemElement, PieModel, QuestionEntity, RubricBlock, TestPart, } from "./types/index.js";
9
+ export type { AssessmentEntity, AssessmentItemRef, AssessmentSection, AdvancedItemConfig, ConfigEntity, ConfigResource, Env, FormativeDeliveryPolicy, FormativeItemPolicy, TimedMediaSectionData, ItemConfig, ItemEntity, ItemSession, OutcomeResponse, PassageEntity, PieController, PieContent, PieDefaultModel, PieItemElement, PieModel, QuestionEntity, RubricBlock, TestPart, } from "./types/index.js";
10
10
  export { editorPostFix } from "./types/index.js";
11
+ export * from "./ui/attribute-coercion.js";
11
12
  export * from "./ui/content-styles.js";
13
+ export * from "./ui/pointer-drag.js";
12
14
  export * from "./ui/focus-trap.js";
13
15
  export * from "./ui/first-focusable.js";
14
16
  export * from "./ui/debug-panel-persistence.js";
package/dist/index.js CHANGED
@@ -8,7 +8,9 @@ export * from "./object/index.js";
8
8
  // Barrel export for PIE runtime utilities
9
9
  export * from "./pie/index.js";
10
10
  export { editorPostFix } from "./types/index.js";
11
+ export * from "./ui/attribute-coercion.js";
11
12
  export * from "./ui/content-styles.js";
13
+ export * from "./ui/pointer-drag.js";
12
14
  export * from "./ui/focus-trap.js";
13
15
  export * from "./ui/first-focusable.js";
14
16
  export * from "./ui/debug-panel-persistence.js";
@@ -3,4 +3,28 @@ export type SafeDefineResult = {
3
3
  status: SafeDefineStatus;
4
4
  tagName: string;
5
5
  };
6
+ export type SafeDefineOutcome = "defined" | "already-defined" | "defined-wrapped" | "error" | "wrapped-error";
7
+ export interface SafeDefineAttempt {
8
+ outcome: SafeDefineOutcome;
9
+ tagName: string;
10
+ error?: unknown;
11
+ }
12
+ export interface AttemptCustomElementDefineOptions {
13
+ /**
14
+ * The browser rejects registering a constructor that's already
15
+ * registered under a different tag name with the same
16
+ * `NotSupportedError` used for a duplicate *tag* registration. When set,
17
+ * a collision that isn't actually a same-tag duplicate (`customElements.get`
18
+ * still doesn't return the tag after the failed define) is retried once
19
+ * with a distinct wrapper subclass so this tag name still ends up defined.
20
+ */
21
+ allowWrappedFallback?: boolean;
22
+ }
23
+ /**
24
+ * Attempt to define a custom element, classifying the outcome instead of
25
+ * throwing, so callers with different error-surfacing conventions (throw vs.
26
+ * record-and-continue) can build their own public behavior on one shared
27
+ * duplicate-define/collision core.
28
+ */
29
+ export declare const attemptCustomElementDefine: (tagName: string, elementConstructor: CustomElementConstructor, context?: string, options?: AttemptCustomElementDefineOptions) => SafeDefineAttempt;
6
30
  export declare const defineCustomElementSafely: (tagName: string, elementConstructor: CustomElementConstructor, context?: string) => SafeDefineResult;
@@ -9,22 +9,52 @@ const isDuplicateDefineError = (error) => {
9
9
  : "";
10
10
  return maybeName === "NotSupportedError";
11
11
  };
12
- export const defineCustomElementSafely = (tagName, elementConstructor, context = "custom element tag") => {
12
+ /**
13
+ * Attempt to define a custom element, classifying the outcome instead of
14
+ * throwing, so callers with different error-surfacing conventions (throw vs.
15
+ * record-and-continue) can build their own public behavior on one shared
16
+ * duplicate-define/collision core.
17
+ */
18
+ export const attemptCustomElementDefine = (tagName, elementConstructor, context = "custom element tag", options = {}) => {
13
19
  const validTagName = validateCustomElementTag(tagName, context);
14
20
  if (customElements.get(validTagName)) {
15
- return { status: "already-defined", tagName: validTagName };
21
+ return { outcome: "already-defined", tagName: validTagName };
16
22
  }
17
23
  try {
18
24
  customElements.define(validTagName, elementConstructor);
19
- return { status: "defined", tagName: validTagName };
25
+ return { outcome: "defined", tagName: validTagName };
20
26
  }
21
27
  catch (error) {
22
28
  if (!isDuplicateDefineError(error)) {
23
- throw error;
29
+ return { outcome: "error", tagName: validTagName, error };
24
30
  }
25
31
  if (customElements.get(validTagName)) {
26
- return { status: "already-defined", tagName: validTagName };
32
+ return { outcome: "already-defined", tagName: validTagName };
33
+ }
34
+ if (!options.allowWrappedFallback) {
35
+ return { outcome: "error", tagName: validTagName, error };
36
+ }
37
+ try {
38
+ customElements.define(validTagName, class extends elementConstructor {
39
+ });
40
+ return { outcome: "defined-wrapped", tagName: validTagName };
41
+ }
42
+ catch (wrappedError) {
43
+ return {
44
+ outcome: "wrapped-error",
45
+ tagName: validTagName,
46
+ error: wrappedError,
47
+ };
27
48
  }
28
- throw error;
29
49
  }
30
50
  };
51
+ export const defineCustomElementSafely = (tagName, elementConstructor, context = "custom element tag") => {
52
+ const attempt = attemptCustomElementDefine(tagName, elementConstructor, context);
53
+ if (attempt.outcome === "error" || attempt.outcome === "wrapped-error") {
54
+ throw attempt.error;
55
+ }
56
+ return {
57
+ status: attempt.outcome === "already-defined" ? "already-defined" : "defined",
58
+ tagName: attempt.tagName,
59
+ };
60
+ };
@@ -36,7 +36,7 @@ export { pieRegistry } from "./registry.js";
36
36
  export { findPieController, scorePieItem } from "./scoring.js";
37
37
  export type { Entry, EventListeners, EventListenersMap, LoadPieElementsOptions, PieElement, PieRegistry, UpdatePieElementOptions, } from "./types.js";
38
38
  export { BundleType, defaultPieElementOptions, isCustomElementConstructor, isPieAvailable, isPieRegistryAvailable, Status, } from "./types.js";
39
- export { defineCustomElementSafely, type SafeDefineResult, type SafeDefineStatus, } from "./custom-element-define.js";
39
+ export { attemptCustomElementDefine, defineCustomElementSafely, type AttemptCustomElementDefineOptions, type SafeDefineAttempt, type SafeDefineOutcome, type SafeDefineResult, type SafeDefineStatus, } from "./custom-element-define.js";
40
40
  export { toPrintHashedTag, toViewTag, validateCustomElementTag, VIEW_TAG_SUFFIX, } from "./tag-names.js";
41
41
  export type { PieViewMode } from "./tag-names.js";
42
42
  export { updatePieElement, updatePieElements, updatePieElementWithRef, } from "./updates.js";
package/dist/pie/index.js CHANGED
@@ -37,7 +37,7 @@ export { BundleType, defaultPieElementOptions, isCustomElementConstructor,
37
37
  // Type guards (functions)
38
38
  isPieAvailable, isPieRegistryAvailable, Status, } from "./types.js";
39
39
  // Tag naming helpers
40
- export { defineCustomElementSafely, } from "./custom-element-define.js";
40
+ export { attemptCustomElementDefine, defineCustomElementSafely, } from "./custom-element-define.js";
41
41
  export { toPrintHashedTag, toViewTag, validateCustomElementTag, VIEW_TAG_SUFFIX, } from "./tag-names.js";
42
42
  // Updates
43
43
  export { updatePieElement, updatePieElements, updatePieElementWithRef, } from "./updates.js";
@@ -27,8 +27,9 @@ export function parseVersionedTagName(tagName) {
27
27
  export function toPackageVersionedTag(tagName, packageSpec, options = {}) {
28
28
  const { version } = parsePackageName(packageSpec);
29
29
  if (!version.trim() && options.preserveUnversionedTag) {
30
- // aggregateElements historically kept unversioned package specs on their
31
- // authored tag. Preserve that caller-specific behavior.
30
+ // pie-item contract compatibility: aggregateElements historically kept
31
+ // unversioned package specs on their authored tag; already-authored item
32
+ // content with no version pinned must keep rendering under that tag.
32
33
  return tagName;
33
34
  }
34
35
  const targetEncodedVersion = encodeVersionForTag(version);
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Shared DOMPurify forbid-lists for every in-repo sanitizer (item/passage
3
+ * markup, SVG icons). Keeping one canonical list means a newly-identified
4
+ * dangerous tag or attribute only needs to be added once to cover every
5
+ * sanitizer instead of drifting between hand-maintained copies.
6
+ */
7
+ export declare const SANITIZER_FORBIDDEN_TAGS: string[];
8
+ export declare const SANITIZER_FORBIDDEN_ATTRS: string[];
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Shared DOMPurify forbid-lists for every in-repo sanitizer (item/passage
3
+ * markup, SVG icons). Keeping one canonical list means a newly-identified
4
+ * dangerous tag or attribute only needs to be added once to cover every
5
+ * sanitizer instead of drifting between hand-maintained copies.
6
+ */
7
+ export const SANITIZER_FORBIDDEN_TAGS = [
8
+ "script",
9
+ "iframe",
10
+ "object",
11
+ "embed",
12
+ "base",
13
+ "form",
14
+ "meta",
15
+ "link",
16
+ // <foreignObject> inside an <svg> is a well-known escape hatch back into
17
+ // HTML context.
18
+ "foreignobject",
19
+ ];
20
+ // DOMPurify already strips `on*` handlers via its default block-list; these
21
+ // entries guarantee they stay stripped even if a consumer tweaks defaults,
22
+ // and they cover the common SVG / math sinks.
23
+ export const SANITIZER_FORBIDDEN_ATTRS = [
24
+ "onerror",
25
+ "onload",
26
+ "onclick",
27
+ "onmouseover",
28
+ "onmouseout",
29
+ "onmouseenter",
30
+ "onmouseleave",
31
+ "onfocus",
32
+ "onblur",
33
+ "onkeydown",
34
+ "onkeyup",
35
+ "onkeypress",
36
+ "onsubmit",
37
+ "onchange",
38
+ "onbeforeunload",
39
+ "formaction",
40
+ "xlink:href",
41
+ ];
@@ -8,6 +8,7 @@
8
8
  * allow-list.
9
9
  */
10
10
  import DOMPurify from "dompurify";
11
+ import { SANITIZER_FORBIDDEN_ATTRS, SANITIZER_FORBIDDEN_TAGS, } from "./sanitize-forbidden-lists.js";
11
12
  import { wrapOverwideImages } from "./wrap-overwide-images.js";
12
13
  import { wrapOverwideTables } from "./wrap-overwide-tables.js";
13
14
  // Attributes every PIE element / wrapper is allowed to carry.
@@ -28,42 +29,8 @@ const BASE_ALLOWED_ATTRS = [
28
29
  "dir",
29
30
  ];
30
31
  const BASE_URI_SAFE_ATTRS = ["pie-id"];
31
- const FORBIDDEN_TAGS = [
32
- "script",
33
- "iframe",
34
- "object",
35
- "embed",
36
- "base",
37
- "form",
38
- "meta",
39
- "link",
40
- // <foreignObject> inside an <svg> is a well-known escape hatch back
41
- // into HTML context; match the SVG-icon sanitizer and forbid it here
42
- // so both sanitizers agree on the surface.
43
- "foreignobject",
44
- ];
45
- // DOMPurify already strips `on*` handlers via its default block-list;
46
- // these entries guarantee they stay stripped even if a consumer tweaks
47
- // defaults, and they cover the common SVG / math sinks.
48
- const FORBIDDEN_ATTRS = [
49
- "onerror",
50
- "onload",
51
- "onclick",
52
- "onmouseover",
53
- "onmouseout",
54
- "onmouseenter",
55
- "onmouseleave",
56
- "onfocus",
57
- "onblur",
58
- "onkeydown",
59
- "onkeyup",
60
- "onkeypress",
61
- "onsubmit",
62
- "onchange",
63
- "onbeforeunload",
64
- "formaction",
65
- "xlink:href",
66
- ];
32
+ const FORBIDDEN_TAGS = SANITIZER_FORBIDDEN_TAGS;
33
+ const FORBIDDEN_ATTRS = SANITIZER_FORBIDDEN_ATTRS;
67
34
  // Any tag that looks like a custom element (contains a hyphen) is permitted
68
35
  // provided it starts with `pie-` or is explicitly named in
69
36
  // `allowedCustomElements`. This intentionally keeps third-party unknown
@@ -10,6 +10,7 @@
10
10
  * or the input is not a string.
11
11
  */
12
12
  import DOMPurify from "dompurify";
13
+ import { SANITIZER_FORBIDDEN_ATTRS, SANITIZER_FORBIDDEN_TAGS, } from "./sanitize-forbidden-lists.js";
13
14
  let svgPurifierInstance = null;
14
15
  function resolveSvgPurifier() {
15
16
  if (svgPurifierInstance)
@@ -23,31 +24,8 @@ function resolveSvgPurifier() {
23
24
  : DOMPurify;
24
25
  return svgPurifierInstance;
25
26
  }
26
- const FORBIDDEN_TAGS = [
27
- "script",
28
- "foreignobject",
29
- "iframe",
30
- "object",
31
- "embed",
32
- "base",
33
- "form",
34
- ];
35
- const FORBIDDEN_ATTRS = [
36
- "onerror",
37
- "onload",
38
- "onclick",
39
- "onmouseover",
40
- "onmouseout",
41
- "onmouseenter",
42
- "onmouseleave",
43
- "onfocus",
44
- "onblur",
45
- "onkeydown",
46
- "onkeyup",
47
- "onkeypress",
48
- "formaction",
49
- "xlink:href",
50
- ];
27
+ const FORBIDDEN_TAGS = SANITIZER_FORBIDDEN_TAGS;
28
+ const FORBIDDEN_ATTRS = SANITIZER_FORBIDDEN_ATTRS;
51
29
  const stringCache = new Map();
52
30
  const STRING_CACHE_MAX = 64;
53
31
  export function sanitizeSvgIcon(icon) {
@@ -22,17 +22,12 @@
22
22
  * PIE element paints into its own light DOM (e.g. a `pie-passage`'s
23
23
  * model-driven content) get the same scrollable affordance even though
24
24
  * they never appeared in the authored markup string.
25
+ *
26
+ * The wrapping itself lives in `./wrap-overwide.js`, shared with the table
27
+ * wrapper: only the four values below and the accessible name differ.
25
28
  */
26
- export interface WrapOverwideImagesInElementOptions {
27
- /**
28
- * When `true`, images whose nearest `pie-*` ancestor is *strictly between*
29
- * the image and `root` are left alone. Used by the string pipeline so the
30
- * authored-markup pass doesn't restructure a PIE element's own template.
31
- * Defaults to `false` so the live-DOM pass *does* wrap element-rendered
32
- * images.
33
- */
34
- skipPieDescendants?: boolean;
35
- }
29
+ import { type WrapOverwideOptions } from "./wrap-overwide.js";
30
+ export type WrapOverwideImagesInElementOptions = WrapOverwideOptions;
36
31
  /**
37
32
  * Wrap every unwrapped `<img>` descendant of `root` with the shared
38
33
  * horizontal-scroll span. Returns the number of newly-wrapped images so