@eagami/ui 5.75.0 → 5.76.0

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
@@ -36,7 +36,7 @@
36
36
  ng add @eagami/ui
37
37
  ```
38
38
 
39
- The schematic installs the package and registers the global stylesheet and fonts. To wire it up by hand, see [Setup](https://eagami.com/ui/setup).
39
+ The schematic installs the package and registers the global stylesheet, the fonts, and `provideStaleBuildReload()`. To wire it up by hand, see [Setup](https://eagami.com/ui/setup).
40
40
 
41
41
  ## Use
42
42
 
@@ -87,6 +87,18 @@ Set it app-wide with `forms`, or on a single field with the `showErrorsOn` input
87
87
  provideEagamiUi({ forms: { showErrorsOn: 'touch' } });
88
88
  ```
89
89
 
90
+ ## Deploys
91
+
92
+ A tab left open across a deploy still runs the old build, so the next page it lazy loads asks for a file the deploy removed. `provideStaleBuildReload()` from `@eagami/ui/router` loads that page in full on the new build instead, and tries only once:
93
+
94
+ ```ts
95
+ bootstrapApplication(AppComponent, {
96
+ providers: [provideRouter(routes), provideStaleBuildReload()],
97
+ });
98
+ ```
99
+
100
+ Pass `version` and `onReport` to log how each reload turned out, and check `isStaleBuildReloadError(error)` to keep the error it answered out of your error reporting.
101
+
90
102
  ## Testing
91
103
 
92
104
  `<ea-dialog>` and `<ea-drawer>` are built on the native `<dialog>` element, and `@eagami/ui/testing` keeps them working in jsdom, which lacks `showModal()` and `close()`. Call `installNativeDialogShim()` once from your test setup file:
