@lilaquadrat/frontend 0.1.1

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 (39) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +132 -0
  3. package/dist/auth.d.ts +194 -0
  4. package/dist/auth.d.ts.map +1 -0
  5. package/dist/auth.js +280 -0
  6. package/dist/auth.js.map +1 -0
  7. package/dist/index.d.ts +5 -0
  8. package/dist/index.d.ts.map +1 -0
  9. package/dist/index.js +4 -0
  10. package/dist/index.js.map +1 -0
  11. package/dist/interfaces/AuthCallbacks.d.ts +29 -0
  12. package/dist/interfaces/AuthCallbacks.d.ts.map +1 -0
  13. package/dist/interfaces/AuthCallbacks.js +2 -0
  14. package/dist/interfaces/AuthCallbacks.js.map +1 -0
  15. package/dist/interfaces/AuthInitOptions.d.ts +15 -0
  16. package/dist/interfaces/AuthInitOptions.d.ts.map +1 -0
  17. package/dist/interfaces/AuthInitOptions.js +2 -0
  18. package/dist/interfaces/AuthInitOptions.js.map +1 -0
  19. package/dist/interfaces/AuthOptions.d.ts +21 -0
  20. package/dist/interfaces/AuthOptions.d.ts.map +1 -0
  21. package/dist/interfaces/AuthOptions.js +2 -0
  22. package/dist/interfaces/AuthOptions.js.map +1 -0
  23. package/dist/interfaces/KeycloakClient.d.ts +36 -0
  24. package/dist/interfaces/KeycloakClient.d.ts.map +1 -0
  25. package/dist/interfaces/KeycloakClient.js +2 -0
  26. package/dist/interfaces/KeycloakClient.js.map +1 -0
  27. package/dist/interfaces/KeycloakRedirectOptions.d.ts +13 -0
  28. package/dist/interfaces/KeycloakRedirectOptions.d.ts.map +1 -0
  29. package/dist/interfaces/KeycloakRedirectOptions.js +2 -0
  30. package/dist/interfaces/KeycloakRedirectOptions.js.map +1 -0
  31. package/dist/interfaces/KeycloakSilentCheckOptions.d.ts +28 -0
  32. package/dist/interfaces/KeycloakSilentCheckOptions.d.ts.map +1 -0
  33. package/dist/interfaces/KeycloakSilentCheckOptions.js +2 -0
  34. package/dist/interfaces/KeycloakSilentCheckOptions.js.map +1 -0
  35. package/dist/resize.d.ts +165 -0
  36. package/dist/resize.d.ts.map +1 -0
  37. package/dist/resize.js +249 -0
  38. package/dist/resize.js.map +1 -0
  39. package/package.json +69 -0
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Application-specific state hooks used by {@link Auth}.
3
+ *
4
+ * Implement these callbacks with any state mechanism, including plain objects,
5
+ * Redux, Pinia, Vuex, React state, or framework-independent stores.
6
+ *
7
+ * @example
8
+ * ```ts
9
+ * const callbacks: AuthCallbacks = {
10
+ * setToken: (token) => session.token = token,
11
+ * isSessionLoaded: () => session.user !== undefined,
12
+ * onAuthenticated: () => session.loadUser(),
13
+ * onUnauthenticated: () => session.reset(),
14
+ * };
15
+ * ```
16
+ */
17
+ export interface AuthCallbacks {
18
+ /** Stores the active token, or clears it when the user is no longer authenticated. */
19
+ setToken(token: string | null): void;
20
+ /** Returns whether the host app has already loaded the current authenticated session. */
21
+ isSessionLoaded?(): boolean;
22
+ /** Loads user data and permissions after an authenticated session is established. */
23
+ onAuthenticated?(): Promise<void> | void;
24
+ /** Clears user-specific application state after logout or token-refresh failure. */
25
+ onUnauthenticated?(): Promise<void> | void;
26
+ /** Receives token-refresh failures raised from Keycloak's expiration callback. */
27
+ onError?(error: unknown): void;
28
+ }
29
+ //# sourceMappingURL=AuthCallbacks.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AuthCallbacks.d.ts","sourceRoot":"","sources":["../../src/interfaces/AuthCallbacks.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AACH,MAAM,WAAW,aAAa;IAC5B,sFAAsF;IACtF,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI,CAAC;IACrC,yFAAyF;IACzF,eAAe,CAAC,IAAI,OAAO,CAAC;IAC5B,qFAAqF;IACrF,eAAe,CAAC,IAAI,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;IACzC,oFAAoF;IACpF,iBAAiB,CAAC,IAAI,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;IAC3C,kFAAkF;IAClF,OAAO,CAAC,CAAC,KAAK,EAAE,OAAO,GAAG,IAAI,CAAC;CAChC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=AuthCallbacks.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AuthCallbacks.js","sourceRoot":"","sources":["../../src/interfaces/AuthCallbacks.ts"],"names":[],"mappings":""}
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Values supplied to {@link Auth.init} for a single authentication initialization.
3
+ *
4
+ * @example
5
+ * ```ts
6
+ * await auth.init({ development: false, keycloakConfig: { url, realm, clientId } });
7
+ * ```
8
+ */
9
+ export interface AuthInitOptions<KeycloakConfiguration> {
10
+ /** Enables local development authentication without constructing a Keycloak client. */
11
+ development: boolean;
12
+ /** Configuration forwarded unchanged to `AuthOptions.createKeycloak` in production. */
13
+ keycloakConfig?: KeycloakConfiguration;
14
+ }
15
+ //# sourceMappingURL=AuthInitOptions.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AuthInitOptions.d.ts","sourceRoot":"","sources":["../../src/interfaces/AuthInitOptions.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,MAAM,WAAW,eAAe,CAAC,qBAAqB;IACpD,uFAAuF;IACvF,WAAW,EAAE,OAAO,CAAC;IACrB,uFAAuF;IACvF,cAAc,CAAC,EAAE,qBAAqB,CAAC;CACxC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=AuthInitOptions.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AuthInitOptions.js","sourceRoot":"","sources":["../../src/interfaces/AuthInitOptions.ts"],"names":[],"mappings":""}
@@ -0,0 +1,21 @@
1
+ import type { AuthCallbacks } from './AuthCallbacks.js';
2
+ import type { KeycloakClient } from './KeycloakClient.js';
3
+ /**
4
+ * Values supplied once when constructing an {@link Auth} instance.
5
+ *
6
+ * @example
7
+ * ```ts
8
+ * const options: AuthOptions<KeycloakConfig> = { callbacks, createKeycloak: (config) => new Keycloak(config) };
9
+ * ```
10
+ */
11
+ export interface AuthOptions<KeycloakConfiguration> {
12
+ /** Application callbacks that connect authentication changes to host state. */
13
+ callbacks: AuthCallbacks;
14
+ /** Builds the Keycloak client from the configuration supplied to `Auth.init`. */
15
+ createKeycloak?(configuration: KeycloakConfiguration): KeycloakClient;
16
+ /** Token used by development mode. Defaults to `DEVTOKEN`. */
17
+ developmentToken?: string;
18
+ /** Application origin used for Keycloak callback URLs. Defaults to `window.location.origin`. */
19
+ origin?: string;
20
+ }
21
+ //# sourceMappingURL=AuthOptions.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AuthOptions.d.ts","sourceRoot":"","sources":["../../src/interfaces/AuthOptions.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACxD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAE1D;;;;;;;GAOG;AACH,MAAM,WAAW,WAAW,CAAC,qBAAqB;IAChD,+EAA+E;IAC/E,SAAS,EAAE,aAAa,CAAC;IACzB,iFAAiF;IACjF,cAAc,CAAC,CAAC,aAAa,EAAE,qBAAqB,GAAG,cAAc,CAAC;IACtE,8DAA8D;IAC9D,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,gGAAgG;IAChG,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=AuthOptions.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AuthOptions.js","sourceRoot":"","sources":["../../src/interfaces/AuthOptions.ts"],"names":[],"mappings":""}
@@ -0,0 +1,36 @@
1
+ import type { KeycloakRedirectOptions } from './KeycloakRedirectOptions.js';
2
+ import type { KeycloakSilentCheckOptions } from './KeycloakSilentCheckOptions.js';
3
+ /**
4
+ * Minimal Keycloak client contract consumed by {@link Auth}.
5
+ *
6
+ * Use a `keycloak-js` instance directly, or adapt another authentication client
7
+ * to this shape in `AuthOptions.createKeycloak`.
8
+ *
9
+ * @example
10
+ * ```ts
11
+ * createKeycloak: (configuration): KeycloakClient => new Keycloak(configuration)
12
+ * ```
13
+ */
14
+ export interface KeycloakClient {
15
+ /** Whether the client currently has an authenticated session. */
16
+ authenticated?: boolean;
17
+ /** Current access token, populated after initialization or a token refresh. */
18
+ token?: string;
19
+ /** Called by Keycloak after the user logs out in another browser context. */
20
+ onAuthLogout?: () => void;
21
+ /** Called by Keycloak when its access token expires. */
22
+ onTokenExpired?: () => void;
23
+ /** Removes the token held by the client after a refresh failure. */
24
+ clearToken(): void;
25
+ /** Initializes Keycloak and silently checks for an existing session. */
26
+ init(options: KeycloakSilentCheckOptions): Promise<boolean>;
27
+ /** Refreshes the token when it expires within the requested number of seconds. */
28
+ updateToken(minValidity: number): Promise<boolean>;
29
+ /** Redirects the user to Keycloak's login page. */
30
+ login(options: KeycloakRedirectOptions): Promise<unknown> | unknown;
31
+ /** Redirects the user to Keycloak's logout page. */
32
+ logout(options: KeycloakRedirectOptions): Promise<unknown> | unknown;
33
+ /** Redirects the user to Keycloak's registration page. */
34
+ register(options: KeycloakRedirectOptions): Promise<unknown> | unknown;
35
+ }
36
+ //# sourceMappingURL=KeycloakClient.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"KeycloakClient.d.ts","sourceRoot":"","sources":["../../src/interfaces/KeycloakClient.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,8BAA8B,CAAC;AAC5E,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,iCAAiC,CAAC;AAElF;;;;;;;;;;GAUG;AACH,MAAM,WAAW,cAAc;IAC7B,iEAAiE;IACjE,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,+EAA+E;IAC/E,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,6EAA6E;IAC7E,YAAY,CAAC,EAAE,MAAM,IAAI,CAAC;IAC1B,wDAAwD;IACxD,cAAc,CAAC,EAAE,MAAM,IAAI,CAAC;IAC5B,oEAAoE;IACpE,UAAU,IAAI,IAAI,CAAC;IACnB,wEAAwE;IACxE,IAAI,CAAC,OAAO,EAAE,0BAA0B,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAC5D,kFAAkF;IAClF,WAAW,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IACnD,mDAAmD;IACnD,KAAK,CAAC,OAAO,EAAE,uBAAuB,GAAG,OAAO,CAAC,OAAO,CAAC,GAAG,OAAO,CAAC;IACpE,oDAAoD;IACpD,MAAM,CAAC,OAAO,EAAE,uBAAuB,GAAG,OAAO,CAAC,OAAO,CAAC,GAAG,OAAO,CAAC;IACrE,0DAA0D;IAC1D,QAAQ,CAAC,OAAO,EAAE,uBAAuB,GAAG,OAAO,CAAC,OAAO,CAAC,GAAG,OAAO,CAAC;CACxE"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=KeycloakClient.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"KeycloakClient.js","sourceRoot":"","sources":["../../src/interfaces/KeycloakClient.ts"],"names":[],"mappings":""}
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Options passed to Keycloak when starting a login, registration, or logout redirect.
3
+ *
4
+ * @example
5
+ * ```ts
6
+ * const redirect: KeycloakRedirectOptions = { redirectUri: 'https://app.example.com/login/callback' };
7
+ * ```
8
+ */
9
+ export interface KeycloakRedirectOptions {
10
+ /** Absolute URL to which Keycloak returns the browser after completing the action. */
11
+ redirectUri: string;
12
+ }
13
+ //# sourceMappingURL=KeycloakRedirectOptions.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"KeycloakRedirectOptions.d.ts","sourceRoot":"","sources":["../../src/interfaces/KeycloakRedirectOptions.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,MAAM,WAAW,uBAAuB;IACtC,sFAAsF;IACtF,WAAW,EAAE,MAAM,CAAC;CACrB"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=KeycloakRedirectOptions.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"KeycloakRedirectOptions.js","sourceRoot":"","sources":["../../src/interfaces/KeycloakRedirectOptions.ts"],"names":[],"mappings":""}
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Options used to initialize a Keycloak client with silent single-sign-on checking.
3
+ *
4
+ * `Auth.init` creates these values automatically; define this type only when
5
+ * implementing a custom `KeycloakClient`.
6
+ *
7
+ * @example
8
+ * ```ts
9
+ * async init(options: KeycloakSilentCheckOptions) {
10
+ * return keycloak.init(options);
11
+ * }
12
+ * ```
13
+ */
14
+ export interface KeycloakSilentCheckOptions {
15
+ /** Checks the current session without forcing an authentication redirect. */
16
+ onLoad: 'check-sso';
17
+ /** Uses the authorization-code flow. */
18
+ flow: 'standard';
19
+ /** Requires PKCE using SHA-256 for the authorization-code flow. */
20
+ pkceMethod: 'S256';
21
+ /** Requests the user's basic profile information. */
22
+ scope: 'profile';
23
+ /** URL of the hosted Keycloak silent-check HTML document. */
24
+ silentCheckSsoRedirectUri: string;
25
+ /** Allows Keycloak's fallback when silent checking is unavailable. */
26
+ silentCheckSsoFallback: boolean;
27
+ }
28
+ //# sourceMappingURL=KeycloakSilentCheckOptions.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"KeycloakSilentCheckOptions.d.ts","sourceRoot":"","sources":["../../src/interfaces/KeycloakSilentCheckOptions.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,MAAM,WAAW,0BAA0B;IACzC,6EAA6E;IAC7E,MAAM,EAAE,WAAW,CAAC;IACpB,wCAAwC;IACxC,IAAI,EAAE,UAAU,CAAC;IACjB,mEAAmE;IACnE,UAAU,EAAE,MAAM,CAAC;IACnB,qDAAqD;IACrD,KAAK,EAAE,SAAS,CAAC;IACjB,6DAA6D;IAC7D,yBAAyB,EAAE,MAAM,CAAC;IAClC,sEAAsE;IACtE,sBAAsB,EAAE,OAAO,CAAC;CACjC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=KeycloakSilentCheckOptions.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"KeycloakSilentCheckOptions.js","sourceRoot":"","sources":["../../src/interfaces/KeycloakSilentCheckOptions.ts"],"names":[],"mappings":""}
@@ -0,0 +1,165 @@
1
+ /**
2
+ * Browser viewport and CSS-media observer used by the shared resize singleton.
3
+ *
4
+ * Import the module-level `resize` instance instead of creating another instance
5
+ * in application code so all components consume the same reactive state.
6
+ *
7
+ * @example
8
+ * ```ts
9
+ * import resize from '@lilaquadrat/frontend/resize';
10
+ *
11
+ * resize.debounceTime = 100;
12
+ * window.addEventListener('resized', () => console.log(resize.realHeight));
13
+ * ```
14
+ */
15
+ export declare class Resize {
16
+ /**
17
+ * Pending debounce timer, exposed for applications that need to cancel it explicitly.
18
+ *
19
+ * @example
20
+ * ```ts
21
+ * clearTimeout(resize.timeout);
22
+ * ```
23
+ */
24
+ timeout?: ReturnType<typeof setTimeout>;
25
+ /**
26
+ * Delay in milliseconds between the last native resize event and the final update.
27
+ *
28
+ * @example
29
+ * ```ts
30
+ * resize.debounceTime = 150;
31
+ * ```
32
+ */
33
+ debounceTime: number;
34
+ /**
35
+ * Event dispatched on `window` after viewport dimensions are updated.
36
+ *
37
+ * @example
38
+ * ```ts
39
+ * window.addEventListener('resized', () => updateLayout());
40
+ * ```
41
+ */
42
+ resizedEvent?: Event;
43
+ /**
44
+ * Event dispatched on `window` after the active CSS media name changes.
45
+ *
46
+ * @example
47
+ * ```ts
48
+ * window.addEventListener('media', () => updateNavigation(resize.media));
49
+ * ```
50
+ */
51
+ mediaEvent?: Event;
52
+ /**
53
+ * Controls whether the next native resize dispatches an immediate initial update.
54
+ *
55
+ * It is reset after the debounced update so resize consumers can respond at the
56
+ * start and end of each resize interaction.
57
+ */
58
+ triggerStart: import("vue").Ref<boolean, boolean>;
59
+ /**
60
+ * Browser window when available, or `null` during server-side rendering.
61
+ *
62
+ * @example
63
+ * ```ts
64
+ * if (resize.safeWindow.value) resize.trigger();
65
+ * ```
66
+ */
67
+ safeWindow: import("vue").ComputedRef<Window | null>;
68
+ /**
69
+ * Active media name read from the visible child of `#mediadetection`.
70
+ *
71
+ * @example
72
+ * ```ts
73
+ * if (resize.media === 'desktop') showSidebar();
74
+ * ```
75
+ */
76
+ get media(): string;
77
+ set media(value: string);
78
+ /**
79
+ * Current visual viewport height in pixels.
80
+ *
81
+ * @example
82
+ * ```ts
83
+ * element.style.height = `${resize.realHeight}px`;
84
+ * ```
85
+ */
86
+ get realHeight(): number;
87
+ /**
88
+ * Initializes viewport state and listens for browser resize events when running in a browser.
89
+ *
90
+ * Normally the module singleton handles this. Create an instance only for an
91
+ * isolated application or test environment.
92
+ *
93
+ * @example
94
+ * ```ts
95
+ * const isolatedResize = new Resize();
96
+ * ```
97
+ */
98
+ constructor();
99
+ /**
100
+ * Updates media and viewport state, then dispatches the `resized` window event.
101
+ *
102
+ * Pass `true` only for the immediate event at the beginning of a resize sequence.
103
+ *
104
+ * @example
105
+ * ```ts
106
+ * resize.trigger();
107
+ * ```
108
+ */
109
+ trigger(isStart?: boolean): void;
110
+ /**
111
+ * Reads the class of the visible child within `#mediadetection` and emits `media` when it changes.
112
+ *
113
+ * @example
114
+ * ```ts
115
+ * resize.getMediaQuery();
116
+ * ```
117
+ */
118
+ getMediaQuery(): void;
119
+ /**
120
+ * Debounces browser resize events while emitting one immediate initial update.
121
+ *
122
+ * Call this only when forwarding a custom resize source; the browser listener
123
+ * registered by the singleton already calls it for native resize events.
124
+ *
125
+ * @example
126
+ * ```ts
127
+ * visualViewport?.addEventListener('resize', () => resize.debounce());
128
+ * ```
129
+ */
130
+ debounce(): void;
131
+ private setViewportHeight;
132
+ }
133
+ /**
134
+ * Shared `Resize` instance initialized once when this module is imported in a browser.
135
+ *
136
+ * @example
137
+ * ```ts
138
+ * import resize from '@lilaquadrat/frontend/resize';
139
+ * console.log(resize.realHeight);
140
+ * ```
141
+ */
142
+ declare const resize: Resize;
143
+ /**
144
+ * Returns the shared reactive viewport state and its controlling singleton.
145
+ *
146
+ * Use `media`, `realHeight`, and `resized` in Vue components; use `plugin` only
147
+ * when manually triggering or configuring the shared resize observer.
148
+ *
149
+ * @example
150
+ * ```ts
151
+ * import { useResize } from '@lilaquadrat/frontend/resize';
152
+ *
153
+ * const { media, realHeight } = useResize();
154
+ * // In a Vue template: {{ media }} / {{ realHeight }}
155
+ * ```
156
+ */
157
+ export declare function useResize(): {
158
+ realHeight: import("vue").Ref<number, number>;
159
+ media: import("vue").Ref<string, string>;
160
+ resized: import("vue").Ref<number, number>;
161
+ plugin: Resize;
162
+ };
163
+ export { resize };
164
+ export default resize;
165
+ //# sourceMappingURL=resize.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resize.d.ts","sourceRoot":"","sources":["../src/resize.ts"],"names":[],"mappings":"AA+BA;;;;;;;;;;;;;GAaG;AACH,qBAAa,MAAM;IACjB;;;;;;;OAOG;IACH,OAAO,CAAC,EAAE,UAAU,CAAC,OAAO,UAAU,CAAC,CAAC;IAExC;;;;;;;OAOG;IACH,YAAY,SAAM;IAElB;;;;;;;OAOG;IACH,YAAY,CAAC,EAAE,KAAK,CAAC;IAErB;;;;;;;OAOG;IACH,UAAU,CAAC,EAAE,KAAK,CAAC;IAEnB;;;;;OAKG;IACH,YAAY,sCAAa;IAEzB;;;;;;;OAOG;IACH,UAAU,2CAAkF;IAE5F;;;;;;;OAOG;IACH,IAAI,KAAK,IAAI,MAAM,CAElB;IAED,IAAI,KAAK,CAAC,KAAK,EAAE,MAAM,EAEtB;IAED;;;;;;;OAOG;IACH,IAAI,UAAU,IAAI,MAAM,CAEvB;IAED;;;;;;;;;;OAUG;;IAcH;;;;;;;;;OASG;IACH,OAAO,CAAC,OAAO,CAAC,EAAE,OAAO,GAAG,IAAI;IAShC;;;;;;;OAOG;IACH,aAAa,IAAI,IAAI;IAuBrB;;;;;;;;;;OAUG;IACH,QAAQ,IAAI,IAAI;IAUhB,OAAO,CAAC,iBAAiB;CAU1B;AAED;;;;;;;;GAQG;AACH,QAAA,MAAM,MAAM,QAAe,CAAC;AAE5B;;;;;;;;;;;;;GAaG;AACH,wBAAgB,SAAS;;;;;EAOxB;AAED,OAAO,EAAE,MAAM,EAAE,CAAC;AAClB,eAAe,MAAM,CAAC"}
package/dist/resize.js ADDED
@@ -0,0 +1,249 @@
1
+ import { computed, ref } from 'vue';
2
+ /**
3
+ * Vue-compatible browser resize state.
4
+ *
5
+ * Include `#mediadetection` in the page with one child displayed at each CSS
6
+ * breakpoint. The displayed child's class becomes the current media value.
7
+ *
8
+ * @example
9
+ * ```html
10
+ * <div id="mediadetection">
11
+ * <span class="mobile"></span>
12
+ * <span class="desktop"></span>
13
+ * </div>
14
+ * ```
15
+ *
16
+ * @example
17
+ * ```css
18
+ * #mediadetection > span { display: none; }
19
+ * #mediadetection > .mobile { display: block; }
20
+ * @media (min-width: 768px) {
21
+ * #mediadetection > .mobile { display: none; }
22
+ * #mediadetection > .desktop { display: block; }
23
+ * }
24
+ * ```
25
+ */
26
+ const mediaReference = ref('mobile');
27
+ const resizedReference = ref(Date.now());
28
+ const realHeightReference = ref(0);
29
+ /**
30
+ * Browser viewport and CSS-media observer used by the shared resize singleton.
31
+ *
32
+ * Import the module-level `resize` instance instead of creating another instance
33
+ * in application code so all components consume the same reactive state.
34
+ *
35
+ * @example
36
+ * ```ts
37
+ * import resize from '@lilaquadrat/frontend/resize';
38
+ *
39
+ * resize.debounceTime = 100;
40
+ * window.addEventListener('resized', () => console.log(resize.realHeight));
41
+ * ```
42
+ */
43
+ export class Resize {
44
+ /**
45
+ * Pending debounce timer, exposed for applications that need to cancel it explicitly.
46
+ *
47
+ * @example
48
+ * ```ts
49
+ * clearTimeout(resize.timeout);
50
+ * ```
51
+ */
52
+ timeout;
53
+ /**
54
+ * Delay in milliseconds between the last native resize event and the final update.
55
+ *
56
+ * @example
57
+ * ```ts
58
+ * resize.debounceTime = 150;
59
+ * ```
60
+ */
61
+ debounceTime = 50;
62
+ /**
63
+ * Event dispatched on `window` after viewport dimensions are updated.
64
+ *
65
+ * @example
66
+ * ```ts
67
+ * window.addEventListener('resized', () => updateLayout());
68
+ * ```
69
+ */
70
+ resizedEvent;
71
+ /**
72
+ * Event dispatched on `window` after the active CSS media name changes.
73
+ *
74
+ * @example
75
+ * ```ts
76
+ * window.addEventListener('media', () => updateNavigation(resize.media));
77
+ * ```
78
+ */
79
+ mediaEvent;
80
+ /**
81
+ * Controls whether the next native resize dispatches an immediate initial update.
82
+ *
83
+ * It is reset after the debounced update so resize consumers can respond at the
84
+ * start and end of each resize interaction.
85
+ */
86
+ triggerStart = ref(true);
87
+ /**
88
+ * Browser window when available, or `null` during server-side rendering.
89
+ *
90
+ * @example
91
+ * ```ts
92
+ * if (resize.safeWindow.value) resize.trigger();
93
+ * ```
94
+ */
95
+ safeWindow = computed(() => (typeof window === 'undefined' ? null : window));
96
+ /**
97
+ * Active media name read from the visible child of `#mediadetection`.
98
+ *
99
+ * @example
100
+ * ```ts
101
+ * if (resize.media === 'desktop') showSidebar();
102
+ * ```
103
+ */
104
+ get media() {
105
+ return mediaReference.value;
106
+ }
107
+ set media(value) {
108
+ mediaReference.value = value;
109
+ }
110
+ /**
111
+ * Current visual viewport height in pixels.
112
+ *
113
+ * @example
114
+ * ```ts
115
+ * element.style.height = `${resize.realHeight}px`;
116
+ * ```
117
+ */
118
+ get realHeight() {
119
+ return realHeightReference.value;
120
+ }
121
+ /**
122
+ * Initializes viewport state and listens for browser resize events when running in a browser.
123
+ *
124
+ * Normally the module singleton handles this. Create an instance only for an
125
+ * isolated application or test environment.
126
+ *
127
+ * @example
128
+ * ```ts
129
+ * const isolatedResize = new Resize();
130
+ * ```
131
+ */
132
+ constructor() {
133
+ const browserWindow = this.safeWindow.value;
134
+ if (!browserWindow)
135
+ return;
136
+ this.resizedEvent = new Event('resized');
137
+ this.mediaEvent = new Event('media');
138
+ realHeightReference.value = this.setViewportHeight();
139
+ this.getMediaQuery();
140
+ browserWindow.addEventListener('resize', () => this.debounce());
141
+ }
142
+ /**
143
+ * Updates media and viewport state, then dispatches the `resized` window event.
144
+ *
145
+ * Pass `true` only for the immediate event at the beginning of a resize sequence.
146
+ *
147
+ * @example
148
+ * ```ts
149
+ * resize.trigger();
150
+ * ```
151
+ */
152
+ trigger(isStart) {
153
+ this.getMediaQuery();
154
+ realHeightReference.value = this.setViewportHeight();
155
+ resizedReference.value = Date.now();
156
+ if (this.resizedEvent)
157
+ this.safeWindow.value?.dispatchEvent(this.resizedEvent);
158
+ if (!isStart)
159
+ this.triggerStart.value = true;
160
+ }
161
+ /**
162
+ * Reads the class of the visible child within `#mediadetection` and emits `media` when it changes.
163
+ *
164
+ * @example
165
+ * ```ts
166
+ * resize.getMediaQuery();
167
+ * ```
168
+ */
169
+ getMediaQuery() {
170
+ if (typeof document === 'undefined')
171
+ return;
172
+ const element = document.getElementById('mediadetection');
173
+ if (!element)
174
+ return;
175
+ const child = Array.from(element.children).find((childElement) => this.safeWindow.value?.getComputedStyle(childElement).display === 'block');
176
+ if (!child)
177
+ return;
178
+ const childClass = child.getAttribute('class');
179
+ if (!childClass)
180
+ return;
181
+ const mediaChanged = this.media !== childClass;
182
+ this.media = childClass;
183
+ if (mediaChanged && this.mediaEvent) {
184
+ this.safeWindow.value?.dispatchEvent(this.mediaEvent);
185
+ }
186
+ }
187
+ /**
188
+ * Debounces browser resize events while emitting one immediate initial update.
189
+ *
190
+ * Call this only when forwarding a custom resize source; the browser listener
191
+ * registered by the singleton already calls it for native resize events.
192
+ *
193
+ * @example
194
+ * ```ts
195
+ * visualViewport?.addEventListener('resize', () => resize.debounce());
196
+ * ```
197
+ */
198
+ debounce() {
199
+ if (this.triggerStart.value) {
200
+ this.triggerStart.value = false;
201
+ this.trigger(true);
202
+ }
203
+ clearTimeout(this.timeout);
204
+ this.timeout = setTimeout(() => this.trigger(), this.debounceTime);
205
+ }
206
+ setViewportHeight() {
207
+ const browserWindow = this.safeWindow.value;
208
+ const height = browserWindow?.visualViewport?.height ?? browserWindow?.innerHeight ?? 0;
209
+ if (typeof document !== 'undefined') {
210
+ document.documentElement.style.setProperty('--vh', `${height}px`);
211
+ }
212
+ return height;
213
+ }
214
+ }
215
+ /**
216
+ * Shared `Resize` instance initialized once when this module is imported in a browser.
217
+ *
218
+ * @example
219
+ * ```ts
220
+ * import resize from '@lilaquadrat/frontend/resize';
221
+ * console.log(resize.realHeight);
222
+ * ```
223
+ */
224
+ const resize = new Resize();
225
+ /**
226
+ * Returns the shared reactive viewport state and its controlling singleton.
227
+ *
228
+ * Use `media`, `realHeight`, and `resized` in Vue components; use `plugin` only
229
+ * when manually triggering or configuring the shared resize observer.
230
+ *
231
+ * @example
232
+ * ```ts
233
+ * import { useResize } from '@lilaquadrat/frontend/resize';
234
+ *
235
+ * const { media, realHeight } = useResize();
236
+ * // In a Vue template: {{ media }} / {{ realHeight }}
237
+ * ```
238
+ */
239
+ export function useResize() {
240
+ return {
241
+ realHeight: realHeightReference,
242
+ media: mediaReference,
243
+ resized: resizedReference,
244
+ plugin: resize,
245
+ };
246
+ }
247
+ export { resize };
248
+ export default resize;
249
+ //# sourceMappingURL=resize.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resize.js","sourceRoot":"","sources":["../src/resize.ts"],"names":[],"mappings":"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"}