@motionstudies/web 0.1.0-alpha.31 → 0.1.0-alpha.33

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.
package/README.md CHANGED
@@ -25,6 +25,28 @@ Build release candidates with `npm run build:packages`. Distribution manifests a
25
25
 
26
26
  Source workspace manifests always stay private. `npm run check:release` builds public candidates, tests their packed consumer and records the tested tarball hashes; `npm run release:dry-run` inspects the publication without writing to npm. The manual main-branch `release.yml` workflow publishes those same tarballs with public access and provenance. See [release instructions](https://github.com/emmettl/motionstudies/blob/main/docs/RELEASING.md) for bootstrap-token and trusted-publisher setup. All four shared packages are MIT-licensed; each distribution includes `LICENSE`.
27
27
 
28
+ ## Edition maintenance and layout observation
29
+
30
+ These exports are introduced in coordinated release `0.1.0-alpha.33`; earlier releases do not expose them.
31
+
32
+ `checkEditionBoundary` from `@motionstudies/data/edition-boundary` verifies the four
33
+ shared registry pins, lockfile metadata, installed package identity, public exports
34
+ and declared source imports. Pass `directories` for the edition's required source
35
+ and worker roots, and `additionalPackages` for other packages needing the same
36
+ exact-pin/public-export checks. `root` defaults to the working directory. The
37
+ checker performs no installation, mutation or network access. It rejects missing
38
+ configured directories, workspaces, shared source copies and installed links.
39
+ `moduleReferences` from `@motionstudies/data/module-references` supplies the same
40
+ AST-based import analysis to local policy tests; it reports nonliteral imports as
41
+ unverifiable. Dataset, worker and publication policy remain edition-owned.
42
+
43
+ `observeLayout` from `@motionstudies/web/observe-layout` measures once immediately,
44
+ then batches resize notifications into one animation frame. Call its returned
45
+ cleanup before removing the observed elements; it disconnects the observer and
46
+ cancels pending work. Without ResizeObserver it still performs the initial
47
+ measurement. The caller owns the elements, geometry, CSS variables and their
48
+ cleanup; the helper does not choose clearance values or depend on React.
49
+
28
50
  ## Timetable patterns, station calls and observation windows
29
51
 
30
52
  `@motionstudies/core/domain/network-patterns` provides a transport-neutral repeated-schedule codec. `@motionstudies/web/use-pattern-network-day` lazily decodes current/adjacent chunks after verifying byte length and SHA-256; it exposes retry and drops old-source state when the manifest URL changes. Supply stable options and a stable asset resolver. Format/category options support existing edition wire contracts.
@@ -126,6 +126,14 @@ export function ButtonTooltips() {
126
126
  window.addEventListener('resize', hide);
127
127
  window.addEventListener('blur', hide);
128
128
  capability.addEventListener('change', hide);
129
+ // Lazy loading may finish after the first hover or keyboard focus event.
130
+ // Recover the current target instead of requiring the user to leave and return.
131
+ const focused = buttonAt(document.activeElement);
132
+ const hovered = document.querySelector('button:hover');
133
+ if (focused?.matches(':focus-visible'))
134
+ show(focused, 0);
135
+ else if (hovered && !window.matchMedia('(any-pointer: coarse)').matches)
136
+ show(hovered, 400);
129
137
  return () => {
130
138
  hide();
131
139
  observer.disconnect();
@@ -0,0 +1,4 @@
1
+ /** Measure once immediately, then coalesce resize notifications into animation frames.
2
+ * Keep geometry and CSS policy in the caller. Dispose before removing its elements.
3
+ */
4
+ export declare function observeLayout(elements: readonly Element[], measure: () => void): () => void;
@@ -0,0 +1,32 @@
1
+ /** Measure once immediately, then coalesce resize notifications into animation frames.
2
+ * Keep geometry and CSS policy in the caller. Dispose before removing its elements.
3
+ */
4
+ export function observeLayout(elements, measure) {
5
+ let frame;
6
+ let disposed = false;
7
+ const observer = typeof ResizeObserver === 'undefined' ? undefined : new ResizeObserver(() => {
8
+ if (disposed || frame !== undefined)
9
+ return;
10
+ frame = requestAnimationFrame(() => {
11
+ frame = undefined;
12
+ if (!disposed)
13
+ measure();
14
+ });
15
+ });
16
+ const dispose = () => {
17
+ disposed = true;
18
+ if (frame !== undefined)
19
+ cancelAnimationFrame(frame);
20
+ frame = undefined;
21
+ observer?.disconnect();
22
+ };
23
+ try {
24
+ elements.forEach(element => observer?.observe(element));
25
+ measure();
26
+ }
27
+ catch (error) {
28
+ dispose();
29
+ throw error;
30
+ }
31
+ return dispose;
32
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@motionstudies/web",
3
- "version": "0.1.0-alpha.31",
3
+ "version": "0.1.0-alpha.33",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "description": "Browser controls and loaders for Motion Studies.",
@@ -166,7 +166,12 @@
166
166
  "import": "./components/TimelineScrubber.js",
167
167
  "default": "./components/TimelineScrubber.js"
168
168
  },
169
- "./timeline-scrubber.css": "./components/timeline-scrubber.css"
169
+ "./timeline-scrubber.css": "./components/timeline-scrubber.css",
170
+ "./observe-layout": {
171
+ "types": "./observe-layout.d.ts",
172
+ "import": "./observe-layout.js",
173
+ "default": "./observe-layout.js"
174
+ }
170
175
  },
171
176
  "files": [
172
177
  "**/*.js",
@@ -183,7 +188,7 @@
183
188
  "**/*.css"
184
189
  ],
185
190
  "dependencies": {
186
- "@motionstudies/core": "0.1.0-alpha.31"
191
+ "@motionstudies/core": "0.1.0-alpha.33"
187
192
  },
188
193
  "peerDependencies": {
189
194
  "react": "^19.3.0",