@proto-labs-inc/rig-core 0.2.0 → 0.2.2

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/dist/index.d.ts CHANGED
@@ -7,6 +7,10 @@
7
7
  import { type Manifest, type ResolvedPreviewTheme, type RigToFrame } from "@proto-labs-inc/wire";
8
8
  export { STATE_PARAM, variantParam } from "@proto-labs-inc/wire";
9
9
  export declare const INTERNAL_PREVIEW_THEME_PARAM = "__protoTheme";
10
+ /** Mark the page light or dark (data-proto-theme and the "dark" class) for
11
+ * a prototype that styles both. The page's own CSS decides color-scheme:
12
+ * forcing it here gave pages that only style light mode dark form fields
13
+ * on a light page whenever the viewer's system was dark. */
10
14
  export declare function applyPreviewTheme(theme: ResolvedPreviewTheme): void;
11
15
  /** All addressable state, mirrored to the URL. Keys: "state" | "v.<component>". */
12
16
  export declare class RigStore {
@@ -14,11 +18,15 @@ export declare class RigStore {
14
18
  private values;
15
19
  private listeners;
16
20
  constructor(manifest: Manifest);
21
+ updateManifest(manifest: Manifest): void;
17
22
  get(key: string): string | undefined;
18
23
  set(key: string, value: string): void;
19
24
  setMany(entries: Array<[string, string]>): void;
20
25
  setState(id: string, variants: Record<string, string>): void;
21
26
  subscribe: (l: () => void) => () => void;
27
+ /** Restore the exact URL, including removed selections, without writing a
28
+ * new history entry while Back or Forward is traversing an existing one. */
29
+ restoreUrl(): void;
22
30
  private syncUrl;
23
31
  }
24
32
  export declare function postToFrame(msg: RigToFrame): void;
package/dist/index.js CHANGED
@@ -6,16 +6,20 @@
6
6
  */
7
7
  import { PROTOCOL_VERSION, STATE_PARAM, variantParam, applyVariantSelection, isFrameToRig, } from "@proto-labs-inc/wire";
8
8
  import { loadFrameTools } from "./frame-tools-loader.js";
9
+ import { observePageHistory } from "./page-history.js";
9
10
  export { STATE_PARAM, variantParam } from "@proto-labs-inc/wire";
10
- const RIG_VERSION = "0.2.0";
11
+ const RIG_VERSION = "0.2.2";
11
12
  export const INTERNAL_PREVIEW_THEME_PARAM = "__protoTheme";
13
+ /** Mark the page light or dark (data-proto-theme and the "dark" class) for
14
+ * a prototype that styles both. The page's own CSS decides color-scheme:
15
+ * forcing it here gave pages that only style light mode dark form fields
16
+ * on a light page whenever the viewer's system was dark. */
12
17
  export function applyPreviewTheme(theme) {
13
18
  if (typeof document === "undefined")
14
19
  return;
15
20
  const root = document.documentElement;
16
21
  root.dataset.protoTheme = theme;
17
22
  root.classList.toggle("dark", theme === "dark");
18
- root.style.colorScheme = theme;
19
23
  }
20
24
  function resolvedSystemTheme() {
21
25
  return window.matchMedia("(prefers-color-scheme: dark)").matches
@@ -57,9 +61,12 @@ export class RigStore {
57
61
  hydrated = true;
58
62
  }
59
63
  if (hydrated) {
60
- window.history.replaceState(null, "", `?${params.toString()}`);
64
+ window.history.replaceState(null, "", `?${params.toString()}${window.location.hash || ""}`);
61
65
  }
62
66
  }
67
+ updateManifest(manifest) {
68
+ this.manifest = manifest;
69
+ }
63
70
  get(key) {
64
71
  return this.values.get(key);
65
72
  }
@@ -112,6 +119,21 @@ export class RigStore {
112
119
  this.listeners.delete(l);
113
120
  };
114
121
  };
