@hawsen-the-first/interactiv 0.0.1 → 0.0.3
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 +59 -0
- package/dist/index.d.ts +3 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -1
- package/dist/src/appBuilder.d.ts +42 -6
- package/dist/src/appBuilder.d.ts.map +1 -1
- package/dist/src/appBuilder.js +107 -8
- package/dist/src/eventBus.d.ts +1 -2
- package/dist/src/eventBus.d.ts.map +1 -1
- package/dist/src/eventBus.js +4 -16
- package/dist/src/navigationManager.d.ts +4 -0
- package/dist/src/navigationManager.d.ts.map +1 -1
- package/dist/src/navigationManager.js +46 -7
- package/dist/src/screensaverManager.d.ts +9 -2
- package/dist/src/screensaverManager.d.ts.map +1 -1
- package/dist/src/screensaverManager.js +98 -22
- package/dist/src/transitionOverlay.d.ts +96 -0
- package/dist/src/transitionOverlay.d.ts.map +1 -0
- package/dist/src/transitionOverlay.js +328 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -253,6 +253,65 @@ eventManager.drag(".draggable", {
|
|
|
253
253
|
});
|
|
254
254
|
```
|
|
255
255
|
|
|
256
|
+
### Screensaver Manager
|
|
257
|
+
|
|
258
|
+
The screensaver manager monitors user activity and triggers actions after a configurable timeout period. It supports two modes:
|
|
259
|
+
|
|
260
|
+
#### Standard Screensaver Mode
|
|
261
|
+
|
|
262
|
+
Navigate to a dedicated screensaver page after inactivity. User activity exits the screensaver and returns to the previous or starting page.
|
|
263
|
+
|
|
264
|
+
```typescript
|
|
265
|
+
// Create a screensaver page with views
|
|
266
|
+
const screensaverPage = new Page("screensaver-page", orchestrator, false);
|
|
267
|
+
const screensaverView = new View("screensaver-view", orchestrator, false,
|
|
268
|
+
html`<div class="screensaver">...</div>`,
|
|
269
|
+
css`.screensaver { /* styles */ }`
|
|
270
|
+
);
|
|
271
|
+
screensaverPage.addView(screensaverView);
|
|
272
|
+
|
|
273
|
+
// Add screensaver with standard behavior
|
|
274
|
+
app.addScreensaver(screensaverPage, {
|
|
275
|
+
timeoutSeconds: 30,
|
|
276
|
+
defaultViewId: "screensaver-view",
|
|
277
|
+
exitBehavior: "return", // Return to where the user was before
|
|
278
|
+
transitionConfig: { type: "fade", duration: 500 },
|
|
279
|
+
});
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
#### Return-to-Home Mode
|
|
283
|
+
|
|
284
|
+
Instead of showing a screensaver, navigate back to a specified home page/view after inactivity. No screensaver page is needed — the app simply redirects to the home screen. The screensaver never enters an "active" state, so user activity just resets the inactivity timer.
|
|
285
|
+
|
|
286
|
+
```typescript
|
|
287
|
+
// No screensaver page needed — pass null
|
|
288
|
+
app.addScreensaver(null, {
|
|
289
|
+
timeoutSeconds: 60,
|
|
290
|
+
screensaverViewBehavior: "returnHome",
|
|
291
|
+
startingPageId: "home-page", // The home page to navigate to
|
|
292
|
+
defaultViewId: "home-view", // Optional: the home view to show
|
|
293
|
+
transitionConfig: { type: "fade", duration: 500 },
|
|
294
|
+
});
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
#### Configuration Options
|
|
298
|
+
|
|
299
|
+
| Option | Type | Description |
|
|
300
|
+
|--------|------|-------------|
|
|
301
|
+
| `timeoutSeconds` | `number` | Seconds of inactivity before activation |
|
|
302
|
+
| `page` | `Page` | Screensaver page (required unless using `returnHome`) |
|
|
303
|
+
| `screensaverViewBehavior` | `"default" \| "specific" \| "return" \| "returnHome"` | How to handle the view on activation |
|
|
304
|
+
| `defaultViewId` | `string` | Default view to show (or home view for `returnHome`) |
|
|
305
|
+
| `exitBehavior` | `"reset" \| "return"` | Where to go when exiting the screensaver |
|
|
306
|
+
| `startingPageId` | `string` | Page to navigate to on exit/reset (or home page for `returnHome`) |
|
|
307
|
+
| `startingViewId` | `string` | View within the starting page on exit |
|
|
308
|
+
| `transitionConfig` | `TransitionConfig` | Transition animation configuration |
|
|
309
|
+
| `activateCallback` | `() => void` | Called when screensaver activates or returns home |
|
|
310
|
+
| `deactivateCallback` | `() => void` | Called when screensaver deactivates |
|
|
311
|
+
| `blockerCallback` | `() => boolean` | Return `true` to prevent activation |
|
|
312
|
+
| `rebootTimeout` | `number \| null` | Minutes before triggering a reboot callback |
|
|
313
|
+
| `rebootCallback` | `() => void` | Called when reboot timeout elapses |
|
|
314
|
+
|
|
256
315
|
### Settings Manager
|
|
257
316
|
|
|
258
317
|
Create hidden settings pages with corner touch activation. See [SETTINGS_MANAGER.md](./SETTINGS_MANAGER.md) for detailed documentation.
|
package/dist/index.d.ts
CHANGED
|
@@ -15,6 +15,7 @@ import { Logger } from "./utils/logger";
|
|
|
15
15
|
import { useAnimations, AnimationManager } from "./src/animationBus";
|
|
16
16
|
import { configureLogger } from "./src/logger";
|
|
17
17
|
import { GarbageCollector, createGarbageCollector, type GarbageCollectionStats } from "./src/garbageCollector";
|
|
18
|
-
|
|
19
|
-
export
|
|
18
|
+
import { TransitionOverlay, transitionWithOverlay, type TransitionOverlayConfig, type TransitionRequestPayload } from "./src/transitionOverlay";
|
|
19
|
+
export { createOrchestrator, EventOrchestrator, EventBus, AppBuilder, Page, View, Component, NavigationManager, ScreensaverManager, SettingsManager, EventManager, AnimationManager, stateManager, getGlobalState, setGlobalState, subscribeToGlobalState, useGlobalStateExternal, useAnimations, ExternalStateManager, ComponentStateManager, GarbageCollector, createGarbageCollector, TransitionOverlay, transitionWithOverlay, css, html, Logger, configureLogger, };
|
|
20
|
+
export type { PointerEventData, DragCallbacks, HoverCallbacks, SwipeCallbacks, StateSubscription, ScreensaverConfig, SettingsConfig, PageProps, ViewProps, ComponentProps, GarbageCollectionStats, TransitionOverlayConfig, TransitionRequestPayload, };
|
|
20
21
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,IAAI,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AACrE,OAAO,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AACjF,OAAO,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;AAC5D,OAAO,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;AAC9D,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AACxD,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,EACL,YAAY,EACZ,cAAc,EACd,cAAc,EACd,sBAAsB,EACtB,sBAAsB,EACtB,oBAAoB,EACpB,qBAAqB,EACtB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,KAAK,EACV,gBAAgB,EAChB,aAAa,EACb,cAAc,EACd,cAAc,EACf,MAAM,oBAAoB,CAAC;AAC5B,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AAC5D,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAC;AAClE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAC5D,OAAO,KAAK,EAAE,SAAS,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACxE,OAAO,EAAE,GAAG,EAAE,IAAI,EAAE,MAAM,0BAA0B,CAAC;AACrD,OAAO,EAAE,MAAM,EAAE,MAAM,gBAAgB,CAAC;AACxC,OAAO,EAAE,aAAa,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AACrE,OAAO,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAC/C,OAAO,EACL,gBAAgB,EAChB,sBAAsB,EACtB,KAAK,sBAAsB,EAC5B,MAAM,wBAAwB,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,IAAI,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AACrE,OAAO,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AACjF,OAAO,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;AAC5D,OAAO,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;AAC9D,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AACxD,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,EACL,YAAY,EACZ,cAAc,EACd,cAAc,EACd,sBAAsB,EACtB,sBAAsB,EACtB,oBAAoB,EACpB,qBAAqB,EACtB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,KAAK,EACV,gBAAgB,EAChB,aAAa,EACb,cAAc,EACd,cAAc,EACf,MAAM,oBAAoB,CAAC;AAC5B,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AAC5D,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAC;AAClE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAC5D,OAAO,KAAK,EAAE,SAAS,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACxE,OAAO,EAAE,GAAG,EAAE,IAAI,EAAE,MAAM,0BAA0B,CAAC;AACrD,OAAO,EAAE,MAAM,EAAE,MAAM,gBAAgB,CAAC;AACxC,OAAO,EAAE,aAAa,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AACrE,OAAO,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAC/C,OAAO,EACL,gBAAgB,EAChB,sBAAsB,EACtB,KAAK,sBAAsB,EAC5B,MAAM,wBAAwB,CAAC;AAChC,OAAO,EACL,iBAAiB,EACjB,qBAAqB,EACrB,KAAK,uBAAuB,EAC5B,KAAK,wBAAwB,EAC9B,MAAM,yBAAyB,CAAC;AAEjC,OAAO,EACL,kBAAkB,EAClB,iBAAiB,EACjB,QAAQ,EACR,UAAU,EACV,IAAI,EACJ,IAAI,EACJ,SAAS,EACT,iBAAiB,EACjB,kBAAkB,EAClB,eAAe,EACf,YAAY,EACZ,gBAAgB,EAEhB,YAAY,EACZ,cAAc,EACd,cAAc,EACd,sBAAsB,EACtB,sBAAsB,EACtB,aAAa,EACb,oBAAoB,EACpB,qBAAqB,EAErB,gBAAgB,EAChB,sBAAsB,EAEtB,iBAAiB,EACjB,qBAAqB,EACrB,GAAG,EACH,IAAI,EACJ,MAAM,EACN,eAAe,GAChB,CAAC;AAEF,YAAY,EACV,gBAAgB,EAChB,aAAa,EACb,cAAc,EACd,cAAc,EACd,iBAAiB,EACjB,iBAAiB,EACjB,cAAc,EACd,SAAS,EACT,SAAS,EACT,cAAc,EACd,sBAAsB,EACtB,uBAAuB,EACvB,wBAAwB,GACzB,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -10,11 +10,15 @@ import { Logger } from "./utils/logger";
|
|
|
10
10
|
import { useAnimations, AnimationManager } from "./src/animationBus";
|
|
11
11
|
import { configureLogger } from "./src/logger";
|
|
12
12
|
import { GarbageCollector, createGarbageCollector, } from "./src/garbageCollector";
|
|
13
|
+
import { TransitionOverlay, transitionWithOverlay, } from "./src/transitionOverlay";
|
|
14
|
+
5;
|
|
13
15
|
export { createOrchestrator, EventOrchestrator, EventBus, AppBuilder, Page, View, Component, NavigationManager, ScreensaverManager, SettingsManager, EventManager, AnimationManager,
|
|
14
16
|
// External State Management
|
|
15
17
|
stateManager, getGlobalState, setGlobalState, subscribeToGlobalState, useGlobalStateExternal, useAnimations, ExternalStateManager, ComponentStateManager,
|
|
16
18
|
// Garbage Collection
|
|
17
|
-
GarbageCollector, createGarbageCollector,
|
|
19
|
+
GarbageCollector, createGarbageCollector,
|
|
20
|
+
// Transition Overlay System
|
|
21
|
+
TransitionOverlay, transitionWithOverlay, css, html, Logger, configureLogger, };
|
|
18
22
|
// Named exports for direct import
|
|
19
23
|
// export {
|
|
20
24
|
// stateManager,
|
package/dist/src/appBuilder.d.ts
CHANGED
|
@@ -5,6 +5,7 @@ import { EventManager } from "./eventManager";
|
|
|
5
5
|
import { ComponentStateManager } from "./stateManager";
|
|
6
6
|
import type { PointerEventData, DragCallbacks, HoverCallbacks, SwipeCallbacks } from "./eventManager";
|
|
7
7
|
import type { RenderOptions } from "./types";
|
|
8
|
+
import { TransitionOverlay, type TransitionOverlayConfig } from "./transitionOverlay";
|
|
8
9
|
declare abstract class RenderableComponent {
|
|
9
10
|
componentId: string;
|
|
10
11
|
state: any;
|
|
@@ -128,26 +129,61 @@ declare class AppBuilder extends RenderableComponent {
|
|
|
128
129
|
isTransitioning(): boolean;
|
|
129
130
|
/**
|
|
130
131
|
* Add a screensaver page with the specified configuration
|
|
131
|
-
* The screensaver page can contain multiple views for rich interactive experiences
|
|
132
|
-
*
|
|
132
|
+
* The screensaver page can contain multiple views for rich interactive experiences.
|
|
133
|
+
* Pass `null` for screensaverPage when using `screensaverViewBehavior: "returnHome"` mode,
|
|
134
|
+
* which navigates back to a home page/view on timeout instead of showing a screensaver.
|
|
135
|
+
* @param screensaverPage The page to use as the screensaver (can contain multiple views), or null for returnHome mode
|
|
133
136
|
* @param config Configuration object for the screensaver (excluding 'page' property)
|
|
134
137
|
*/
|
|
135
|
-
addScreensaver(screensaverPage: Page, config: Omit<ScreensaverConfig, 'page'>): void;
|
|
138
|
+
addScreensaver(screensaverPage: Page | null, config: Omit<ScreensaverConfig, 'page'>): void;
|
|
136
139
|
attachToDom(): void;
|
|
137
140
|
}
|
|
138
141
|
declare class Page extends RenderableComponent {
|
|
139
142
|
private views;
|
|
143
|
+
private transitionOverlay;
|
|
144
|
+
private transitionBus;
|
|
140
145
|
constructor(id: string, orchestrator: EventOrchestrator, bubbleChanges?: boolean, customTemplate?: string, customStyles?: string);
|
|
141
146
|
protected defineTemplate(): void;
|
|
142
147
|
protected defineStyles(): void;
|
|
143
148
|
addView(view: View): void;
|
|
144
149
|
removeView(viewId: string): void;
|
|
145
150
|
/**
|
|
146
|
-
*
|
|
147
|
-
*
|
|
151
|
+
* Enable transition overlay for this page
|
|
152
|
+
*
|
|
153
|
+
* Creates a full-screen overlay that fades in/out to hide content changes during state updates.
|
|
154
|
+
* Once enabled, components can request transitions via the "page-transition-overlay" event bus.
|
|
155
|
+
*
|
|
156
|
+
* @param config - Configuration for the overlay appearance and timing
|
|
157
|
+
*/
|
|
158
|
+
useTransitionOverlay(config: TransitionOverlayConfig): void;
|
|
159
|
+
/**
|
|
160
|
+
* Execute a callback wrapped in the transition overlay lifecycle
|
|
161
|
+
*
|
|
162
|
+
* This is a convenience method for direct Page-level access to the overlay.
|
|
163
|
+
* For deeply nested components, prefer using the standalone `transitionWithOverlay` utility
|
|
164
|
+
* or emitting to the "page-transition-overlay" event bus.
|
|
165
|
+
*
|
|
166
|
+
* @param callback - Function to execute while overlay is opaque (state changes happen here)
|
|
167
|
+
* @returns Promise that resolves after the full transition completes
|
|
168
|
+
* @throws Error if useTransitionOverlay() hasn't been called
|
|
169
|
+
*/
|
|
170
|
+
transitionWithOverlay(callback: () => void | Promise<void>): Promise<void>;
|
|
171
|
+
/**
|
|
172
|
+
* Check if this page has a transition overlay enabled
|
|
173
|
+
*/
|
|
174
|
+
hasTransitionOverlay(): boolean;
|
|
175
|
+
/**
|
|
176
|
+
* Get the TransitionOverlay instance for this page (for NavigationManager integration)
|
|
177
|
+
*/
|
|
178
|
+
getTransitionOverlay(): TransitionOverlay | null;
|
|
179
|
+
/**
|
|
180
|
+
* Add a screensaver page with the specified configuration.
|
|
181
|
+
* Pass `null` for screensaverPage when using `screensaverViewBehavior: "returnHome"` mode,
|
|
182
|
+
* which navigates back to a home page/view on timeout instead of showing a screensaver.
|
|
183
|
+
* @param screensaverPage The page to use as the screensaver (can contain multiple views), or null for returnHome mode
|
|
148
184
|
* @param config Configuration object for the screensaver (excluding 'page' property)
|
|
149
185
|
*/
|
|
150
|
-
addScreensaver(screensaverPage: Page, config: Omit<ScreensaverConfig, 'page'>): void;
|
|
186
|
+
addScreensaver(screensaverPage: Page | null, config: Omit<ScreensaverConfig, 'page'>): void;
|
|
151
187
|
/**
|
|
152
188
|
* Add a hidden settings page to this page with the specified configuration
|
|
153
189
|
* The settings page is activated by touching corners in sequence: top-left, top-right, bottom-right
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"appBuilder.d.ts","sourceRoot":"","sources":["../../src/appBuilder.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,QAAQ,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAEzD,OAAO,EAEL,KAAK,iBAAiB,EACvB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAAmB,KAAK,cAAc,EAAE,MAAM,mBAAmB,CAAC;AACzE,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAC9C,OAAO,EAAE,qBAAqB,EAAE,MAAM,gBAAgB,CAAC;AACvD,OAAO,KAAK,EACV,gBAAgB,EAChB,aAAa,EACb,cAAc,EACd,cAAc,EACf,MAAM,gBAAgB,CAAC;AACxB,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"appBuilder.d.ts","sourceRoot":"","sources":["../../src/appBuilder.ts"],"names":[],"mappings":"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"}
|
package/dist/src/appBuilder.js
CHANGED
|
@@ -6,6 +6,7 @@ import { SettingsManager } from "./settingsManager";
|
|
|
6
6
|
import { EventManager } from "./eventManager";
|
|
7
7
|
import { ComponentStateManager } from "./stateManager";
|
|
8
8
|
import { logger } from "./logger";
|
|
9
|
+
import { TransitionOverlay, stateChangesToCallback, } from "./transitionOverlay";
|
|
9
10
|
class RenderableComponent {
|
|
10
11
|
componentId;
|
|
11
12
|
state;
|
|
@@ -499,20 +500,24 @@ class AppBuilder extends RenderableComponent {
|
|
|
499
500
|
}
|
|
500
501
|
/**
|
|
501
502
|
* Add a screensaver page with the specified configuration
|
|
502
|
-
* The screensaver page can contain multiple views for rich interactive experiences
|
|
503
|
-
*
|
|
503
|
+
* The screensaver page can contain multiple views for rich interactive experiences.
|
|
504
|
+
* Pass `null` for screensaverPage when using `screensaverViewBehavior: "returnHome"` mode,
|
|
505
|
+
* which navigates back to a home page/view on timeout instead of showing a screensaver.
|
|
506
|
+
* @param screensaverPage The page to use as the screensaver (can contain multiple views), or null for returnHome mode
|
|
504
507
|
* @param config Configuration object for the screensaver (excluding 'page' property)
|
|
505
508
|
*/
|
|
506
509
|
addScreensaver(screensaverPage, config) {
|
|
507
|
-
// Add the screensaver page to the app
|
|
508
|
-
|
|
510
|
+
// Add the screensaver page to the app (only if provided)
|
|
511
|
+
if (screensaverPage) {
|
|
512
|
+
this.addPage(screensaverPage);
|
|
513
|
+
}
|
|
509
514
|
// Register the screensaver with the screensaver manager via event bus
|
|
510
515
|
const screensaverBus = this.orchestrator.getEventBus("screensaver-manager");
|
|
511
516
|
if (screensaverBus) {
|
|
512
517
|
screensaverBus.emit("register-screensaver", {
|
|
513
518
|
config: {
|
|
514
519
|
...config,
|
|
515
|
-
page: screensaverPage,
|
|
520
|
+
page: screensaverPage || undefined,
|
|
516
521
|
},
|
|
517
522
|
});
|
|
518
523
|
}
|
|
@@ -534,6 +539,8 @@ class AppBuilder extends RenderableComponent {
|
|
|
534
539
|
// Page - Contains Views
|
|
535
540
|
class Page extends RenderableComponent {
|
|
536
541
|
views = [];
|
|
542
|
+
transitionOverlay = null;
|
|
543
|
+
transitionBus = null;
|
|
537
544
|
constructor(id, orchestrator, bubbleChanges = true, customTemplate, customStyles) {
|
|
538
545
|
super(id, orchestrator, bubbleChanges);
|
|
539
546
|
// Set custom template/styles before initial render if provided
|
|
@@ -644,8 +651,100 @@ class Page extends RenderableComponent {
|
|
|
644
651
|
}
|
|
645
652
|
}
|
|
646
653
|
/**
|
|
647
|
-
*
|
|
648
|
-
*
|
|
654
|
+
* Enable transition overlay for this page
|
|
655
|
+
*
|
|
656
|
+
* Creates a full-screen overlay that fades in/out to hide content changes during state updates.
|
|
657
|
+
* Once enabled, components can request transitions via the "page-transition-overlay" event bus.
|
|
658
|
+
*
|
|
659
|
+
* @param config - Configuration for the overlay appearance and timing
|
|
660
|
+
*/
|
|
661
|
+
useTransitionOverlay(config) {
|
|
662
|
+
if (this.transitionOverlay) {
|
|
663
|
+
logger.warn(`Page ${this.componentId} already has a transition overlay configured`);
|
|
664
|
+
return;
|
|
665
|
+
}
|
|
666
|
+
// Create the transition overlay
|
|
667
|
+
this.transitionOverlay = new TransitionOverlay(config, this.shadowRoot);
|
|
668
|
+
// Register the event bus for transition requests
|
|
669
|
+
this.transitionBus = this.orchestrator.registerEventBus("page-transition-overlay");
|
|
670
|
+
// Listen for transition requests
|
|
671
|
+
this.transitionBus.on("request-transition", async (e) => {
|
|
672
|
+
const payload = e.detail;
|
|
673
|
+
try {
|
|
674
|
+
// Build the callback from state changes or use provided callback
|
|
675
|
+
let callback;
|
|
676
|
+
if (payload.callback) {
|
|
677
|
+
// Direct callback provided (from standalone utility)
|
|
678
|
+
callback = payload.callback;
|
|
679
|
+
}
|
|
680
|
+
else if (payload.stateChanges) {
|
|
681
|
+
// State changes array provided (from consumer usage)
|
|
682
|
+
callback = async () => {
|
|
683
|
+
stateChangesToCallback(payload.stateChanges)();
|
|
684
|
+
// Execute afterStateChange if provided
|
|
685
|
+
if (payload.afterStateChange) {
|
|
686
|
+
await Promise.resolve(payload.afterStateChange());
|
|
687
|
+
}
|
|
688
|
+
};
|
|
689
|
+
}
|
|
690
|
+
else {
|
|
691
|
+
throw new Error("Transition request must include either 'callback' or 'stateChanges'");
|
|
692
|
+
}
|
|
693
|
+
// Execute the transition with config overrides
|
|
694
|
+
await this.transitionOverlay.executeTransition(callback, {
|
|
695
|
+
fadeInDuration: payload.fadeInDuration,
|
|
696
|
+
holdDuration: payload.holdDuration,
|
|
697
|
+
fadeOutDuration: payload.fadeOutDuration,
|
|
698
|
+
});
|
|
699
|
+
// Resolve the promise if provided (from standalone utility)
|
|
700
|
+
if (payload._resolve) {
|
|
701
|
+
payload._resolve();
|
|
702
|
+
}
|
|
703
|
+
}
|
|
704
|
+
catch (error) {
|
|
705
|
+
logger.error("Transition overlay error:", error);
|
|
706
|
+
// Reject the promise if provided (from standalone utility)
|
|
707
|
+
if (payload._reject) {
|
|
708
|
+
payload._reject(error);
|
|
709
|
+
}
|
|
710
|
+
}
|
|
711
|
+
});
|
|
712
|
+
logger.trace(`Transition overlay enabled for page ${this.componentId}`);
|
|
713
|
+
}
|
|
714
|
+
/**
|
|
715
|
+
* Execute a callback wrapped in the transition overlay lifecycle
|
|
716
|
+
*
|
|
717
|
+
* This is a convenience method for direct Page-level access to the overlay.
|
|
718
|
+
* For deeply nested components, prefer using the standalone `transitionWithOverlay` utility
|
|
719
|
+
* or emitting to the "page-transition-overlay" event bus.
|
|
720
|
+
*
|
|
721
|
+
* @param callback - Function to execute while overlay is opaque (state changes happen here)
|
|
722
|
+
* @returns Promise that resolves after the full transition completes
|
|
723
|
+
* @throws Error if useTransitionOverlay() hasn't been called
|
|
724
|
+
*/
|
|
725
|
+
async transitionWithOverlay(callback) {
|
|
726
|
+
if (!this.transitionOverlay) {
|
|
727
|
+
throw new Error(`Page ${this.componentId} does not have a transition overlay. Call useTransitionOverlay() first.`);
|
|
728
|
+
}
|
|
729
|
+
return this.transitionOverlay.executeTransition(callback);
|
|
730
|
+
}
|
|
731
|
+
/**
|
|
732
|
+
* Check if this page has a transition overlay enabled
|
|
733
|
+
*/
|
|
734
|
+
hasTransitionOverlay() {
|
|
735
|
+
return this.transitionOverlay !== null;
|
|
736
|
+
}
|
|
737
|
+
/**
|
|
738
|
+
* Get the TransitionOverlay instance for this page (for NavigationManager integration)
|
|
739
|
+
*/
|
|
740
|
+
getTransitionOverlay() {
|
|
741
|
+
return this.transitionOverlay;
|
|
742
|
+
}
|
|
743
|
+
/**
|
|
744
|
+
* Add a screensaver page with the specified configuration.
|
|
745
|
+
* Pass `null` for screensaverPage when using `screensaverViewBehavior: "returnHome"` mode,
|
|
746
|
+
* which navigates back to a home page/view on timeout instead of showing a screensaver.
|
|
747
|
+
* @param screensaverPage The page to use as the screensaver (can contain multiple views), or null for returnHome mode
|
|
649
748
|
* @param config Configuration object for the screensaver (excluding 'page' property)
|
|
650
749
|
*/
|
|
651
750
|
addScreensaver(screensaverPage, config) {
|
|
@@ -655,7 +754,7 @@ class Page extends RenderableComponent {
|
|
|
655
754
|
screensaverBus.emit("register-screensaver", {
|
|
656
755
|
config: {
|
|
657
756
|
...config,
|
|
658
|
-
page: screensaverPage,
|
|
757
|
+
page: screensaverPage || undefined,
|
|
659
758
|
},
|
|
660
759
|
});
|
|
661
760
|
}
|
package/dist/src/eventBus.d.ts
CHANGED
|
@@ -18,8 +18,7 @@ export declare class EventBus<DetailType = any> {
|
|
|
18
18
|
* Destroy this event bus and clean up all listeners
|
|
19
19
|
*/
|
|
20
20
|
destroy(): void;
|
|
21
|
-
emit(type: string, detail?: DetailType): boolean
|
|
22
|
-
private validateEventDispatch;
|
|
21
|
+
emit(type: string, detail?: DetailType): boolean;
|
|
23
22
|
}
|
|
24
23
|
export declare class EventOrchestrator {
|
|
25
24
|
private eventBuses;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"eventBus.d.ts","sourceRoot":"","sources":["../../src/eventBus.ts"],"names":[],"mappings":"AAMA,qBAAa,QAAQ,CAAC,UAAU,GAAG,GAAG;IACpC,OAAO,CAAC,WAAW,CAAc;IACjC,OAAO,CAAC,eAAe,CAA6B;IACpD,OAAO,CAAC,EAAE,CAAS;gBACP,WAAW,SAAc;IAIrC,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC,KAAK,EAAE,WAAW,CAAC,UAAU,CAAC,KAAK,IAAI;IAiBnE,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC,KAAK,EAAE,WAAW,CAAC,UAAU,CAAC,KAAK,IAAI;IAiBrE,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI;IAmB1B;;OAEG;IACH,SAAS,IAAI,IAAI;IAQjB;;OAEG;IACH,gBAAgB,IAAI,MAAM;IAI1B;;OAEG;IACH,OAAO,IAAI,IAAI;IASf,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,UAAU;
|
|
1
|
+
{"version":3,"file":"eventBus.d.ts","sourceRoot":"","sources":["../../src/eventBus.ts"],"names":[],"mappings":"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"}
|
package/dist/src/eventBus.js
CHANGED
|
@@ -86,24 +86,12 @@ export class EventBus {
|
|
|
86
86
|
log.trace(`Event Bus ${this.id} destroyed`);
|
|
87
87
|
}
|
|
88
88
|
emit(type, detail) {
|
|
89
|
-
|
|
90
|
-
this.validateEventDispatch(type);
|
|
91
|
-
return this.eventTarget.dispatchEvent(new CustomEvent(type, { detail }));
|
|
92
|
-
}
|
|
93
|
-
catch (error) {
|
|
94
|
-
log.error(error.message, error);
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
// private validateNewListener(type: string): void {
|
|
98
|
-
// // Allow multiple listeners for the same event type
|
|
99
|
-
// // This validation is too restrictive for our use case
|
|
100
|
-
// // Components may need to register multiple listeners for the same event
|
|
101
|
-
// return;
|
|
102
|
-
// }
|
|
103
|
-
validateEventDispatch(type) {
|
|
89
|
+
// Check if there are any listeners before dispatching
|
|
104
90
|
if (this.activeListeners.filter((l) => l.eventName === type).length === 0) {
|
|
105
|
-
|
|
91
|
+
log.trace(`Event Bus [${this.id}]: No listeners registered for event "${type}", skipping dispatch.`);
|
|
92
|
+
return true; // Return true to indicate the event was "handled" (just not dispatched)
|
|
106
93
|
}
|
|
94
|
+
return this.eventTarget.dispatchEvent(new CustomEvent(type, { detail }));
|
|
107
95
|
}
|
|
108
96
|
}
|
|
109
97
|
export class EventOrchestrator {
|
|
@@ -66,6 +66,10 @@ export declare class NavigationManager {
|
|
|
66
66
|
private performPageNavigationInternal;
|
|
67
67
|
private performViewNavigationInternal;
|
|
68
68
|
private performPageTransition;
|
|
69
|
+
/**
|
|
70
|
+
* Perform an immediate view swap (no animation) - used when transition overlay is handling the fade
|
|
71
|
+
*/
|
|
72
|
+
private performViewSwapImmediate;
|
|
69
73
|
private performViewTransition;
|
|
70
74
|
private animateOut;
|
|
71
75
|
private animateIn;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"navigationManager.d.ts","sourceRoot":"","sources":["../../src/navigationManager.ts"],"names":[],"mappings":"AAAA,OAAO,EAAY,iBAAiB,EAAE,MAAM,YAAY,CAAC;AACzD,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,cAAc,CAAC;AAC1C,OAAO,EAAgB,KAAK,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AAKtE,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,OAAO,GAAG,MAAM,GAAG,OAAO,GAAG,MAAM,GAAG,MAAM,GAAG,QAAQ,CAAC;IAC9D,SAAS,CAAC,EAAE,MAAM,GAAG,OAAO,GAAG,IAAI,GAAG,MAAM,CAAC;IAC7C,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,eAAe;IAC9B,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,eAAe,EAAE,OAAO,CAAC;CAC1B;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,qBAAa,iBAAiB;IAC5B,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAkC;IACzD,OAAO,CAAC,MAAM,CAAC,WAAW,CAAkB;IAE5C,OAAO,CAAC,QAAQ,CAAW;IAC3B,OAAO,CAAC,YAAY,CAAoB;IACxC,OAAO,CAAC,KAAK,CAAgC;IAC7C,OAAO,CAAC,KAAK,CAAgC;IAC7C,OAAO,CAAC,kBAAkB,CAA2B;IACrD,OAAO,CAAC,wBAAwB,CAA2C;IAE3E;;;OAGG;WACW,WAAW,IAAI,iBAAiB,GAAG,IAAI;gBAIzC,YAAY,EAAE,iBAAiB;IA4B3C,OAAO,CAAC,qBAAqB;IAa7B,OAAO,CAAC,mBAAmB;IAsBpB,YAAY,CAAC,IAAI,EAAE,IAAI,GAAG,IAAI;IAa9B,YAAY,CAAC,IAAI,EAAE,IAAI,GAAG,IAAI;IAQrC,OAAO,CAAC,kBAAkB;IAM1B,OAAO,CAAC,kBAAkB;IAMb,cAAc,CACzB,MAAM,EAAE,MAAM,EACd,MAAM,GAAE,gBAAmC,EAC3C,QAAQ,GAAE,MAAoB,GAC7B,OAAO,CAAC,IAAI,CAAC;IAYH,cAAc,CACzB,MAAM,EAAE,MAAM,EACd,MAAM,GAAE,gBAAmC,EAC3C,QAAQ,GAAE,MAAoB,GAC7B,OAAO,CAAC,IAAI,CAAC;YAYF,6BAA6B;YA6B7B,6BAA6B;
|
|
1
|
+
{"version":3,"file":"navigationManager.d.ts","sourceRoot":"","sources":["../../src/navigationManager.ts"],"names":[],"mappings":"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"}
|
|
@@ -181,13 +181,35 @@ export class NavigationManager {
|
|
|
181
181
|
log.trace(`Starting navigation from ${previousViewId} to ${viewId}`);
|
|
182
182
|
stateManager.set("navigation.isTransitioning", true);
|
|
183
183
|
try {
|
|
184
|
-
|
|
185
|
-
stateManager.
|
|
186
|
-
this.
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
184
|
+
// Check if current page has a transition overlay
|
|
185
|
+
const currentPageId = stateManager.get("navigation.currentPageId");
|
|
186
|
+
const currentPage = currentPageId ? this.pages.get(currentPageId) : null;
|
|
187
|
+
const hasOverlay = currentPage && currentPage.hasTransitionOverlay();
|
|
188
|
+
if (hasOverlay) {
|
|
189
|
+
// Use the overlay to wrap the view transition
|
|
190
|
+
log.trace(`Using transition overlay for view navigation to ${viewId}`);
|
|
191
|
+
const overlay = currentPage.getTransitionOverlay();
|
|
192
|
+
await overlay.executeTransition(async () => {
|
|
193
|
+
// Perform the view swap while overlay is opaque
|
|
194
|
+
await this.performViewSwapImmediate(viewId, previousViewId);
|
|
195
|
+
stateManager.set("navigation.currentViewId", viewId);
|
|
196
|
+
});
|
|
197
|
+
this.eventBus.emit("view-changed", {
|
|
198
|
+
newViewId: viewId,
|
|
199
|
+
previousViewId,
|
|
200
|
+
});
|
|
201
|
+
log.trace(`Navigation to ${viewId} completed successfully with overlay`);
|
|
202
|
+
}
|
|
203
|
+
else {
|
|
204
|
+
// No overlay - use standard transition
|
|
205
|
+
await this.performViewTransition(viewId, config);
|
|
206
|
+
stateManager.set("navigation.currentViewId", viewId);
|
|
207
|
+
this.eventBus.emit("view-changed", {
|
|
208
|
+
newViewId: viewId,
|
|
209
|
+
previousViewId,
|
|
210
|
+
});
|
|
211
|
+
log.trace(`Navigation to ${viewId} completed successfully`);
|
|
212
|
+
}
|
|
191
213
|
}
|
|
192
214
|
catch (error) {
|
|
193
215
|
log.error(`Navigation to ${viewId} failed:`, error);
|
|
@@ -211,6 +233,23 @@ export class NavigationManager {
|
|
|
211
233
|
}
|
|
212
234
|
await this.animateIn(targetPage.getHostElement(), normalizedConfig);
|
|
213
235
|
}
|
|
236
|
+
/**
|
|
237
|
+
* Perform an immediate view swap (no animation) - used when transition overlay is handling the fade
|
|
238
|
+
*/
|
|
239
|
+
async performViewSwapImmediate(targetViewId, currentViewId) {
|
|
240
|
+
const currentView = currentViewId ? this.views.get(currentViewId) : null;
|
|
241
|
+
const targetView = this.views.get(targetViewId);
|
|
242
|
+
// Hide current view immediately
|
|
243
|
+
if (currentView) {
|
|
244
|
+
this.hideView(currentViewId);
|
|
245
|
+
}
|
|
246
|
+
// Show target view immediately (no animation since overlay handles the fade)
|
|
247
|
+
this.showView(targetViewId, false);
|
|
248
|
+
const element = targetView.getHostElement();
|
|
249
|
+
element.style.opacity = "1";
|
|
250
|
+
element.style.visibility = "visible";
|
|
251
|
+
element.style.transform = "none";
|
|
252
|
+
}
|
|
214
253
|
async performViewTransition(targetViewId, config) {
|
|
215
254
|
const currentViewId = stateManager.get("navigation.currentViewId");
|
|
216
255
|
const currentView = currentViewId ? this.views.get(currentViewId) : null;
|
|
@@ -3,9 +3,9 @@ import { Page } from "./appBuilder";
|
|
|
3
3
|
import { NavigationManager, type TransitionConfig } from "./navigationManager";
|
|
4
4
|
export interface ScreensaverConfig {
|
|
5
5
|
timeoutSeconds: number;
|
|
6
|
-
page
|
|
6
|
+
page?: Page;
|
|
7
7
|
defaultViewId?: string;
|
|
8
|
-
screensaverViewBehavior?: "default" | "specific" | "return";
|
|
8
|
+
screensaverViewBehavior?: "default" | "specific" | "return" | "returnHome";
|
|
9
9
|
specificViewId?: string;
|
|
10
10
|
transitionConfig?: TransitionConfig;
|
|
11
11
|
exitBehavior?: "reset" | "return";
|
|
@@ -38,6 +38,7 @@ export declare class ScreensaverManager {
|
|
|
38
38
|
private initializeGlobalState;
|
|
39
39
|
private setupEventListeners;
|
|
40
40
|
registerScreensaver(config: ScreensaverConfig): void;
|
|
41
|
+
private isReturnHomeMode;
|
|
41
42
|
private validateConfig;
|
|
42
43
|
private setupGlobalActivityListeners;
|
|
43
44
|
private shouldIgnoreActivity;
|
|
@@ -45,6 +46,12 @@ export declare class ScreensaverManager {
|
|
|
45
46
|
private pauseActivityTimer;
|
|
46
47
|
private clearActivityTimer;
|
|
47
48
|
private activateScreensaver;
|
|
49
|
+
/**
|
|
50
|
+
* Return-to-home activation: navigates to the configured home page/view
|
|
51
|
+
* without entering "screensaver active" state. The timer resets immediately
|
|
52
|
+
* for the next inactivity cycle.
|
|
53
|
+
*/
|
|
54
|
+
private activateReturnHome;
|
|
48
55
|
private determineScreensaverView;
|
|
49
56
|
private handleScreensaverExit;
|
|
50
57
|
private deactivateScreensaver;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"screensaverManager.d.ts","sourceRoot":"","sources":["../../src/screensaverManager.ts"],"names":[],"mappings":"AAAA,OAAO,EAAY,iBAAiB,EAAE,MAAM,YAAY,CAAC;AACzD,OAAO,EAAE,IAAI,EAAE,MAAM,cAAc,CAAC;AACpC,OAAO,EAAE,iBAAiB,EAAE,KAAK,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAM/E,MAAM,WAAW,iBAAiB;IAChC,cAAc,EAAE,MAAM,CAAC;IACvB,IAAI,EAAE,IAAI,CAAC;
|
|
1
|
+
{"version":3,"file":"screensaverManager.d.ts","sourceRoot":"","sources":["../../src/screensaverManager.ts"],"names":[],"mappings":"AAAA,OAAO,EAAY,iBAAiB,EAAE,MAAM,YAAY,CAAC;AACzD,OAAO,EAAE,IAAI,EAAE,MAAM,cAAc,CAAC;AACpC,OAAO,EAAE,iBAAiB,EAAE,KAAK,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAM/E,MAAM,WAAW,iBAAiB;IAChC,cAAc,EAAE,MAAM,CAAC;IACvB,IAAI,CAAC,EAAE,IAAI,CAAC;IACZ,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,uBAAuB,CAAC,EAAE,SAAS,GAAG,UAAU,GAAG,QAAQ,GAAG,YAAY,CAAC;IAC3E,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,gBAAgB,CAAC,EAAE,gBAAgB,CAAC;IACpC,YAAY,CAAC,EAAE,OAAO,GAAG,QAAQ,CAAC;IAClC,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;IAC1B,gBAAgB,CAAC,EAAE,MAAM,EAAE,CAAC;IAC5B,gBAAgB,CAAC,EAAE,MAAM,IAAI,CAAC;IAC9B,kBAAkB,CAAC,EAAE,MAAM,IAAI,CAAC;IAChC,eAAe,CAAC,EAAE,MAAM,OAAO,CAAC;IAChC,aAAa,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,cAAc,CAAC,EAAE,MAAM,IAAI,CAAC;CAC7B;AAED,qBAAa,kBAAkB;IAC7B,OAAO,CAAC,QAAQ,CAAW;IAC3B,OAAO,CAAC,YAAY,CAAoB;IACxC,OAAO,CAAC,iBAAiB,CAAoB;IAC7C,OAAO,CAAC,MAAM,CAAkC;IAChD,OAAO,CAAC,aAAa,CAAuB;IAC5C,OAAO,CAAC,mBAAmB,CAAuB;IAClD,OAAO,CAAC,mBAAmB,CAAkB;IAC7C,OAAO,CAAC,gBAAgB,CAAuB;IAC/C,OAAO,CAAC,gBAAgB,CAAuB;IAC/C,OAAO,CAAC,qBAAqB,CAAuB;IACpD,OAAO,CAAC,qBAAqB,CAAuB;IACpD,OAAO,CAAC,eAAe,CAIf;IAGR,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAAQ;IAG1C,OAAO,CAAC,QAAQ,CAAC,uBAAuB,CAStC;gBAEU,YAAY,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,iBAAiB;IASjF,OAAO,CAAC,qBAAqB;IAkB7B,OAAO,CAAC,mBAAmB;IAyCpB,mBAAmB,CAAC,MAAM,EAAE,iBAAiB,GAAG,IAAI;IAyC3D,OAAO,CAAC,gBAAgB;IAIxB,OAAO,CAAC,cAAc;IAkCtB,OAAO,CAAC,4BAA4B;IAmDpC,OAAO,CAAC,oBAAoB;IAgB5B,OAAO,CAAC,kBAAkB;IAe1B,OAAO,CAAC,kBAAkB;IAO1B,OAAO,CAAC,kBAAkB;YAOZ,mBAAmB;IA6DjC;;;;OAIG;YACW,kBAAkB;IAkDhC,OAAO,CAAC,wBAAwB;YAclB,qBAAqB;YAmDrB,qBAAqB;IAI5B,QAAQ,IAAI,OAAO;IAInB,gBAAgB,IAAI,iBAAiB,GAAG,IAAI;IAI5C,mBAAmB,IAAI,MAAM,GAAG,IAAI;IAIpC,mBAAmB,IAAI,MAAM,GAAG,IAAI;IAIpC,wBAAwB,IAAI,MAAM,GAAG,IAAI;IAKzC,aAAa,IAAI,IAAI;IAKrB,eAAe,IAAI,IAAI;IAIvB,UAAU,IAAI,IAAI;IAMzB,OAAO,CAAC,OAAO;IAcR,OAAO,IAAI,IAAI;IAUtB,OAAO,CAAC,uBAAuB;IAc/B,OAAO,CAAC,qBAAqB;IAc7B,OAAO,CAAC,wBAAwB;IAchC,OAAO,CAAC,uBAAuB;CAOhC"}
|
|
@@ -67,7 +67,8 @@ export class ScreensaverManager {
|
|
|
67
67
|
if (navBus) {
|
|
68
68
|
navBus.on("page-changed", (e) => {
|
|
69
69
|
const { newPageId } = e.detail;
|
|
70
|
-
|
|
70
|
+
const screensaverPageId = this.config?.page?.componentId;
|
|
71
|
+
if (!this.isScreensaverActive && (!screensaverPageId || newPageId !== screensaverPageId)) {
|
|
71
72
|
this.lastActivePageId = newPageId;
|
|
72
73
|
stateManager.set("screensaver.lastActivePageId", newPageId);
|
|
73
74
|
}
|
|
@@ -103,33 +104,52 @@ export class ScreensaverManager {
|
|
|
103
104
|
type: "snap",
|
|
104
105
|
},
|
|
105
106
|
};
|
|
106
|
-
// Register the screensaver page with navigation manager
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
navBus
|
|
107
|
+
// Register the screensaver page with navigation manager (only in screensaver mode)
|
|
108
|
+
if (!this.isReturnHomeMode() && this.config.page) {
|
|
109
|
+
const navBus = this.orchestrator.getEventBus("navigation-manager");
|
|
110
|
+
if (navBus) {
|
|
111
|
+
navBus.emit("register-page", { page: this.config.page });
|
|
112
|
+
}
|
|
110
113
|
}
|
|
111
114
|
this.setupGlobalActivityListeners();
|
|
112
115
|
this.resetActivityTimer();
|
|
113
|
-
|
|
116
|
+
if (this.isReturnHomeMode()) {
|
|
117
|
+
log.trace(`Screensaver registered in returnHome mode with ${config.timeoutSeconds}s timeout, target: ${this.config.startingPageId}/${this.config.defaultViewId || "default"}`);
|
|
118
|
+
}
|
|
119
|
+
else {
|
|
120
|
+
log.trace(`Screensaver registered with ${config.timeoutSeconds}s timeout and '${this.config.exitBehavior}' exit behavior`);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
isReturnHomeMode() {
|
|
124
|
+
return this.config?.screensaverViewBehavior === "returnHome";
|
|
114
125
|
}
|
|
115
126
|
validateConfig(config) {
|
|
116
|
-
if (!config.page) {
|
|
117
|
-
throw new Error("Screensaver page is required");
|
|
118
|
-
}
|
|
119
127
|
if (config.timeoutSeconds <= 0) {
|
|
120
128
|
throw new Error("timeoutSeconds must be greater than 0");
|
|
121
129
|
}
|
|
122
|
-
if (config.
|
|
123
|
-
throw new Error('
|
|
130
|
+
if (config.screensaverViewBehavior && !["default", "specific", "return", "returnHome"].includes(config.screensaverViewBehavior)) {
|
|
131
|
+
throw new Error('screensaverViewBehavior must be "default", "specific", "return", or "returnHome"');
|
|
124
132
|
}
|
|
125
|
-
if (config.
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
133
|
+
if (config.screensaverViewBehavior === "returnHome") {
|
|
134
|
+
// In returnHome mode, startingPageId is required as the home page target
|
|
135
|
+
if (!config.startingPageId) {
|
|
136
|
+
throw new Error('startingPageId is required when screensaverViewBehavior is "returnHome"');
|
|
137
|
+
}
|
|
130
138
|
}
|
|
131
|
-
|
|
132
|
-
|
|
139
|
+
else {
|
|
140
|
+
// In screensaver modes, page is required
|
|
141
|
+
if (!config.page) {
|
|
142
|
+
throw new Error('Screensaver page is required when screensaverViewBehavior is not "returnHome"');
|
|
143
|
+
}
|
|
144
|
+
if (config.exitBehavior === "reset" && !config.startingPageId) {
|
|
145
|
+
throw new Error('startingPageId is required when exitBehavior is "reset"');
|
|
146
|
+
}
|
|
147
|
+
if (config.exitBehavior && !["reset", "return"].includes(config.exitBehavior)) {
|
|
148
|
+
throw new Error('exitBehavior must be either "reset" or "return"');
|
|
149
|
+
}
|
|
150
|
+
if (config.screensaverViewBehavior === "specific" && !config.specificViewId) {
|
|
151
|
+
throw new Error('specificViewId is required when screensaverViewBehavior is "specific"');
|
|
152
|
+
}
|
|
133
153
|
}
|
|
134
154
|
}
|
|
135
155
|
setupGlobalActivityListeners() {
|
|
@@ -219,17 +239,26 @@ export class ScreensaverManager {
|
|
|
219
239
|
}
|
|
220
240
|
}
|
|
221
241
|
async activateScreensaver() {
|
|
222
|
-
if (!this.config
|
|
242
|
+
if (!this.config)
|
|
223
243
|
return;
|
|
224
244
|
if (this.config.blockerCallback && this.config.blockerCallback()) {
|
|
225
245
|
this.resetActivityTimer();
|
|
226
246
|
return;
|
|
227
247
|
}
|
|
248
|
+
// Handle returnHome mode — navigate to home page/view without entering screensaver state
|
|
249
|
+
if (this.isReturnHomeMode()) {
|
|
250
|
+
await this.activateReturnHome();
|
|
251
|
+
return;
|
|
252
|
+
}
|
|
253
|
+
// Standard screensaver mode
|
|
254
|
+
if (this.isScreensaverActive)
|
|
255
|
+
return;
|
|
228
256
|
log.trace("Activating screensaver");
|
|
229
257
|
// Store the current page and view before switching to screensaver
|
|
230
258
|
const currentPageId = this.navigationManager.getCurrentPageId();
|
|
231
259
|
const currentViewId = this.navigationManager.getCurrentViewId();
|
|
232
|
-
|
|
260
|
+
const screensaverPageId = this.config.page.componentId;
|
|
261
|
+
if (currentPageId && currentPageId !== screensaverPageId) {
|
|
233
262
|
this.lastActivePageId = currentPageId;
|
|
234
263
|
this.lastActiveViewId = currentViewId;
|
|
235
264
|
stateManager.set("screensaver.lastActivePageId", currentPageId);
|
|
@@ -239,14 +268,14 @@ export class ScreensaverManager {
|
|
|
239
268
|
stateManager.set("screensaver.isActive", true);
|
|
240
269
|
try {
|
|
241
270
|
// Navigate to screensaver page
|
|
242
|
-
await this.navigationManager.navigateToPage(
|
|
271
|
+
await this.navigationManager.navigateToPage(screensaverPageId, this.config.transitionConfig);
|
|
243
272
|
// Determine which view to show based on screensaverViewBehavior
|
|
244
273
|
const targetViewId = this.determineScreensaverView();
|
|
245
274
|
if (targetViewId) {
|
|
246
275
|
await this.navigationManager.navigateToView(targetViewId, { type: "snap" });
|
|
247
276
|
}
|
|
248
277
|
this.eventBus.emit("screensaver-activated", {
|
|
249
|
-
pageId:
|
|
278
|
+
pageId: screensaverPageId,
|
|
250
279
|
viewId: targetViewId,
|
|
251
280
|
previousPageId: this.lastActivePageId,
|
|
252
281
|
previousViewId: this.lastActiveViewId,
|
|
@@ -263,6 +292,53 @@ export class ScreensaverManager {
|
|
|
263
292
|
this.startRebootCheckInterval();
|
|
264
293
|
this.checkAndPerformReboot();
|
|
265
294
|
}
|
|
295
|
+
/**
|
|
296
|
+
* Return-to-home activation: navigates to the configured home page/view
|
|
297
|
+
* without entering "screensaver active" state. The timer resets immediately
|
|
298
|
+
* for the next inactivity cycle.
|
|
299
|
+
*/
|
|
300
|
+
async activateReturnHome() {
|
|
301
|
+
if (!this.config)
|
|
302
|
+
return;
|
|
303
|
+
const targetPageId = this.config.startingPageId;
|
|
304
|
+
const targetViewId = this.config.defaultViewId || null;
|
|
305
|
+
const currentPageId = this.navigationManager.getCurrentPageId();
|
|
306
|
+
const currentViewId = this.navigationManager.getCurrentViewId();
|
|
307
|
+
// Skip navigation if already on the target page/view
|
|
308
|
+
const alreadyOnTargetPage = currentPageId === targetPageId;
|
|
309
|
+
const alreadyOnTargetView = !targetViewId || currentViewId === targetViewId;
|
|
310
|
+
if (alreadyOnTargetPage && alreadyOnTargetView) {
|
|
311
|
+
log.trace("ReturnHome: Already on home page/view, resetting timer");
|
|
312
|
+
this.resetActivityTimer();
|
|
313
|
+
return;
|
|
314
|
+
}
|
|
315
|
+
log.trace(`ReturnHome: Navigating to ${targetPageId}/${targetViewId || "default"}`);
|
|
316
|
+
try {
|
|
317
|
+
// Navigate to home page
|
|
318
|
+
if (!alreadyOnTargetPage) {
|
|
319
|
+
await this.navigationManager.navigateToPage(targetPageId, this.config.transitionConfig);
|
|
320
|
+
}
|
|
321
|
+
// Navigate to home view if specified
|
|
322
|
+
if (targetViewId && !alreadyOnTargetView) {
|
|
323
|
+
await this.navigationManager.navigateToView(targetViewId, { type: "snap" });
|
|
324
|
+
}
|
|
325
|
+
this.eventBus.emit("screensaver-returned-home", {
|
|
326
|
+
targetPageId,
|
|
327
|
+
targetViewId,
|
|
328
|
+
previousPageId: currentPageId,
|
|
329
|
+
previousViewId: currentViewId,
|
|
330
|
+
});
|
|
331
|
+
if (this.config.activateCallback)
|
|
332
|
+
this.config.activateCallback();
|
|
333
|
+
}
|
|
334
|
+
catch (error) {
|
|
335
|
+
log.error("Failed to return to home:", error);
|
|
336
|
+
}
|
|
337
|
+
// Reset timer for next inactivity cycle
|
|
338
|
+
this.resetActivityTimer();
|
|
339
|
+
// Check reboot timeout if configured
|
|
340
|
+
this.checkAndPerformReboot();
|
|
341
|
+
}
|
|
266
342
|
determineScreensaverView() {
|
|
267
343
|
if (!this.config)
|
|
268
344
|
return null;
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { EventOrchestrator } from "./eventBus";
|
|
2
|
+
export interface TransitionOverlayConfig {
|
|
3
|
+
backgroundColor?: string;
|
|
4
|
+
fadeInDuration?: number;
|
|
5
|
+
holdDuration?: number;
|
|
6
|
+
fadeOutDuration?: number;
|
|
7
|
+
zIndex?: number;
|
|
8
|
+
}
|
|
9
|
+
export interface TransitionRequestPayload {
|
|
10
|
+
stateChanges?: Array<{
|
|
11
|
+
key: string;
|
|
12
|
+
value: any;
|
|
13
|
+
}>;
|
|
14
|
+
afterStateChange?: () => void | Promise<void>;
|
|
15
|
+
fadeInDuration?: number;
|
|
16
|
+
holdDuration?: number;
|
|
17
|
+
fadeOutDuration?: number;
|
|
18
|
+
}
|
|
19
|
+
type TransitionPhase = "idle" | "fade-in" | "hold" | "fade-out";
|
|
20
|
+
/**
|
|
21
|
+
* TransitionOverlay - Manages visual overlay transitions for seamless content updates
|
|
22
|
+
*
|
|
23
|
+
* This class provides a fade-to-opaque overlay that hides content changes during state updates.
|
|
24
|
+
*
|
|
25
|
+
* **Concurrency Strategy:**
|
|
26
|
+
* - If a transition is in the **fade-in or hold phase**, incoming requests queue their callbacks
|
|
27
|
+
* to be batched into the current cycle's hold phase
|
|
28
|
+
* - If a transition is in the **fade-out phase**, incoming requests start a new transition cycle
|
|
29
|
+
* after the current one completes
|
|
30
|
+
*
|
|
31
|
+
* This ensures smooth transitions without jarring interruptions or visible content changes.
|
|
32
|
+
*/
|
|
33
|
+
export declare class TransitionOverlay {
|
|
34
|
+
private config;
|
|
35
|
+
private shadowRoot;
|
|
36
|
+
private overlayElement;
|
|
37
|
+
private currentPhase;
|
|
38
|
+
private queuedCallbacks;
|
|
39
|
+
private queuedRequests;
|
|
40
|
+
private activeTransitionPromise;
|
|
41
|
+
constructor(config: TransitionOverlayConfig, shadowRoot: ShadowRoot);
|
|
42
|
+
private injectOverlayElement;
|
|
43
|
+
/**
|
|
44
|
+
* Execute a transition with the overlay lifecycle
|
|
45
|
+
*
|
|
46
|
+
* @param callback - Function to execute while overlay is opaque (state changes happen here)
|
|
47
|
+
* @param configOverrides - Optional per-request config overrides
|
|
48
|
+
* @returns Promise that resolves after the full transition completes
|
|
49
|
+
*/
|
|
50
|
+
executeTransition(callback: () => void | Promise<void>, configOverrides?: Partial<TransitionOverlayConfig>): Promise<void>;
|
|
51
|
+
private handleConcurrentRequest;
|
|
52
|
+
private performTransition;
|
|
53
|
+
private fadeIn;
|
|
54
|
+
private executeCallbacks;
|
|
55
|
+
private fadeOut;
|
|
56
|
+
private processQueuedRequests;
|
|
57
|
+
/**
|
|
58
|
+
* Update the overlay configuration
|
|
59
|
+
*/
|
|
60
|
+
updateConfig(config: Partial<TransitionOverlayConfig>): void;
|
|
61
|
+
/**
|
|
62
|
+
* Get the current transition phase
|
|
63
|
+
*/
|
|
64
|
+
getPhase(): TransitionPhase;
|
|
65
|
+
/**
|
|
66
|
+
* Check if a transition is currently active
|
|
67
|
+
*/
|
|
68
|
+
isTransitioning(): boolean;
|
|
69
|
+
/**
|
|
70
|
+
* Clean up the overlay element and resources
|
|
71
|
+
*/
|
|
72
|
+
destroy(): void;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Standalone utility for requesting overlay transitions via event bus
|
|
76
|
+
*
|
|
77
|
+
* This is the recommended way for components to request transitions, as it:
|
|
78
|
+
* 1. Works from anywhere in the component tree
|
|
79
|
+
* 2. Gracefully falls back to direct execution if no overlay is available
|
|
80
|
+
* 3. Supports state changes via the standardized event format
|
|
81
|
+
*
|
|
82
|
+
* @param orchestrator - The EventOrchestrator instance
|
|
83
|
+
* @param callback - Function to execute during the transition (while overlay is opaque)
|
|
84
|
+
* @param config - Optional per-request config overrides
|
|
85
|
+
* @returns Promise that resolves after the transition completes
|
|
86
|
+
*/
|
|
87
|
+
export declare function transitionWithOverlay(orchestrator: EventOrchestrator, callback: () => void | Promise<void>, config?: Partial<TransitionOverlayConfig>): Promise<void>;
|
|
88
|
+
/**
|
|
89
|
+
* Helper to convert state changes array to callback function
|
|
90
|
+
*/
|
|
91
|
+
export declare function stateChangesToCallback(stateChanges: Array<{
|
|
92
|
+
key: string;
|
|
93
|
+
value: any;
|
|
94
|
+
}>): () => void;
|
|
95
|
+
export {};
|
|
96
|
+
//# sourceMappingURL=transitionOverlay.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"transitionOverlay.d.ts","sourceRoot":"","sources":["../../src/transitionOverlay.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAM/C,MAAM,WAAW,uBAAuB;IACtC,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,wBAAwB;IACvC,YAAY,CAAC,EAAE,KAAK,CAAC;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,GAAG,CAAA;KAAE,CAAC,CAAC;IAClD,gBAAgB,CAAC,EAAE,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC9C,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B;AASD,KAAK,eAAe,GAAG,MAAM,GAAG,SAAS,GAAG,MAAM,GAAG,UAAU,CAAC;AAEhE;;;;;;;;;;;;GAYG;AACH,qBAAa,iBAAiB;IAC5B,OAAO,CAAC,MAAM,CAAoC;IAClD,OAAO,CAAC,UAAU,CAAa;IAC/B,OAAO,CAAC,cAAc,CAA+B;IACrD,OAAO,CAAC,YAAY,CAA2B;IAC/C,OAAO,CAAC,eAAe,CAAyC;IAChE,OAAO,CAAC,cAAc,CAAiC;IACvD,OAAO,CAAC,uBAAuB,CAA8B;gBAEjD,MAAM,EAAE,uBAAuB,EAAE,UAAU,EAAE,UAAU;IAYnE,OAAO,CAAC,oBAAoB;IA8B5B;;;;;;OAMG;IACU,iBAAiB,CAC5B,QAAQ,EAAE,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,EACpC,eAAe,CAAC,EAAE,OAAO,CAAC,uBAAuB,CAAC,GACjD,OAAO,CAAC,IAAI,CAAC;IAiBhB,OAAO,CAAC,uBAAuB;YAwBjB,iBAAiB;IAmC/B,OAAO,CAAC,MAAM;YAoDA,gBAAgB;IAsB9B,OAAO,CAAC,OAAO;IAmDf,OAAO,CAAC,qBAAqB;IAW7B;;OAEG;IACI,YAAY,CAAC,MAAM,EAAE,OAAO,CAAC,uBAAuB,CAAC,GAAG,IAAI;IAgBnE;;OAEG;IACI,QAAQ,IAAI,eAAe;IAIlC;;OAEG;IACI,eAAe,IAAI,OAAO;IAIjC;;OAEG;IACI,OAAO,IAAI,IAAI;CAUvB;AAED;;;;;;;;;;;;GAYG;AACH,wBAAsB,qBAAqB,CACzC,YAAY,EAAE,iBAAiB,EAC/B,QAAQ,EAAE,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,EACpC,MAAM,CAAC,EAAE,OAAO,CAAC,uBAAuB,CAAC,GACxC,OAAO,CAAC,IAAI,CAAC,CAwBf;AAED;;GAEG;AACH,wBAAgB,sBAAsB,CACpC,YAAY,EAAE,KAAK,CAAC;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,GAAG,CAAA;CAAE,CAAC,GAC/C,MAAM,IAAI,CAMZ"}
|
|
@@ -0,0 +1,328 @@
|
|
|
1
|
+
import { setGlobalState } from "./stateManager";
|
|
2
|
+
import { logger } from "./logger";
|
|
3
|
+
const log = logger;
|
|
4
|
+
/**
|
|
5
|
+
* TransitionOverlay - Manages visual overlay transitions for seamless content updates
|
|
6
|
+
*
|
|
7
|
+
* This class provides a fade-to-opaque overlay that hides content changes during state updates.
|
|
8
|
+
*
|
|
9
|
+
* **Concurrency Strategy:**
|
|
10
|
+
* - If a transition is in the **fade-in or hold phase**, incoming requests queue their callbacks
|
|
11
|
+
* to be batched into the current cycle's hold phase
|
|
12
|
+
* - If a transition is in the **fade-out phase**, incoming requests start a new transition cycle
|
|
13
|
+
* after the current one completes
|
|
14
|
+
*
|
|
15
|
+
* This ensures smooth transitions without jarring interruptions or visible content changes.
|
|
16
|
+
*/
|
|
17
|
+
export class TransitionOverlay {
|
|
18
|
+
config;
|
|
19
|
+
shadowRoot;
|
|
20
|
+
overlayElement = null;
|
|
21
|
+
currentPhase = "idle";
|
|
22
|
+
queuedCallbacks = [];
|
|
23
|
+
queuedRequests = [];
|
|
24
|
+
activeTransitionPromise = null;
|
|
25
|
+
constructor(config, shadowRoot) {
|
|
26
|
+
this.config = {
|
|
27
|
+
backgroundColor: config.backgroundColor ?? "#000",
|
|
28
|
+
fadeInDuration: config.fadeInDuration ?? 200,
|
|
29
|
+
holdDuration: config.holdDuration ?? 100,
|
|
30
|
+
fadeOutDuration: config.fadeOutDuration ?? 200,
|
|
31
|
+
zIndex: config.zIndex ?? 100,
|
|
32
|
+
};
|
|
33
|
+
this.shadowRoot = shadowRoot;
|
|
34
|
+
this.injectOverlayElement();
|
|
35
|
+
}
|
|
36
|
+
injectOverlayElement() {
|
|
37
|
+
this.overlayElement = document.createElement("div");
|
|
38
|
+
this.overlayElement.className = "transition-overlay";
|
|
39
|
+
this.overlayElement.style.cssText = `
|
|
40
|
+
position: absolute;
|
|
41
|
+
inset: 0;
|
|
42
|
+
background-color: ${this.config.backgroundColor};
|
|
43
|
+
opacity: 0;
|
|
44
|
+
pointer-events: none;
|
|
45
|
+
z-index: ${this.config.zIndex};
|
|
46
|
+
will-change: opacity;
|
|
47
|
+
transition: opacity ${this.config.fadeInDuration}ms ease-in-out;
|
|
48
|
+
`;
|
|
49
|
+
// Append to the shadow root or to .page if it exists
|
|
50
|
+
const pageElement = this.shadowRoot.querySelector(".page");
|
|
51
|
+
const container = pageElement || this.shadowRoot;
|
|
52
|
+
// Ensure the container has relative positioning for absolute overlay
|
|
53
|
+
if (container instanceof HTMLElement) {
|
|
54
|
+
const currentPosition = window.getComputedStyle(container).position;
|
|
55
|
+
if (currentPosition === "static") {
|
|
56
|
+
container.style.position = "relative";
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
container.appendChild(this.overlayElement);
|
|
60
|
+
log.trace("TransitionOverlay element injected into shadow DOM");
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Execute a transition with the overlay lifecycle
|
|
64
|
+
*
|
|
65
|
+
* @param callback - Function to execute while overlay is opaque (state changes happen here)
|
|
66
|
+
* @param configOverrides - Optional per-request config overrides
|
|
67
|
+
* @returns Promise that resolves after the full transition completes
|
|
68
|
+
*/
|
|
69
|
+
async executeTransition(callback, configOverrides) {
|
|
70
|
+
// Merge config overrides
|
|
71
|
+
const effectiveConfig = {
|
|
72
|
+
...this.config,
|
|
73
|
+
...configOverrides,
|
|
74
|
+
};
|
|
75
|
+
// Handle concurrency
|
|
76
|
+
if (this.currentPhase !== "idle") {
|
|
77
|
+
return this.handleConcurrentRequest(callback, effectiveConfig);
|
|
78
|
+
}
|
|
79
|
+
// Start new transition
|
|
80
|
+
this.activeTransitionPromise = this.performTransition(callback, effectiveConfig);
|
|
81
|
+
return this.activeTransitionPromise;
|
|
82
|
+
}
|
|
83
|
+
handleConcurrentRequest(callback, config) {
|
|
84
|
+
return new Promise((resolve, reject) => {
|
|
85
|
+
// If we're in fade-in or hold, batch this callback into the current cycle
|
|
86
|
+
if (this.currentPhase === "fade-in" || this.currentPhase === "hold") {
|
|
87
|
+
log.trace("Batching callback into current transition cycle", {
|
|
88
|
+
currentPhase: this.currentPhase
|
|
89
|
+
});
|
|
90
|
+
this.queuedCallbacks.push(callback);
|
|
91
|
+
// Resolve when the current transition completes
|
|
92
|
+
this.activeTransitionPromise?.then(resolve).catch(reject);
|
|
93
|
+
}
|
|
94
|
+
// If we're in fade-out, queue for the next cycle
|
|
95
|
+
else if (this.currentPhase === "fade-out") {
|
|
96
|
+
log.trace("Queueing request for next transition cycle", {
|
|
97
|
+
currentPhase: this.currentPhase
|
|
98
|
+
});
|
|
99
|
+
this.queuedRequests.push({ callback, config, resolve, reject });
|
|
100
|
+
}
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
async performTransition(callback, config) {
|
|
104
|
+
if (!this.overlayElement) {
|
|
105
|
+
throw new Error("Overlay element not initialized");
|
|
106
|
+
}
|
|
107
|
+
try {
|
|
108
|
+
// Phase 1: Fade in
|
|
109
|
+
this.currentPhase = "fade-in";
|
|
110
|
+
await this.fadeIn(config.fadeInDuration);
|
|
111
|
+
// Phase 2: Hold & execute callbacks
|
|
112
|
+
this.currentPhase = "hold";
|
|
113
|
+
await this.executeCallbacks(callback, config.holdDuration);
|
|
114
|
+
// Phase 3: Fade out
|
|
115
|
+
this.currentPhase = "fade-out";
|
|
116
|
+
await this.fadeOut(config.fadeOutDuration);
|
|
117
|
+
// Back to idle
|
|
118
|
+
this.currentPhase = "idle";
|
|
119
|
+
// Process any queued requests
|
|
120
|
+
this.processQueuedRequests();
|
|
121
|
+
}
|
|
122
|
+
catch (error) {
|
|
123
|
+
log.error("Transition error:", error);
|
|
124
|
+
this.currentPhase = "idle";
|
|
125
|
+
this.queuedCallbacks = [];
|
|
126
|
+
throw error;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
fadeIn(duration) {
|
|
130
|
+
if (!this.overlayElement)
|
|
131
|
+
return Promise.resolve();
|
|
132
|
+
return new Promise((resolve) => {
|
|
133
|
+
const element = this.overlayElement;
|
|
134
|
+
// Update transition duration
|
|
135
|
+
element.style.transition = `opacity ${duration}ms ease-in-out`;
|
|
136
|
+
// Enable pointer events to block interaction
|
|
137
|
+
element.style.pointerEvents = "all";
|
|
138
|
+
let transitionEndFired = false;
|
|
139
|
+
let fallbackTimerId = null;
|
|
140
|
+
const cleanup = () => {
|
|
141
|
+
if (transitionEndFired)
|
|
142
|
+
return;
|
|
143
|
+
transitionEndFired = true;
|
|
144
|
+
element.removeEventListener("transitionend", transitionEndHandler);
|
|
145
|
+
if (fallbackTimerId !== null) {
|
|
146
|
+
clearTimeout(fallbackTimerId);
|
|
147
|
+
fallbackTimerId = null;
|
|
148
|
+
}
|
|
149
|
+
log.trace("Overlay fade-in complete");
|
|
150
|
+
resolve();
|
|
151
|
+
};
|
|
152
|
+
const transitionEndHandler = (e) => {
|
|
153
|
+
// Only respond to opacity transitions on this element
|
|
154
|
+
if (e.target === element && e.propertyName === "opacity") {
|
|
155
|
+
cleanup();
|
|
156
|
+
}
|
|
157
|
+
};
|
|
158
|
+
element.addEventListener("transitionend", transitionEndHandler);
|
|
159
|
+
// Trigger fade-in by setting opacity to 1
|
|
160
|
+
// Use requestAnimationFrame to ensure CSS transition is applied
|
|
161
|
+
requestAnimationFrame(() => {
|
|
162
|
+
element.style.opacity = "1";
|
|
163
|
+
});
|
|
164
|
+
// Fallback timeout
|
|
165
|
+
fallbackTimerId = window.setTimeout(() => {
|
|
166
|
+
log.trace("Overlay fade-in fallback timeout triggered");
|
|
167
|
+
cleanup();
|
|
168
|
+
}, duration + 50);
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
async executeCallbacks(callback, holdDuration) {
|
|
172
|
+
// Execute the main callback
|
|
173
|
+
await Promise.resolve(callback());
|
|
174
|
+
// Execute any batched callbacks
|
|
175
|
+
if (this.queuedCallbacks.length > 0) {
|
|
176
|
+
log.trace(`Executing ${this.queuedCallbacks.length} batched callbacks`);
|
|
177
|
+
for (const queuedCallback of this.queuedCallbacks) {
|
|
178
|
+
await Promise.resolve(queuedCallback());
|
|
179
|
+
}
|
|
180
|
+
this.queuedCallbacks = [];
|
|
181
|
+
}
|
|
182
|
+
// Hold duration
|
|
183
|
+
if (holdDuration > 0) {
|
|
184
|
+
await new Promise(resolve => setTimeout(resolve, holdDuration));
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
fadeOut(duration) {
|
|
188
|
+
if (!this.overlayElement)
|
|
189
|
+
return Promise.resolve();
|
|
190
|
+
return new Promise((resolve) => {
|
|
191
|
+
const element = this.overlayElement;
|
|
192
|
+
// Update transition duration
|
|
193
|
+
element.style.transition = `opacity ${duration}ms ease-in-out`;
|
|
194
|
+
let transitionEndFired = false;
|
|
195
|
+
let fallbackTimerId = null;
|
|
196
|
+
const cleanup = () => {
|
|
197
|
+
if (transitionEndFired)
|
|
198
|
+
return;
|
|
199
|
+
transitionEndFired = true;
|
|
200
|
+
element.removeEventListener("transitionend", transitionEndHandler);
|
|
201
|
+
if (fallbackTimerId !== null) {
|
|
202
|
+
clearTimeout(fallbackTimerId);
|
|
203
|
+
fallbackTimerId = null;
|
|
204
|
+
}
|
|
205
|
+
// Disable pointer events after fade-out
|
|
206
|
+
element.style.pointerEvents = "none";
|
|
207
|
+
log.trace("Overlay fade-out complete");
|
|
208
|
+
resolve();
|
|
209
|
+
};
|
|
210
|
+
const transitionEndHandler = (e) => {
|
|
211
|
+
// Only respond to opacity transitions on this element
|
|
212
|
+
if (e.target === element && e.propertyName === "opacity") {
|
|
213
|
+
cleanup();
|
|
214
|
+
}
|
|
215
|
+
};
|
|
216
|
+
element.addEventListener("transitionend", transitionEndHandler);
|
|
217
|
+
// Trigger fade-out by setting opacity to 0
|
|
218
|
+
requestAnimationFrame(() => {
|
|
219
|
+
element.style.opacity = "0";
|
|
220
|
+
});
|
|
221
|
+
// Fallback timeout
|
|
222
|
+
fallbackTimerId = window.setTimeout(() => {
|
|
223
|
+
log.trace("Overlay fade-out fallback timeout triggered");
|
|
224
|
+
cleanup();
|
|
225
|
+
}, duration + 50);
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
processQueuedRequests() {
|
|
229
|
+
if (this.queuedRequests.length > 0) {
|
|
230
|
+
log.trace(`Processing ${this.queuedRequests.length} queued transition requests`);
|
|
231
|
+
const request = this.queuedRequests.shift();
|
|
232
|
+
this.performTransition(request.callback, request.config)
|
|
233
|
+
.then(request.resolve)
|
|
234
|
+
.catch(request.reject);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
/**
|
|
238
|
+
* Update the overlay configuration
|
|
239
|
+
*/
|
|
240
|
+
updateConfig(config) {
|
|
241
|
+
this.config = {
|
|
242
|
+
...this.config,
|
|
243
|
+
...config,
|
|
244
|
+
};
|
|
245
|
+
if (this.overlayElement) {
|
|
246
|
+
if (config.backgroundColor) {
|
|
247
|
+
this.overlayElement.style.backgroundColor = config.backgroundColor;
|
|
248
|
+
}
|
|
249
|
+
if (config.zIndex !== undefined) {
|
|
250
|
+
this.overlayElement.style.zIndex = String(config.zIndex);
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
/**
|
|
255
|
+
* Get the current transition phase
|
|
256
|
+
*/
|
|
257
|
+
getPhase() {
|
|
258
|
+
return this.currentPhase;
|
|
259
|
+
}
|
|
260
|
+
/**
|
|
261
|
+
* Check if a transition is currently active
|
|
262
|
+
*/
|
|
263
|
+
isTransitioning() {
|
|
264
|
+
return this.currentPhase !== "idle";
|
|
265
|
+
}
|
|
266
|
+
/**
|
|
267
|
+
* Clean up the overlay element and resources
|
|
268
|
+
*/
|
|
269
|
+
destroy() {
|
|
270
|
+
if (this.overlayElement && this.overlayElement.parentNode) {
|
|
271
|
+
this.overlayElement.parentNode.removeChild(this.overlayElement);
|
|
272
|
+
}
|
|
273
|
+
this.overlayElement = null;
|
|
274
|
+
this.queuedCallbacks = [];
|
|
275
|
+
this.queuedRequests = [];
|
|
276
|
+
this.currentPhase = "idle";
|
|
277
|
+
log.trace("TransitionOverlay destroyed");
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
/**
|
|
281
|
+
* Standalone utility for requesting overlay transitions via event bus
|
|
282
|
+
*
|
|
283
|
+
* This is the recommended way for components to request transitions, as it:
|
|
284
|
+
* 1. Works from anywhere in the component tree
|
|
285
|
+
* 2. Gracefully falls back to direct execution if no overlay is available
|
|
286
|
+
* 3. Supports state changes via the standardized event format
|
|
287
|
+
*
|
|
288
|
+
* @param orchestrator - The EventOrchestrator instance
|
|
289
|
+
* @param callback - Function to execute during the transition (while overlay is opaque)
|
|
290
|
+
* @param config - Optional per-request config overrides
|
|
291
|
+
* @returns Promise that resolves after the transition completes
|
|
292
|
+
*/
|
|
293
|
+
export async function transitionWithOverlay(orchestrator, callback, config) {
|
|
294
|
+
const transitionBus = orchestrator.getEventBus("page-transition-overlay");
|
|
295
|
+
if (transitionBus) {
|
|
296
|
+
// Wrap in a promise that resolves when the transition completes
|
|
297
|
+
return new Promise((resolve, reject) => {
|
|
298
|
+
try {
|
|
299
|
+
transitionBus.emit("request-transition", {
|
|
300
|
+
callback,
|
|
301
|
+
...config,
|
|
302
|
+
_resolve: resolve,
|
|
303
|
+
_reject: reject,
|
|
304
|
+
});
|
|
305
|
+
}
|
|
306
|
+
catch (error) {
|
|
307
|
+
// If the emit fails (no listeners), fall back to direct execution
|
|
308
|
+
log.warn("No transition overlay available, executing callback directly");
|
|
309
|
+
Promise.resolve(callback()).then(resolve).catch(reject);
|
|
310
|
+
}
|
|
311
|
+
});
|
|
312
|
+
}
|
|
313
|
+
else {
|
|
314
|
+
// Fallback: no overlay available, execute directly
|
|
315
|
+
log.trace("No transition overlay bus found, executing callback directly");
|
|
316
|
+
await Promise.resolve(callback());
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
/**
|
|
320
|
+
* Helper to convert state changes array to callback function
|
|
321
|
+
*/
|
|
322
|
+
export function stateChangesToCallback(stateChanges) {
|
|
323
|
+
return () => {
|
|
324
|
+
stateChanges.forEach(({ key, value }) => {
|
|
325
|
+
setGlobalState(key, value);
|
|
326
|
+
});
|
|
327
|
+
};
|
|
328
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hawsen-the-first/interactiv",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.3",
|
|
4
4
|
"description": "A TypeScript framework for building interactive applications with event management, state management, navigation, and screensaver functionality",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"module": "./dist/index.js",
|