@pie-players/pie-players-shared 0.3.68 → 0.3.70

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 (35) hide show
  1. package/dist/index.d.ts +1 -0
  2. package/dist/index.js +1 -0
  3. package/dist/loaders/iife-adapter.js +7 -3
  4. package/dist/pie/element-observer.d.ts +59 -0
  5. package/dist/pie/element-observer.js +131 -0
  6. package/dist/pie/index.d.ts +4 -2
  7. package/dist/pie/index.js +7 -2
  8. package/dist/pie/initialization.d.ts +13 -13
  9. package/dist/pie/initialization.js +97 -147
  10. package/dist/pie/initialize-element.d.ts +23 -0
  11. package/dist/pie/initialize-element.js +78 -0
  12. package/dist/pie/instrumentation-event-map.d.ts +1 -0
  13. package/dist/pie/instrumentation-event-map.js +18 -0
  14. package/dist/pie/math-rendering.js +6 -2
  15. package/dist/pie/types.d.ts +10 -0
  16. package/dist/pie/utils.d.ts +27 -0
  17. package/dist/pie/utils.js +56 -1
  18. package/dist/security/index.d.ts +3 -2
  19. package/dist/security/index.js +3 -2
  20. package/dist/security/sanitize-forbidden-lists.js +9 -0
  21. package/dist/security/sanitize-item-markup.js +4 -0
  22. package/dist/security/sanitize-style-attribute.d.ts +48 -0
  23. package/dist/security/sanitize-style-attribute.js +129 -0
  24. package/dist/security/sanitize-svg-icon.js +2 -0
  25. package/dist/security/validate-style-url.d.ts +13 -0
  26. package/dist/security/validate-style-url.js +36 -2
  27. package/dist/security/wrap-overwide-images.d.ts +7 -0
  28. package/dist/security/wrap-overwide-images.js +10 -1
  29. package/dist/security/wrap-overwide-tables.d.ts +7 -0
  30. package/dist/security/wrap-overwide-tables.js +10 -1
  31. package/dist/security/wrap-overwide.d.ts +16 -0
  32. package/dist/security/wrap-overwide.js +52 -0
  33. package/dist/ui/overlay-containment.d.ts +48 -0
  34. package/dist/ui/overlay-containment.js +65 -0
  35. package/package.json +7 -7
@@ -26,7 +26,7 @@
26
26
  * The wrapping itself lives in `./wrap-overwide.js`, shared with the image
27
27
  * wrapper: only the four values below and the accessible name differ.
28
28
  */