122
+ /** Restore the exact URL, including removed selections, without writing a
123
+ * new history entry while Back or Forward is traversing an existing one. */
124
+ restoreUrl() {
125
+ const next = new Map();
126
+ for (const [key, value] of new URLSearchParams(window.location.search)) {
127
+ if (key === STATE_PARAM || key.startsWith("v."))
128
+ next.set(key, value);
129
+ }
130
+ const changed = next.size !== this.values.size ||
131
+ [...next].some(([key, value]) => this.values.get(key) !== value);
132
+ if (!changed)
133
+ return;
134
+ this.values = next;
135
+ this.listeners.forEach((listener) => listener());
136
+ }
115
137
  syncUrl() {
116
138
  const params = new URLSearchParams(window.location.search);
117
139
  for (const key of [...params.keys()]) {
@@ -120,9 +142,8 @@ export class RigStore {
120
142
  }
121
143
  for (const [k, v] of this.values)
122
144
  params.set(k, v);
123
- const search = `?${params.toString()}`;
145
+ const search = `?${params.toString()}${window.location.hash || ""}`;
124
146
  window.history.replaceState(null, "", search);
125
- postToFrame({ type: "state-changed", search: searchForFrame() });
126
147
  }
127
148
  }
128
149
  export function postToFrame(msg) {
@@ -200,6 +221,11 @@ async function captureThumbnail() {
200
221
  * Call client-side only.
201
222
  */
202
223
  export function wireRig(store, manifest) {
224
+ store.updateManifest(manifest);
225
+ const disposeHistory = observePageHistory(() => {
226
+ store.restoreUrl();
227
+ postToFrame({ type: "state-changed", search: searchForFrame() });
228
+ });
203
229
  const disposeTools = loadFrameTools(manifest.frameOrigin);
204
230
  let controlledTheme = false;
205
231
  const media = window.matchMedia("(prefers-color-scheme: dark)");
@@ -243,6 +269,7 @@ export function wireRig(store, manifest) {
243
269
  });
244
270
  }
245
271
  return () => {
272
+ disposeHistory();
246
273
  media.removeEventListener("change", onSystemTheme);
247
274
  window.removeEventListener("message", onMessage);
248
275
  disposeTools();
@@ -0,0 +1,4 @@
1
+ /** Every URL change in a prototype is a step the browser bar can revisit.
2
+ * Keep state-only history writes intact: routers use those for bookkeeping.
3
+ * Install after boot-time URL hydration, which is not a navigation. */
4
+ export declare function observePageHistory(onNavigate: () => void): () => void;
@@ -0,0 +1,38 @@
1
+ /** Every URL change in a prototype is a step the browser bar can revisit.
2
+ * Keep state-only history writes intact: routers use those for bookkeeping.
3
+ * Install after boot-time URL hydration, which is not a navigation. */
4
+ export function observePageHistory(onNavigate) {
5
+ const history = window.history;
6
+ const push = history.pushState;
7
+ const replace = history.replaceState;
8
+ let currentUrl = window.location.href;
9
+ const changed = () => {
10
+ const url = window.location.href;
11
+ if (url === currentUrl)
12
+ return;
13
+ currentUrl = url;
14
+ onNavigate();
15
+ };
16
+ const pushState = function (data, unused, url) {
17
+ push.call(this, data, unused, url);
18
+ changed();
19
+ };
20
+ const replaceState = function (data, unused, url) {
21
+ const next = url == null ? window.location.href : new URL(url, window.location.href).href;
22
+ const write = next === window.location.href ? replace : push;
23
+ write.call(this, data, unused, url);
24
+ changed();
25
+ };
26
+ history.pushState = pushState;
27
+ history.replaceState = replaceState;
28
+ window.addEventListener("popstate", changed);
29
+ window.addEventListener("hashchange", changed);
30
+ return () => {
31
+ if (history.pushState === pushState)
32
+ history.pushState = push;
33
+ if (history.replaceState === replaceState)
34
+ history.replaceState = replace;
35
+ window.removeEventListener("popstate", changed);
36
+ window.removeEventListener("hashchange", changed);
37
+ };
38
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@proto-labs-inc/rig-core",
3
- "version": "0.2.0",
3
+ "version": "0.2.2",
4
4
  "description": "Proto's rig, framework-free: URL-state sync, the postMessage bridge to the Frame, and comment mode.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -28,7 +28,7 @@
28
28
  }
29
29
  },
30
30
  "dependencies": {
31
- "@proto-labs-inc/wire": "^0.2.0"
31
+ "@proto-labs-inc/wire": "^0.2.1"
32
32
  },
33
33
  "devDependencies": {
34
34
  "typescript": "~6.0.2",