@@ -0,0 +1,124 @@
1
+ import { DOCUMENT, Location, isPlatformBrowser } from "@angular/common";
2
+ import * as i0 from "@angular/core";
3
+ import { Injectable, InjectionToken, PLATFORM_ID, inject, makeEnvironmentProviders, provideEnvironmentInitializer } from "@angular/core";
4
+ import { NavigationEnd, NavigationError, Router } from "@angular/router";
5
+ const PENDING_RELOAD_KEY = "ea-pending-reload";
6
+ const CHUNK_LOAD_FAILURES = [
7
+ "Failed to fetch dynamically imported module",
8
+ "error loading dynamically imported module",
9
+ "Importing a module script failed",
10
+ "is not a valid JavaScript MIME type"
11
+ ];
12
+ const STALE_BUILD_RELOAD_CONFIG = new InjectionToken("STALE_BUILD_RELOAD_CONFIG");
13
+ const LOAD_PAGE = new InjectionToken("LOAD_PAGE", {
14
+ providedIn: "root",
15
+ factory: () => {
16
+ const location = inject(DOCUMENT).location;
17
+ return (url, replace) => replace ? location.replace(url) : location.assign(url);
18
+ }
19
+ });
20
+ const reloadedErrors = /* @__PURE__ */ new WeakSet();
21
+ function isStaleBuildReloadError(error) {
22
+ return error instanceof Error && reloadedErrors.has(error);
23
+ }
24
+ function isChunkLoadFailure(error) {
25
+ return error instanceof TypeError && CHUNK_LOAD_FAILURES.some((failure) => error.message.includes(failure));
26
+ }
27
+ function isSessionStorageAvailable() {
28
+ const probeKey = `${PENDING_RELOAD_KEY}-probe`;
29
+ try {
30
+ sessionStorage.setItem(probeKey, probeKey);
31
+ sessionStorage.removeItem(probeKey);
32
+ return true;
33
+ } catch {
34
+ return false;
35
+ }
36
+ }
37
+ var StaleBuildReloader = class StaleBuildReloader {
38
+ config = inject(STALE_BUILD_RELOAD_CONFIG);
39
+ loadPage = inject(LOAD_PAGE);
40
+ location = inject(Location);
41
+ router = inject(Router);
42
+ canTrackReloads = isSessionStorageAvailable();
43
+ pageId = Math.random().toString(36).slice(2);
44
+ constructor() {
45
+ this.router.events.subscribe((event) => {
46
+ if (event instanceof NavigationEnd) this.settlePendingReload();
47
+ else if (event instanceof NavigationError && isChunkLoadFailure(event.error)) this.reloadIntoCurrentBuild(event.url, event.error);
48
+ });
49
+ }
50
+ settlePendingReload() {
51
+ const pending = this.takePendingReload();
52
+ if (pending) this.report(pending.pageId === this.pageId ? "stayed" : "recovered", pending);
53
+ }
54
+ reloadIntoCurrentBuild(url, error) {
55
+ const pending = this.takePendingReload();
56
+ if (pending) {
57
+ const isAfterReload = pending.pageId !== this.pageId;
58
+ this.report(isAfterReload ? "failed-after-reload" : "stayed", pending);
59
+ if (isAfterReload) return;
60
+ }
61
+ if (!this.canTrackReloads || !navigator.onLine) return;
62
+ const navigation = this.router.currentNavigation();
63
+ const replace = !this.router.navigated || navigation?.trigger !== "imperative" || !!navigation.extras.replaceUrl;
64
+ const reload = {
65
+ pageId: this.pageId,
66
+ route: url.replace(/[?#].*/, ""),
67
+ version: this.config.version
68
+ };
69
+ sessionStorage.setItem(PENDING_RELOAD_KEY, JSON.stringify(reload));
70
+ reloadedErrors.add(error);
71
+ this.loadPage(this.location.prepareExternalUrl(url), replace);
72
+ }
73
+ takePendingReload() {
74
+ const value = this.canTrackReloads ? sessionStorage.getItem(PENDING_RELOAD_KEY) : null;
75
+ if (!value) return null;
76
+ sessionStorage.removeItem(PENDING_RELOAD_KEY);
77
+ return JSON.parse(value);
78
+ }
79
+ report(outcome, pending) {
80
+ this.config.onReport?.({
81
+ outcome,
82
+ route: pending.route,
83
+ fromVersion: pending.version,
84
+ toVersion: this.config.version
85
+ });
86
+ }
87
+ static ɵfac = i0.ɵɵngDeclareFactory({
88
+ minVersion: "12.0.0",
89
+ version: "22.2.3",
90
+ ngImport: i0,
91
+ type: StaleBuildReloader,
92
+ deps: [],
93
+ target: i0.ɵɵFactoryTarget.Injectable
94
+ });
95
+ static ɵprov = i0.ɵɵngDeclareInjectable({
96
+ minVersion: "12.0.0",
97
+ version: "22.2.3",
98
+ ngImport: i0,
99
+ type: StaleBuildReloader
100
+ });
101
+ };
102
+ i0.ɵɵngDeclareClassMetadata({
103
+ minVersion: "12.0.0",
104
+ version: "22.2.3",
105
+ ngImport: i0,
106
+ type: StaleBuildReloader,
107
+ decorators: [{ type: Injectable }],
108
+ ctorParameters: () => []
109
+ });
110
+ function provideStaleBuildReload(config = {}) {
111
+ return makeEnvironmentProviders([
112
+ {
113
+ provide: STALE_BUILD_RELOAD_CONFIG,
114
+ useValue: config
115
+ },
116
+ StaleBuildReloader,
117
+ provideEnvironmentInitializer(() => {
118
+ if (isPlatformBrowser(inject(PLATFORM_ID))) inject(StaleBuildReloader);
119
+ })
120
+ ]);
121
+ }
122
+ export { isStaleBuildReloadError, provideStaleBuildReload };
123
+
124
+ //# sourceMappingURL=eagami-ui-router.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"eagami-ui-router.mjs","names":[],"sources":["../../../router/src/stale-build-reload.ts"],"sourcesContent":["import { DOCUMENT, Location, isPlatformBrowser } from '@angular/common';\nimport {\n type EnvironmentProviders,\n Injectable,\n InjectionToken,\n PLATFORM_ID,\n inject,\n makeEnvironmentProviders,\n provideEnvironmentInitializer,\n} from '@angular/core';\nimport { NavigationEnd, NavigationError, Router } from '@angular/router';\n\n/**\n * How a reload into the current build turned out:\n *\n * - `'recovered'`: the reloaded page opened\n * - `'failed-after-reload'`: the reloaded page failed to open too, so the error is left to surface\n * - `'stayed'`: the visitor chose to stay when the browser asked them to confirm leaving\n */\nexport type StaleBuildReloadOutcome = 'recovered' | 'failed-after-reload' | 'stayed';\n\nexport interface StaleBuildReloadReport {\n outcome: StaleBuildReloadOutcome;\n /** The path that failed to open, without its query or fragment. */\n route: string;\n /** The `version` configured in the build that failed to open the page. */\n fromVersion?: string;\n /** The `version` configured in the build making the report. */\n toVersion?: string;\n}\n\nexport interface StaleBuildReloadConfig {\n /** The app's version, carried across the reload so a report names both builds. */\n version?: string;\n /** Called once per reload with how it turned out, for example to log it. */\n onReport?: (report: StaleBuildReloadReport) => void;\n}\n\ninterface PendingReload {\n pageId: string;\n route: string;\n version?: string;\n}\n\nconst PENDING_RELOAD_KEY = 'ea-pending-reload';\n\n// Each browser engine words a failed dynamic import differently\nconst CHUNK_LOAD_FAILURES = [\n 'Failed to fetch dynamically imported module', // Chromium\n 'error loading dynamically imported module', // Firefox\n 'Importing a module script failed', // WebKit\n 'is not a valid JavaScript MIME type', // WebKit, when the host answers a removed file with the app's page\n];\n\nconst STALE_BUILD_RELOAD_CONFIG = new InjectionToken<StaleBuildReloadConfig>(\n 'STALE_BUILD_RELOAD_CONFIG',\n);\n\n// Swappable in tests, since jsdom cannot navigate\nexport const LOAD_PAGE = new InjectionToken<(url: string, replace: boolean) => void>(\n 'LOAD_PAGE',\n {\n providedIn: 'root',\n factory: () => {\n const location = inject(DOCUMENT).location;\n return (url, replace) => (replace ? location.replace(url) : location.assign(url));\n },\n },\n);\n\nconst reloadedErrors = new WeakSet<Error>();\n\n/**\n * Whether `error` is a failed page load that `provideStaleBuildReload()` has\n * already answered by reloading. Error reporters can drop it, since the\n * reload's outcome reaches `onReport` instead:\n *\n * ```ts\n * Sentry.init({\n * beforeSend: (event, hint) =>\n * isStaleBuildReloadError(hint.originalException) ? null : event,\n * });\n * ```\n */\nexport function isStaleBuildReloadError(error: unknown): boolean {\n return error instanceof Error && reloadedErrors.has(error);\n}\n\nfunction isChunkLoadFailure(error: unknown): error is TypeError {\n return (\n error instanceof TypeError &&\n CHUNK_LOAD_FAILURES.some(failure => error.message.includes(failure))\n );\n}\n\nfunction isSessionStorageAvailable(): boolean {\n const probeKey = `${PENDING_RELOAD_KEY}-probe`;\n try {\n sessionStorage.setItem(probeKey, probeKey);\n sessionStorage.removeItem(probeKey);\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * A pending reload left by another page means this page is that reload. One\n * left by this same page means the visitor stayed when asked to confirm leaving.\n */\n@Injectable()\nclass StaleBuildReloader {\n private readonly config = inject(STALE_BUILD_RELOAD_CONFIG);\n private readonly loadPage = inject(LOAD_PAGE);\n private readonly location = inject(Location);\n private readonly router = inject(Router);\n\n private readonly canTrackReloads = isSessionStorageAvailable();\n private readonly pageId = Math.random().toString(36).slice(2);\n\n constructor() {\n this.router.events.subscribe(event => {\n if (event instanceof NavigationEnd) {\n this.settlePendingReload();\n } else if (event instanceof NavigationError && isChunkLoadFailure(event.error)) {\n this.reloadIntoCurrentBuild(event.url, event.error);\n }\n });\n }\n\n private settlePendingReload(): void {\n const pending = this.takePendingReload();\n\n if (pending) {\n this.report(pending.pageId === this.pageId ? 'stayed' : 'recovered', pending);\n }\n }\n\n private reloadIntoCurrentBuild(url: string, error: TypeError): void {\n const pending = this.takePendingReload();\n\n if (pending) {\n const isAfterReload = pending.pageId !== this.pageId;\n this.report(isAfterReload ? 'failed-after-reload' : 'stayed', pending);\n\n if (isAfterReload) {\n return;\n }\n }\n\n // Untracked, a failing reload could repeat forever, and offline it would only leave the app\n if (!this.canTrackReloads || !navigator.onLine) {\n return;\n }\n\n const navigation = this.router.currentNavigation();\n // A first load, Back or Forward, or a replacing navigation already has its history entry\n const replace =\n !this.router.navigated ||\n navigation?.trigger !== 'imperative' ||\n !!navigation.extras.replaceUrl;\n\n const reload: PendingReload = {\n pageId: this.pageId,\n route: url.replace(/[?#].*/, ''),\n version: this.config.version,\n };\n sessionStorage.setItem(PENDING_RELOAD_KEY, JSON.stringify(reload));\n reloadedErrors.add(error);\n\n this.loadPage(this.location.prepareExternalUrl(url), replace);\n }\n\n private takePendingReload(): PendingReload | null {\n const value = this.canTrackReloads\n ? sessionStorage.getItem(PENDING_RELOAD_KEY)\n : null;\n\n if (!value) {\n return null;\n }\n\n sessionStorage.removeItem(PENDING_RELOAD_KEY);\n return JSON.parse(value);\n }\n\n private report(outcome: StaleBuildReloadOutcome, pending: PendingReload): void {\n this.config.onReport?.({\n outcome,\n route: pending.route,\n fromVersion: pending.version,\n toVersion: this.config.version,\n });\n }\n}\n\n/**\n * Loads the current build in full when the code of a page being opened fails\n * to load, which is what a tab still running a build that a deploy has since\n * replaced runs into. The reload opens the page the visitor asked for, and\n * stops after one attempt if the new build fails as well.\n *\n * ```ts\n * bootstrapApplication(AppComponent, {\n * providers: [provideRouter(routes), provideStaleBuildReload()],\n * });\n * ```\n *\n * Does nothing during server rendering.\n */\nexport function provideStaleBuildReload(\n config: StaleBuildReloadConfig = {},\n): EnvironmentProviders {\n return makeEnvironmentProviders([\n { provide: STALE_BUILD_RELOAD_CONFIG, useValue: config },\n StaleBuildReloader,\n // Created ahead of the first navigation, which a tab restored on an old build can fail\n provideEnvironmentInitializer(() => {\n if (isPlatformBrowser(inject(PLATFORM_ID))) {\n inject(StaleBuildReloader);\n }\n }),\n ]);\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@eagami/ui",
3
- "version": "5.75.0",
3
+ "version": "5.76.0",
4
4
  "description": "Lightweight, accessible, themeable Angular UI component library and icon set built on CSS custom properties",
5
5
  "author": "Michal Wiraszka <michal@eagami.com>",
6
6
  "license": "MIT",
@@ -33,7 +33,13 @@
33
33
  "peerDependencies": {
34
34
  "@angular/common": "^21.0.0 || ^22.0.0",
35
35
  "@angular/core": "^21.0.0 || ^22.0.0",
36
- "@angular/forms": "^21.0.0 || ^22.0.0"
36
+ "@angular/forms": "^21.0.0 || ^22.0.0",
37
+ "@angular/router": "^21.0.0 || ^22.0.0"
38
+ },
39
+ "peerDependenciesMeta": {
40
+ "@angular/router": {
41
+ "optional": true
42
+ }
37
43
  },
38
44
  "dependencies": {},
39
45
  "engines": {
@@ -49,6 +55,10 @@
49
55
  "types": "./types/eagami-ui.d.ts",
50
56
  "default": "./fesm2022/eagami-ui.mjs"
51
57
  },
58
+ "./router": {
59
+ "types": "./types/eagami-ui-router.d.ts",
60
+ "default": "./fesm2022/eagami-ui-router.mjs"
61
+ },
52
62
  "./testing": {
53
63
  "types": "./types/eagami-ui-testing.d.ts",
54
64
  "default": "./fesm2022/eagami-ui-testing.mjs"
@@ -61,6 +71,7 @@
61
71
  "src/styles",
62
72
  "schematics",
63
73
  "testing",
74
+ "router",
64
75
  "README.md"
65
76
  ],
66
77
  "schematics": "./schematics/collection.json"
@@ -0,0 +1,4 @@
1
+ {
2
+ "module": "../fesm2022/eagami-ui-router.mjs",
3
+ "typings": "../types/eagami-ui-router.d.ts"
4
+ }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "schematics": {
3
3
  "ng-add": {
4
- "description": "Set up Eagami UI: register the global stylesheet and fonts in an Angular workspace.",
4
+ "description": "Set up Eagami UI: register the global stylesheet, the fonts, and provideStaleBuildReload() in an Angular workspace.",
5
5
  "factory": "./ng-add/index#ngAdd",
6
6
  "schema": "./ng-add/schema.json"
7
7
  }
@@ -2,10 +2,16 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.ngAdd = ngAdd;
4
4
  const schematics_1 = require("@angular-devkit/schematics");
5
+ const utility_1 = require("@schematics/angular/utility");
5
6
  const workspace_1 = require("@schematics/angular/utility/workspace");
6
7
  const utils_1 = require("./utils");
7
8
  function ngAdd(options) {
8
- return (0, schematics_1.chain)([registerStylesheet(options), registerFonts(options), logNextSteps()]);
9
+ return (0, schematics_1.chain)([
10
+ registerStylesheet(options),
11
+ registerFonts(options),
12
+ registerStaleBuildReload(options),
13
+ logNextSteps(),
14
+ ]);
9
15
  }
10
16
  function registerStylesheet(options) {
11
17
  return (0, workspace_1.updateWorkspace)(workspace => {
@@ -41,9 +47,15 @@ function registerFonts(options) {
41
47
  }
42
48
  };
43
49
  }
50
+ function registerStaleBuildReload(options) {
51
+ return async (tree) => {
52
+ const { name } = findProject(await (0, workspace_1.getWorkspace)(tree), options.project);
53
+ return (0, utility_1.addRootProvider)(name, ({ code, external }) => code `${external('provideStaleBuildReload', '@eagami/ui/router')}()`);
54
+ };
55
+ }
44
56
  function logNextSteps() {
45
57
  return (_tree, context) => {
46
- context.logger.info('Eagami UI is set up: the global stylesheet and fonts are registered.');
58
+ context.logger.info('Eagami UI is set up: the global stylesheet and fonts are registered, and provideStaleBuildReload() reloads a tab left open across a deploy into the new build.');
47
59
  context.logger.info('Optional: call provideEagamiUi() in app.config.ts for a custom brand palette or extra locales.');
48
60
  };
49
61
  }
@@ -0,0 +1,54 @@
1
+ import { EnvironmentProviders, InjectionToken } from "@angular/core";
2
+ /**
3
+ * How a reload into the current build turned out:
4
+ *
5
+ * - `'recovered'`: the reloaded page opened
6
+ * - `'failed-after-reload'`: the reloaded page failed to open too, so the error is left to surface
7
+ * - `'stayed'`: the visitor chose to stay when the browser asked them to confirm leaving
8
+ */
9
+ type StaleBuildReloadOutcome = 'recovered' | 'failed-after-reload' | 'stayed';
10
+ interface StaleBuildReloadReport {
11
+ outcome: StaleBuildReloadOutcome;
12
+ /** The path that failed to open, without its query or fragment. */
13
+ route: string;
14
+ /** The `version` configured in the build that failed to open the page. */
15
+ fromVersion?: string;
16
+ /** The `version` configured in the build making the report. */
17
+ toVersion?: string;
18
+ }
19
+ interface StaleBuildReloadConfig {
20
+ /** The app's version, carried across the reload so a report names both builds. */
21
+ version?: string;
22
+ /** Called once per reload with how it turned out, for example to log it. */
23
+ onReport?: (report: StaleBuildReloadReport) => void;
24
+ }
25
+ declare const LOAD_PAGE: InjectionToken<(url: string, replace: boolean) => void>;
26
+ /**
27
+ * Whether `error` is a failed page load that `provideStaleBuildReload()` has
28
+ * already answered by reloading. Error reporters can drop it, since the
29
+ * reload's outcome reaches `onReport` instead:
30
+ *
31
+ * ```ts
32
+ * Sentry.init({
33
+ * beforeSend: (event, hint) =>
34
+ * isStaleBuildReloadError(hint.originalException) ? null : event,
35
+ * });
36
+ * ```
37
+ */
38
+ export declare function isStaleBuildReloadError(error: unknown): boolean;
39
+ /**
40
+ * Loads the current build in full when the code of a page being opened fails
41
+ * to load, which is what a tab still running a build that a deploy has since
42
+ * replaced runs into. The reload opens the page the visitor asked for, and
43
+ * stops after one attempt if the new build fails as well.
44
+ *
45
+ * ```ts
46
+ * bootstrapApplication(AppComponent, {
47
+ * providers: [provideRouter(routes), provideStaleBuildReload()],
48
+ * });
49
+ * ```
50
+ *
51
+ * Does nothing during server rendering.
52
+ */
53
+ export declare function provideStaleBuildReload(config?: StaleBuildReloadConfig): EnvironmentProviders;
54
+ export type { StaleBuildReloadConfig, StaleBuildReloadOutcome, StaleBuildReloadReport };