29
- import { wrapOverwideInElement, wrapOverwideMarkup, } from "./wrap-overwide.js";
29
+ import { isOverwideWrapMutation, wrapOverwideInElement, wrapOverwideMarkup, } from "./wrap-overwide.js";
30
30
  function buildAriaLabel(table) {
31
31
  // Authors commonly label tables via <caption>, aria-label, or aria-labelledby.
32
32
  // Prefer the most explicit signal and fall back to the generic label so
@@ -75,3 +75,12 @@ export function wrapOverwideTablesInElement(root, options = {}) {
75
75
  export function wrapOverwideTables(markup) {
76
76
  return wrapOverwideMarkup(markup, TABLE_SPEC);
77
77
  }
78
+ /**
79
+ * True when `record` mentions nothing but the output of
80
+ * {@link wrapOverwideTablesInElement} — a `pie-table-scroll` wrapper, or a
81
+ * `<table>` moving inside one. An observer-driven caller ignores such a record
82
+ * so its own wrap does not schedule a second pass.
83
+ */
84
+ export function isOverwideTableWrapMutation(record) {
85
+ return isOverwideWrapMutation(record, TABLE_SPEC);
86
+ }
@@ -49,3 +49,19 @@ export declare function wrapOverwideInElement(root: Element, spec: OverwideWrapS
49
49
  * is returned unchanged and the browser re-run on hydrate performs the wrap.
50
50
  */
51
51
  export declare function wrapOverwideMarkup(markup: string, spec: OverwideWrapSpec): string;
52
+ /**
53
+ * True when `record` mentions nothing but this module's own live-DOM output for
54
+ * `spec`: a wrapper landing beside the node it wraps, or that node moving inside
55
+ * it.
56
+ *
57
+ * An observer-driven caller re-runs {@link wrapOverwideInElement} on every
58
+ * mutation tick, and the wrap itself inserts elements — so without this test the
59
+ * pass retriggers the observer that scheduled it, converging only because the
60
+ * wrap is idempotent. Ignoring the pass's own records is what removes the
61
+ * retrigger; a PIE element that re-renders over its own subtree and drops the
62
+ * wrapper is the case where absorbing it instead becomes a sustained loop.
63
+ *
64
+ * Conservative in the safe direction: anything it cannot account for reads as
65
+ * foreign, which costs one extra pass and never a missed wrap.
66
+ */
67
+ export declare function isOverwideWrapMutation(record: MutationRecord, spec: OverwideWrapSpec): boolean;
@@ -89,3 +89,55 @@ export function wrapOverwideMarkup(markup, spec) {
89
89
  });
90
90
  return wrapped > 0 ? body.innerHTML : markup;
91
91
  }
92
+ function isElement(node) {
93
+ return node.nodeType === 1;
94
+ }
95
+ /** A `spec` wrapper holding nothing but the nodes `spec` wraps. */
96
+ function isWrapperFor(node, spec) {
97
+ if (!isElement(node))
98
+ return false;
99
+ if (!node.classList?.contains(spec.wrapperClass))
100
+ return false;
101
+ for (const child of Array.from(node.children)) {
102
+ if (!child.matches(spec.selector))
103
+ return false;
104
+ }
105
+ return true;
106
+ }
107
+ /** A node `spec` wraps, currently held by a `spec` wrapper. */
108
+ function isWrappedBy(node, spec) {
109
+ if (!isElement(node))
110
+ return false;
111
+ const parent = node.parentElement;
112
+ if (!parent?.classList?.contains(spec.wrapperClass))
113
+ return false;
114
+ return node.matches(spec.selector);
115
+ }
116
+ function everyNodeIsWrapOutput(nodes, spec) {
117
+ for (const node of Array.from(nodes)) {
118
+ if (!isWrapperFor(node, spec) && !isWrappedBy(node, spec))
119
+ return false;
120
+ }
121
+ return true;
122
+ }
123
+ /**
124
+ * True when `record` mentions nothing but this module's own live-DOM output for
125
+ * `spec`: a wrapper landing beside the node it wraps, or that node moving inside
126
+ * it.
127
+ *
128
+ * An observer-driven caller re-runs {@link wrapOverwideInElement} on every
129
+ * mutation tick, and the wrap itself inserts elements — so without this test the
130
+ * pass retriggers the observer that scheduled it, converging only because the
131
+ * wrap is idempotent. Ignoring the pass's own records is what removes the
132
+ * retrigger; a PIE element that re-renders over its own subtree and drops the
133
+ * wrapper is the case where absorbing it instead becomes a sustained loop.
134
+ *
135
+ * Conservative in the safe direction: anything it cannot account for reads as
136
+ * foreign, which costs one extra pass and never a missed wrap.
137
+ */
138
+ export function isOverwideWrapMutation(record, spec) {
139
+ if (record.type !== "childList")
140
+ return false;
141
+ return (everyNodeIsWrapOutput(record.addedNodes, spec) &&
142
+ everyNodeIsWrapOutput(record.removedNodes, spec));
143
+ }
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Containment for tool overlays that position themselves.
3
+ *
4
+ * An overlay that draws its own surface computes its own coordinates, and those
5
+ * coordinates are only meaningful against the box the browser resolved as its
6
+ * containing block. Reading that box from the element rather than from
7
+ * `window` is what keeps a tool correct wherever a host mounts it: the same
8
+ * numbers that centre a tool in the viewport put it outside a pane.
9
+ */
10
+ /** Distance kept clear of the block's edges so an overlay's controls stay reachable. */
11
+ export declare const DEFAULT_CONTAINMENT_GUTTER = 4;
12
+ export interface Size {
13
+ width: number;
14
+ height: number;
15
+ }
16
+ export interface Point {
17
+ x: number;
18
+ y: number;
19
+ }
20
+ /**
21
+ * The box `element` resolves its `left`/`top` against: its containing block, or
22
+ * the viewport when that is the initial containing block.
23
+ *
24
+ * `offsetParent` is the containing block as the browser resolved it, including
25
+ * across a shadow boundary, which is what makes this correct for a tool mounted
26
+ * by a host it knows nothing about. Returns `undefined` outside a browser.
27
+ */
28
+ export declare function resolveContainingBlockRect(element: HTMLElement | undefined | null): DOMRect | undefined;
29
+ /**
30
+ * Clamps a translate offset applied to a box that is already centred in its
31
+ * containing block, so the box stays inside it.
32
+ *
33
+ * The travel available on each axis is symmetric about the centre: half the
34
+ * block, less half the box and the gutter. A box wider or taller than its block
35
+ * cannot satisfy that on the offending axis, so it stays centred there rather
36
+ * than clamping to an inverted range.
37
+ */
38
+ export declare function clampOffsetWithinBlock(offset: Point, box: Size, block: Size, gutter?: number): Point;
39
+ /**
40
+ * Clamps an absolute centre point, in containing-block coordinates, so the box
41
+ * it positions stays inside that block.
42
+ *
43
+ * The same invariant as `clampOffsetWithinBlock` in the coordinate system an
44
+ * overlay uses when it writes `left`/`top` instead of translating: the two are
45
+ * conjugate by a translation of half the block, including the degenerate case
46
+ * where a box exceeds its block and centres on the offending axis.
47
+ */
48
+ export declare function clampPointWithinBlock(point: Point, box: Size, block: Size, gutter?: number): Point;
@@ -0,0 +1,65 @@
1
+ /**
2
+ * Containment for tool overlays that position themselves.
3
+ *
4
+ * An overlay that draws its own surface computes its own coordinates, and those
5
+ * coordinates are only meaningful against the box the browser resolved as its
6
+ * containing block. Reading that box from the element rather than from
7
+ * `window` is what keeps a tool correct wherever a host mounts it: the same
8
+ * numbers that centre a tool in the viewport put it outside a pane.
9
+ */
10
+ /** Distance kept clear of the block's edges so an overlay's controls stay reachable. */
11
+ export const DEFAULT_CONTAINMENT_GUTTER = 4;
12
+ /**
13
+ * The box `element` resolves its `left`/`top` against: its containing block, or
14
+ * the viewport when that is the initial containing block.
15
+ *
16
+ * `offsetParent` is the containing block as the browser resolved it, including
17
+ * across a shadow boundary, which is what makes this correct for a tool mounted
18
+ * by a host it knows nothing about. Returns `undefined` outside a browser.
19
+ */
20
+ export function resolveContainingBlockRect(element) {
21
+ if (typeof window === "undefined" || !element)
22
+ return undefined;
23
+ const parent = element.offsetParent;
24
+ if (parent instanceof HTMLElement) {
25
+ const rect = parent.getBoundingClientRect();
26
+ if (rect.width > 0 && rect.height > 0)
27
+ return rect;
28
+ }
29
+ return new DOMRect(0, 0, window.innerWidth, window.innerHeight);
30
+ }
31
+ /**
32
+ * Clamps a translate offset applied to a box that is already centred in its
33
+ * containing block, so the box stays inside it.
34
+ *
35
+ * The travel available on each axis is symmetric about the centre: half the
36
+ * block, less half the box and the gutter. A box wider or taller than its block
37
+ * cannot satisfy that on the offending axis, so it stays centred there rather
38
+ * than clamping to an inverted range.
39
+ */
40
+ export function clampOffsetWithinBlock(offset, box, block, gutter = DEFAULT_CONTAINMENT_GUTTER) {
41
+ const clampAxis = (value, boxExtent, blockExtent) => {
42
+ const travel = blockExtent / 2 - boxExtent / 2 - gutter;
43
+ if (travel <= 0)
44
+ return 0;
45
+ return Math.max(-travel, Math.min(travel, value));
46
+ };
47
+ return {
48
+ x: clampAxis(offset.x, box.width, block.width),
49
+ y: clampAxis(offset.y, box.height, block.height),
50
+ };
51
+ }
52
+ /**
53
+ * Clamps an absolute centre point, in containing-block coordinates, so the box
54
+ * it positions stays inside that block.
55
+ *
56
+ * The same invariant as `clampOffsetWithinBlock` in the coordinate system an
57
+ * overlay uses when it writes `left`/`top` instead of translating: the two are
58
+ * conjugate by a translation of half the block, including the degenerate case
59
+ * where a box exceeds its block and centres on the offending axis.
60
+ */
61
+ export function clampPointWithinBlock(point, box, block, gutter = DEFAULT_CONTAINMENT_GUTTER) {
62
+ const centre = { x: block.width / 2, y: block.height / 2 };
63
+ const offset = clampOffsetWithinBlock({ x: point.x - centre.x, y: point.y - centre.y }, box, block, gutter);
64
+ return { x: centre.x + offset.x, y: centre.y + offset.y };
65
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pie-players/pie-players-shared",
3
- "version": "0.3.68",
3
+ "version": "0.3.70",
4
4
  "type": "module",
5
5
  "description": "Shared runtime + UI utilities for PIE players",
6
6
  "license": "MIT",
@@ -106,8 +106,8 @@
106
106
  "dist"
107
107
  ],
108
108
  "dependencies": {
109
- "@pie-lib/math-rendering-module": "5.1.2",
110
- "dompurify": "^3.4.13",
109
+ "@pie-lib/math-rendering-module": "6.0.0",
110
+ "dompurify": "^3.4.14",
111
111
  "semver": "^7.8.5"
112
112
  },
113
113
  "scripts": {
@@ -116,18 +116,18 @@
116
116
  "typecheck": "tsc -p tsconfig.json --noEmit",
117
117
  "lint": "biome check .",
118
118
  "check": "tsc -p tsconfig.json --noEmit",
119
- "test": "bun test '.test.ts'",
119
+ "test": "bun test",
120
120
  "check-i18n": "bun run src/i18n/scripts/check-coverage.ts",
121
121
  "scan-hardcoded": "bun run src/i18n/scripts/scan-hardcoded.ts"
122
122
  },
123
123
  "devDependencies": {
124
- "@biomejs/biome": "^2.5.8",
125
- "@happy-dom/global-registrator": "^20.11.1",
124
+ "@biomejs/biome": "^2.5.11",
125
+ "@happy-dom/global-registrator": "^20.11.15",
126
126
  "@playwright/test": "^1.62.1",
127
127
  "@types/semver": "^7.8.0",
128
128
  "esbuild": "^0.28.2",
129
129
  "glob": "^13.0.0",
130
- "svelte": "^5.56.8",
130
+ "svelte": "^5.57.0",
131
131
  "typescript": "^5.9.3"
132
132
  },
133
133
  "homepage": "https://github.com/pie-framework/pie-players/tree/master/packages/players-shared#readme",