@omerdlw/base-framework 1.0.0 → 1.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +1 -1
- package/README.md +121 -52
- package/dist/atoms/index.d.ts +0 -1
- package/dist/atoms/index.d.ts.map +1 -1
- package/dist/atoms/index.js +1 -64
- package/dist/atoms/index.js.map +1 -1
- package/dist/error/index.js +22 -25
- package/dist/error/index.js.map +1 -1
- package/dist/hooks/index.d.ts +2 -9
- package/dist/hooks/index.d.ts.map +1 -1
- package/dist/hooks/index.js +97 -540
- package/dist/hooks/index.js.map +1 -1
- package/dist/hooks/types.d.ts +3 -45
- package/dist/hooks/types.d.ts.map +1 -1
- package/dist/hooks/use-controllable-state.d.ts +1 -1
- package/dist/hooks/use-controllable-state.d.ts.map +1 -1
- package/dist/hooks/use-global-event.d.ts +0 -2
- package/dist/hooks/use-global-event.d.ts.map +1 -1
- package/dist/hooks/use-hotkey.d.ts +1 -1
- package/dist/hooks/use-hotkey.d.ts.map +1 -1
- package/dist/index.js +11283 -11772
- package/dist/index.js.map +1 -1
- package/dist/kernel/adapters.d.ts.map +1 -1
- package/dist/kernel/index.d.ts +1 -0
- package/dist/kernel/index.d.ts.map +1 -1
- package/dist/kernel/index.js +10 -81
- package/dist/kernel/index.js.map +1 -1
- package/dist/kernel/installed.d.ts.map +1 -1
- package/dist/kernel/page-controller.d.ts.map +1 -1
- package/dist/kernel/provider.d.ts.map +1 -1
- package/dist/modules/ambient/constants.d.ts +1 -1
- package/dist/modules/ambient/constants.d.ts.map +1 -1
- package/dist/modules/ambient/index.js +180 -326
- package/dist/modules/ambient/index.js.map +1 -1
- package/dist/modules/ambient/module.d.ts.map +1 -1
- package/dist/modules/background/constants.d.ts +4 -2
- package/dist/modules/background/constants.d.ts.map +1 -1
- package/dist/modules/background/context.d.ts +3 -0
- package/dist/modules/background/context.d.ts.map +1 -1
- package/dist/modules/background/hooks.d.ts +1 -1
- package/dist/modules/background/index.d.ts +2 -2
- package/dist/modules/background/index.d.ts.map +1 -1
- package/dist/modules/background/index.js +1172 -1270
- package/dist/modules/background/index.js.map +1 -1
- package/dist/modules/background/overlay.d.ts +1 -1
- package/dist/modules/background/overlay.d.ts.map +1 -1
- package/dist/modules/background/types.d.ts +1 -1
- package/dist/modules/background/types.d.ts.map +1 -1
- package/dist/modules/context-menu/constants.d.ts +1 -1
- package/dist/modules/context-menu/constants.d.ts.map +1 -1
- package/dist/modules/context-menu/context.d.ts.map +1 -1
- package/dist/modules/context-menu/hooks.d.ts.map +1 -1
- package/dist/modules/context-menu/index.js +133 -238
- package/dist/modules/context-menu/index.js.map +1 -1
- package/dist/modules/controls/constants.d.ts +1 -1
- package/dist/modules/controls/constants.d.ts.map +1 -1
- package/dist/modules/controls/hooks.d.ts +1 -1
- package/dist/modules/controls/index.js +28 -162
- package/dist/modules/controls/index.js.map +1 -1
- package/dist/modules/controls/types.d.ts +1 -1
- package/dist/modules/controls/types.d.ts.map +1 -1
- package/dist/modules/dock/cards.d.ts +2 -2
- package/dist/modules/dock/constants.d.ts +1 -1
- package/dist/modules/dock/constants.d.ts.map +1 -1
- package/dist/modules/dock/context.d.ts +10 -6
- package/dist/modules/dock/context.d.ts.map +1 -1
- package/dist/modules/dock/hooks.d.ts +5 -5
- package/dist/modules/dock/hooks.d.ts.map +1 -1
- package/dist/modules/dock/index.d.ts +1 -1
- package/dist/modules/dock/index.d.ts.map +1 -1
- package/dist/modules/dock/index.js +2060 -2080
- package/dist/modules/dock/index.js.map +1 -1
- package/dist/modules/dock/runtime/dock.d.ts.map +1 -1
- package/dist/modules/dock/types.d.ts +1 -1
- package/dist/modules/dock/types.d.ts.map +1 -1
- package/dist/modules/index.js +10789 -11027
- package/dist/modules/index.js.map +1 -1
- package/dist/modules/loading/constants.d.ts +4 -2
- package/dist/modules/loading/constants.d.ts.map +1 -1
- package/dist/modules/loading/context.d.ts +3 -0
- package/dist/modules/loading/context.d.ts.map +1 -1
- package/dist/modules/loading/hooks.d.ts +1 -1
- package/dist/modules/loading/hooks.d.ts.map +1 -1
- package/dist/modules/loading/index.d.ts +2 -2
- package/dist/modules/loading/index.d.ts.map +1 -1
- package/dist/modules/loading/index.js +118 -222
- package/dist/modules/loading/index.js.map +1 -1
- package/dist/modules/loading/types.d.ts +1 -1
- package/dist/modules/loading/types.d.ts.map +1 -1
- package/dist/modules/media/constants.d.ts +2 -1
- package/dist/modules/media/constants.d.ts.map +1 -1
- package/dist/modules/media/context.d.ts +3 -0
- package/dist/modules/media/context.d.ts.map +1 -1
- package/dist/modules/media/index.d.ts +2 -2
- package/dist/modules/media/index.d.ts.map +1 -1
- package/dist/modules/media/index.js +61 -218
- package/dist/modules/media/index.js.map +1 -1
- package/dist/modules/modal/constants.d.ts +1 -1
- package/dist/modules/modal/constants.d.ts.map +1 -1
- package/dist/modules/modal/context.d.ts.map +1 -1
- package/dist/modules/modal/hooks.d.ts +2 -2
- package/dist/modules/modal/index.js +20 -366
- package/dist/modules/modal/index.js.map +1 -1
- package/dist/modules/notification/constants.d.ts +4 -2
- package/dist/modules/notification/constants.d.ts.map +1 -1
- package/dist/modules/notification/context.d.ts +4 -0
- package/dist/modules/notification/context.d.ts.map +1 -1
- package/dist/modules/notification/hooks.d.ts +1 -1
- package/dist/modules/notification/index.d.ts +2 -3
- package/dist/modules/notification/index.d.ts.map +1 -1
- package/dist/modules/notification/index.js +70 -154
- package/dist/modules/notification/index.js.map +1 -1
- package/dist/modules/notification/module.d.ts.map +1 -1
- package/dist/provider.js +255 -257
- package/dist/provider.js.map +1 -1
- package/dist/theme.d.ts +1 -0
- package/dist/theme.d.ts.map +1 -1
- package/dist/theme.js +2 -2
- package/dist/theme.js.map +1 -1
- package/dist/utils/index.d.ts +5 -6
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/index.js +1 -57
- package/dist/utils/index.js.map +1 -1
- package/dist/utils/number.d.ts +0 -1
- package/dist/utils/number.d.ts.map +1 -1
- package/dist/utils/object.d.ts +0 -2
- package/dist/utils/object.d.ts.map +1 -1
- package/dist/utils/string.d.ts +0 -3
- package/dist/utils/string.d.ts.map +1 -1
- package/dist/utils/timing.d.ts +0 -1
- package/dist/utils/timing.d.ts.map +1 -1
- package/docs/README.md +124 -0
- package/docs/architecture-and-rules.md +129 -0
- package/docs/core-engine.md +196 -0
- package/docs/custom-modules.md +267 -0
- package/docs/getting-started.md +199 -0
- package/docs/modules/README.md +176 -0
- package/docs/modules/ambient.md +150 -0
- package/docs/modules/background.md +195 -0
- package/docs/modules/context-menu.md +248 -0
- package/docs/modules/controls.md +159 -0
- package/docs/modules/dock.md +734 -0
- package/docs/modules/error-boundary.md +160 -0
- package/docs/modules/kernel.md +266 -0
- package/docs/modules/loading.md +157 -0
- package/docs/modules/media.md +196 -0
- package/docs/modules/modal.md +229 -0
- package/docs/modules/notification.md +161 -0
- package/docs/modules/result.md +135 -0
- package/docs/testing.md +160 -0
- package/docs/theming.md +185 -0
- package/package.json +50 -2
- package/templates/AGENTS.md +165 -0
- package/dist/hooks/use-async-action.d.ts +0 -3
- package/dist/hooks/use-async-action.d.ts.map +0 -1
- package/dist/hooks/use-debounce.d.ts +0 -2
- package/dist/hooks/use-debounce.d.ts.map +0 -1
- package/dist/hooks/use-intersection-observer.d.ts +0 -4
- package/dist/hooks/use-intersection-observer.d.ts.map +0 -1
- package/dist/hooks/use-media-query.d.ts +0 -2
- package/dist/hooks/use-media-query.d.ts.map +0 -1
- package/dist/hooks/use-mounted.d.ts +0 -2
- package/dist/hooks/use-mounted.d.ts.map +0 -1
- package/dist/hooks/use-server-action.d.ts +0 -4
- package/dist/hooks/use-server-action.d.ts.map +0 -1
- package/dist/hooks/use-storage-state.d.ts +0 -5
- package/dist/hooks/use-storage-state.d.ts.map +0 -1
- package/dist/modules/theme.d.ts +0 -3
- package/dist/modules/theme.d.ts.map +0 -1
- package/dist/utils/json.d.ts +0 -3
- package/dist/utils/json.d.ts.map +0 -1
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
# Background — `@omerdlw/base-framework/modules/background`
|
|
2
|
+
|
|
3
|
+
> **Read this first if you need to:** put an image, a video file or a YouTube video behind a page, dim or fade it, change it from code (mute, loop, play), or understand how the dock's video controls reach the video.
|
|
4
|
+
|
|
5
|
+
## 1. At a glance
|
|
6
|
+
|
|
7
|
+
| | |
|
|
8
|
+
| :----------------- | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
9
|
+
| **Import** | `import { … } from "@omerdlw/base-framework/modules/background"` |
|
|
10
|
+
| **Module id** | `background` (`backgroundModule`, in `defaultModules`) |
|
|
11
|
+
| **Renders** | `BackgroundOverlay` as a **Backdrop**: a full-viewport layer _behind_ the page (the host renders backdrops before `children`) |
|
|
12
|
+
| **Registry type** | `background` — singleton key `page-background`, lifecycle `immediate` (entries are removed the moment the registering page unmounts; the declared 600 ms delay applies only if a registration opts into `graceful` / `route`) |
|
|
13
|
+
| **Page slice** | `usePage({ background })` → controller `page.modules.background` |
|
|
14
|
+
| **Peers (`uses`)** | `media` (the video registers itself as source `background`) |
|
|
15
|
+
| **Theme** | `backgroundTheme` slots in `src/config/background.module.theme.ts` |
|
|
16
|
+
| **HTTP route** | `GET /api/background/youtube` (stream / thumbnail / metadata proxy for YouTube) |
|
|
17
|
+
| **Tests** | `tests/modules/background.test.ts` |
|
|
18
|
+
|
|
19
|
+
## 2. Mental model
|
|
20
|
+
|
|
21
|
+
A background is a **single state object** (`BackgroundState`) that is resolved from layers, lowest to highest:
|
|
22
|
+
|
|
23
|
+
```
|
|
24
|
+
DEFAULT_BACKGROUND (nothing)
|
|
25
|
+
← registry value what usePage({ background }) / useBackground() registered for this route
|
|
26
|
+
← scoped override what you did at runtime with setBackground / toggleMute / … (this pathname only)
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
- The registry value belongs to the **page**: when the page unmounts the value is dropped immediately (`immediate`), so the background disappears or the next page's background fades in.
|
|
30
|
+
- The runtime override is **scoped to the pathname** it was made on. Navigating away discards it; `resetBackground()` clears it manually.
|
|
31
|
+
- `BackgroundOverlay` keys its layer by the media identity (image URL, video URL, or `youtube:<id>`) and cross-fades with `AnimatePresence mode="sync"` when the key changes. Changing only an option (overlay opacity, mute) keeps the same layer.
|
|
32
|
+
|
|
33
|
+
## 3. Quick start
|
|
34
|
+
|
|
35
|
+
```tsx
|
|
36
|
+
"use client";
|
|
37
|
+
import { usePage } from "@omerdlw/base-framework/kernel";
|
|
38
|
+
|
|
39
|
+
// Image with a dimming overlay
|
|
40
|
+
usePage({
|
|
41
|
+
title: "Home",
|
|
42
|
+
background: { image: "/images/hero.jpg", overlay: true, overlayOpacity: 0.5 },
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
// Shorthand: a string is an image, or a video when it looks like one
|
|
46
|
+
usePage({ background: "/images/hero.jpg" });
|
|
47
|
+
usePage({ background: "https://youtu.be/dQw4w9WgXcQ?t=30" });
|
|
48
|
+
|
|
49
|
+
// YouTube loop, muted, 1080p via the stream proxy
|
|
50
|
+
usePage({
|
|
51
|
+
background: {
|
|
52
|
+
video: "https://www.youtube.com/watch?v=dQw4w9WgXcQ&t=30",
|
|
53
|
+
videoOptions: { loop: true, muted: true, quality: "1080p" },
|
|
54
|
+
},
|
|
55
|
+
});
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## 4. Capabilities
|
|
59
|
+
|
|
60
|
+
| Capability | How |
|
|
61
|
+
| :----------------------------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
62
|
+
| Solid color | `color: "var(--black)"` (the default color token). |
|
|
63
|
+
| Image | `image: "/x.jpg"`, `position: "center \| left \| right \| …"`, `imageStyle` (CSS + `className`, `leftGradient`, `rightGradient`). |
|
|
64
|
+
| Direct video file | `video: "/x.mp4"` (`.mp4 .webm .mov .m4v .ogg .ogv .m3u8`, `blob:` URLs, `/api/background/youtube…`). |
|
|
65
|
+
| YouTube video | `video: "<youtube url>"` or `"youtube:<id>"` / `"yt:<id>"`. Start/end times from `?t=`, `?start=`, `?end=` (`90`, `1m30s`, `1h2m3s`). |
|
|
66
|
+
| Autoplay / mute / loop / speed | `videoOptions.autoplay` (default true), `muted` (default true), `loop`, `playbackRate`, `corp` (trim seconds from the end). |
|
|
67
|
+
| Quality & codec (YouTube) | `videoOptions.quality` (`auto 2160p 1440p 1080p 720p 480p 360p`, default `1080p`), `codec` (`auto hevc av1 h264 vp9`). |
|
|
68
|
+
| Poster & spinner (YouTube) | `showPoster` (uses `image` as poster), `showSpinner` (default true). |
|
|
69
|
+
| Iframe fallback | `videoOptions.forceIframe: true` uses the YouTube embed instead of the stream proxy. |
|
|
70
|
+
| Dimming overlay | `overlay: true`, `overlayColor` (default `var(--black)`), `overlayOpacity`. |
|
|
71
|
+
| Edge fades | `fadeEdges` (`number`, `"20"`, `{ left, right }`, `false`), `leftGradient` / `rightGradient` (strength 1–n). A custom `width` implies a 20 % fade unless `fadeEdges: false`. |
|
|
72
|
+
| Film grain | `noiseStyle: { opacity, mixBlendMode }`. |
|
|
73
|
+
| Layout | `width` (number px or CSS length) places a video frame left/right/center via `position`; `fit` / `videoOptions.fit` (`cover contain fill none scale-down`); `bg-*` class tokens in `className`/`videoClassName` map to object-position slots. |
|
|
74
|
+
| Custom transition | `animation: { initial, animate, exit, transition, exitDurationFactor }` (defaults: opacity fade with `DURATION_TOKENS.SLOW`; exit factor 0.6). |
|
|
75
|
+
| Programmatic control | `useBackgroundActions()` / `page.modules.background`. |
|
|
76
|
+
| Dock media controls | Automatic through the `media` module ([§8](#8-media-integration)). |
|
|
77
|
+
|
|
78
|
+
## 5. Public API
|
|
79
|
+
|
|
80
|
+
| Export | Kind | Description |
|
|
81
|
+
| :------------------------------------------------------------------------------------------------------------------------------------------------------------- | :-------- | :------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
82
|
+
| `backgroundModule` | module | `defineModule` definition. Install it in `CoreProvider` (already in `defaultModules`). |
|
|
83
|
+
| `BackgroundProvider` | component | Resolves state, owns the video element ref, registers the media source. Mounted by the host. |
|
|
84
|
+
| `BackgroundOverlay` | component | Renders the layer(s). Mounted by the host as the module's Backdrop. |
|
|
85
|
+
| `useBackground(config?, options?)` | hook | Registers `config` for this route **and** returns `{ ...state, ...actions }`. `options`: `RegistryMetadata & { enabled? }` (e.g. `priority`, `source`). |
|
|
86
|
+
| `useBackgroundRegistration(config, options?)` | hook | Register only (what `usePage` does under the hood). `null` / `undefined` removes it. |
|
|
87
|
+
| `useBackgroundState()` | hook | `BackgroundStateComputed` (re-renders on state change). |
|
|
88
|
+
| `useBackgroundActions()` | hook | Stable actions (below). Throws outside the provider. |
|
|
89
|
+
| `defineBackground(def)` | builder | Reusable preset: `defineBackground({ id?, ...state }).use(overrides?, options?)`. A string is an image. |
|
|
90
|
+
| `BackgroundContext` | context | For tests and custom providers. |
|
|
91
|
+
| `extractYouTubeVideoId(input, { allowBareId? })`, `isYouTubeUrl`, `isDirectVideoUrl`, `parseYouTubeUrlConfig`, `getYouTubeStreamUrl`, `getYouTubeThumbnailUrl` | utils | URL parsing and proxy URL builders. |
|
|
92
|
+
| `DEFAULT_BACKGROUND`, `BACKGROUND_MEDIA_ID` (`"background"`), `backgroundTheme` | constants | |
|
|
93
|
+
| Types | | `BackgroundState`, `BackgroundStateComputed`, `BackgroundActions`, `VideoOptions`, `BackgroundPageConfig`, `DefinedBackground`, `BackgroundThemeSlot`, … |
|
|
94
|
+
|
|
95
|
+
### 5.1 `BackgroundState`
|
|
96
|
+
|
|
97
|
+
| Field | Default | Notes |
|
|
98
|
+
| :------------------------------------------- | :------------------------------------------ | :----------------------------------------------------------------------------------------------------------------------------- |
|
|
99
|
+
| `image` | `null` | URL. A YouTube URL here is moved to `video`. |
|
|
100
|
+
| `video` | `null` | File or YouTube URL. Setting a video resets `isPlaying` to `videoOptions.autoplay !== false`. |
|
|
101
|
+
| `color` | — | Solid color; counts as "has a background". |
|
|
102
|
+
| `position` | `"center"` | Image position or video side (`left` / `right` / otherwise centered). |
|
|
103
|
+
| `width` | `null` | Video frame width (also `videoOptions.width`, `videoStyle.width`). |
|
|
104
|
+
| `fit` | `null` | Object fit for the video. |
|
|
105
|
+
| `overlay`, `overlayColor`, `overlayOpacity` | `false`, `var(--black)`, `0` | Dimming layer and gradient colour. |
|
|
106
|
+
| `fadeEdges`, `leftGradient`, `rightGradient` | `null` | See capabilities. |
|
|
107
|
+
| `noiseStyle` | `{}` | Grain. |
|
|
108
|
+
| `imageStyle`, `videoStyle` | `{}` | CSS plus `className`, `leftGradient`, `rightGradient`. |
|
|
109
|
+
| `className`, `videoClassName` | `""` | Extra classes (`bg-*` tokens are mapped to theme slots, `w-*`/`max-w-*`/`min-w-*` become the frame width). |
|
|
110
|
+
| `videoOptions` | autoplay ✓, muted ✓, loop ✗, rate 1, corp 0 | See `VideoOptions` in `types.ts`. Also `startTime`, `endTime`, `showPoster`, `showSpinner`, `quality`, `codec`, `forceIframe`. |
|
|
111
|
+
| `animation` | `null` | Per-background motion override. |
|
|
112
|
+
| `isPlaying`, `videoElement` | | Runtime values published by the player. |
|
|
113
|
+
|
|
114
|
+
`BackgroundStateComputed` adds `hasBackground`, `isVideo`, `isYouTube`, `youtubeVideoId`, `posterUrl` (the proxied thumbnail). `hasBackground` is true when there is an image, a video, a color, an overlay, or visible noise; without it nothing renders.
|
|
115
|
+
|
|
116
|
+
### 5.2 Actions
|
|
117
|
+
|
|
118
|
+
| Action | Effect |
|
|
119
|
+
| :----------------------------- | :-------------------------------------------------------------------------------------------- |
|
|
120
|
+
| `setBackground(patchOrString)` | Merge a patch into the runtime override for this pathname. `videoOptions` are merged deeply. |
|
|
121
|
+
| `resetBackground()` | Drop the override. |
|
|
122
|
+
| `toggleVideo()` | Play/pause the real element (falls back to state when no element); keeps `isPlaying` in sync. |
|
|
123
|
+
| `toggleMute()` | Toggle `videoOptions.muted` and the element; unmuting also sets `isPlaying: true`. |
|
|
124
|
+
| `setVideoMuted(boolean)` | Same, explicit. |
|
|
125
|
+
| `toggleLoop()` | Toggle `videoOptions.loop` and the element. |
|
|
126
|
+
| `setVideoPlaying(boolean)` | Used by the player when the element's real state changes. |
|
|
127
|
+
| `setVideoElement(el \| null)` | Used by the player to publish the element (YouTube uses a proxy `<video>`). |
|
|
128
|
+
|
|
129
|
+
`page.modules.background` is these actions plus `set(background)` (replaces the page slice: `page.set({ background })`).
|
|
130
|
+
|
|
131
|
+
## 6. How a page registers a background
|
|
132
|
+
|
|
133
|
+
`usePage` runs `selectPageBackground(config.background)`: `null`/falsy → nothing registered; a string → `normalizeBackgroundInput` (`image`, or `video` when it is a YouTube/direct video URL); an object → used as is. The result is registered under the singleton key `page-background`. `mergeBackgroundState` then merges it over `DEFAULT_BACKGROUND` (deep-merging `videoOptions`, `imageStyle`, `videoStyle`, `noiseStyle`, `animation`).
|
|
134
|
+
|
|
135
|
+
Priority between several registrations of the same key follows the kernel rules (`static` < `dynamic` < `user`, then `priority`). For example a layout can register a default with `useBackgroundRegistration(bg, { source: "static" })` and pages override it.
|
|
136
|
+
|
|
137
|
+
## 7. Video playback details
|
|
138
|
+
|
|
139
|
+
- **Direct files** use a native `<video>`. On `loadeddata` the playback rate is applied and, when `autoplay` is on, the element is muted first (if `muted`) and played; a rejected `play()` is reported as a warning and leaves `isPlaying` false. When the video ends it restarts if `loop` is set, otherwise it pauses. `videoOptions.corp` (seconds, default 0) makes the video "end" that many seconds before its real end, which hides a bad last frame in loops. `startTime` / `endTime` apply to YouTube.
|
|
140
|
+
- **YouTube** is _not_ embedded by default. The module requests `/api/background/youtube?id=<id>&stream=video|audio|muxed|thumbnail|meta&quality=…&codec=…` and drives real `<video>`/`<audio>` elements, so muting, looping, speed and poster frames behave like a file. The route (`runtime = "nodejs"`, `force-dynamic`) resolves streams server-side, supports range requests and serves thumbnails. `stream=meta` returns JSON (author, duration, which streams exist). Bad ids return `400`, unresolvable videos `502`.
|
|
141
|
+
- **Autoplay policy:** browsers may block unmuted playback. A blocked autoplay leaves the video paused (`isPlaying` false) and the dock shows a play button. Audio is unlocked on the first user gesture.
|
|
142
|
+
- **Iframe fallback:** `forceIframe` loads the YouTube IFrame API and wraps it in a `<video>`-shaped proxy (`createYouTubeVideoElementProxy`) so the same actions work.
|
|
143
|
+
|
|
144
|
+
## 8. Media integration
|
|
145
|
+
|
|
146
|
+
While `isVideo` is true the provider registers the video with the `media` module as source `background` (`kind: "video"`) and with its own controls (`toggleVideo`, `toggleLoop`, `setVideoMuted`); it unregisters when the video goes away or the provider unmounts. The dock's controls, and any audio source that sets `follow: "background"`, operate through that registration — see [media.md](./media.md). If the `media` module is not installed the registration is an inert no-op.
|
|
147
|
+
|
|
148
|
+
```tsx
|
|
149
|
+
// Muted background video + a parallel audio track
|
|
150
|
+
usePage({
|
|
151
|
+
background: { video: "/video/city.mp4", videoOptions: { muted: true } },
|
|
152
|
+
});
|
|
153
|
+
return <MediaSource src="/audio/city.mp3" follow={BACKGROUND_MEDIA_ID} />;
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
## 9. Theming
|
|
157
|
+
|
|
158
|
+
All classes come from `backgroundTheme` (`src/config/background.module.theme.ts`): layer slots (`root`, `image`, `solid`, `gradient`, `edge`, `noise`), direct video slots (`video`, `videoFrame`) and YouTube player slots (`youtube*`). Variants are `data-*` modifiers on the slot (`data-side`, `data-position`, `data-width`, `data-visible`). `fit` and `bg-*` tokens are translated to inline `object-fit` / `object-position` by the module, not by theme slots. Stays in code: compositor hints (`will-change`), mask/gradient math (`generateBaseGradient`, `generateEdgeGradient`, `getEdgeFadeMask`), and motion (defaults derive from `DURATION_TOKENS` / `EASING_CURVES`, [Rule 4](../architecture-and-rules.md)). The module deliberately has **no presets** (a test bans `BACKGROUND_PRESETS`): pass a URL or a state object, or build your own with `defineBackground`.
|
|
159
|
+
|
|
160
|
+
## 10. Recipes and gotchas
|
|
161
|
+
|
|
162
|
+
| I want to… | Do this |
|
|
163
|
+
| :------------------------------------------- | :-------------------------------------------------------------------------------------------------------------------------------------- |
|
|
164
|
+
| A different background per route | Call `usePage({ background })` in each page. |
|
|
165
|
+
| Keep one background across routes | Register once in a layout (`useBackgroundRegistration`); do not set it per page. |
|
|
166
|
+
| Pause the video while a modal is open | `const { toggleVideo, isPlaying } = useBackground()` and call it from the modal. Prefer the `media` actions when audio may be involved. |
|
|
167
|
+
| Show a static fallback while the video loads | `showPoster: true` with `image` set (YouTube), or a `color`. |
|
|
168
|
+
| Hide the background temporarily | `page.set({ background: null })` or `setBackground({ overlay: true, overlayOpacity: 1 })`. |
|
|
169
|
+
|
|
170
|
+
- A runtime `setBackground` is lost on navigation by design; register it with `usePage` if it must persist.
|
|
171
|
+
- Changing `video` URL cross-fades; changing only `videoOptions` does not recreate the layer.
|
|
172
|
+
- `useBackground()` re-renders on every state change; use `useBackgroundActions()` when you only trigger things.
|
|
173
|
+
- `width` makes the video a framed element (with fades), not a full-bleed one; overlay/noise then apply to the frame, not the viewport.
|
|
174
|
+
|
|
175
|
+
## 11. File map
|
|
176
|
+
|
|
177
|
+
| File | Responsibility |
|
|
178
|
+
| :------------------ | :--------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
179
|
+
| `index.ts` | Public barrel. |
|
|
180
|
+
| `types.ts` | `BackgroundState`, `VideoOptions`, actions, computed state, YouTube controller/player types, theme slots. |
|
|
181
|
+
| `constants.ts` | `DEFAULT_BACKGROUND`, registry key, media id, token → slot maps, `backgroundTheme`. |
|
|
182
|
+
| `utils.ts` | Input normalization and merging, `selectPageBackground`, video option and class resolution, gradient/edge-fade/mask math, direct-video sync helpers. |
|
|
183
|
+
| `context.tsx` | `BackgroundProvider`: registry read, scoped override store, computed state store, actions, `media` registration; `useBackground*` hooks. |
|
|
184
|
+
| `hooks.ts` | View models: `useBackgroundOverlayModel`, `useNativeVideoModel`, `useYouTubeBackgroundPlayerModel`. |
|
|
185
|
+
| `overlay.tsx` | `BackgroundOverlay`, `NativeVideo`, `YouTubeBackgroundPlayer`, gradient / edge / noise / solid layers. |
|
|
186
|
+
| `motion.ts` | Default and custom motion resolution, CSS duration/easing helpers. |
|
|
187
|
+
| `module.tsx` | `backgroundModule`, page API (`set` + actions), `defineBackground`, kernel type augmentation. |
|
|
188
|
+
| `youtube/parse.ts` | URL/ID parsing, time params, stream/thumbnail URL builders, the `<video>` element proxy. |
|
|
189
|
+
| `youtube/iframe.ts` | Iframe API loader and player wrapper (fallback path). |
|
|
190
|
+
|
|
191
|
+
## 12. Dependencies
|
|
192
|
+
|
|
193
|
+
- **Uses:** `@omerdlw/base-framework/kernel` (`defineModule`, `definePeer`, `useModuleRegistration`, `useRegistryValue`), `@omerdlw/base-framework/hooks`, `@omerdlw/base-framework/utils`, `@omerdlw/base-framework/tokens`, `@omerdlw/base-framework/atoms` (`Spinner`), `@omerdlw/base-framework/theme`, `motion/react`, `next/navigation`; the route `src/app/api/background/youtube`.
|
|
194
|
+
- **Peer:** `media` (read through `definePeer`, listed in `uses`).
|
|
195
|
+
- **Used by:** `src/core/provider.tsx` (installs it) and every page that calls `usePage({ background })`; the dock and audio sources interact with it only through `media`.
|
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
# Context Menu — `@omerdlw/base-framework/modules/context-menu`
|
|
2
|
+
|
|
3
|
+
> **Read this first if you need to:** replace the browser's right-click menu, add menu entries for a page, a region, or a single element, give entries conditions/shortcuts/danger styling, or know how the "best" menu is chosen.
|
|
4
|
+
|
|
5
|
+
## 1. At a glance
|
|
6
|
+
|
|
7
|
+
| | |
|
|
8
|
+
| :----------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
9
|
+
| **Import** | `import { useContextMenu, defineContextMenu, … } from "@omerdlw/base-framework/modules/context-menu"` |
|
|
10
|
+
| **Module id** | `contextMenu` (camelCase: `contextMenuModule`; the theme id is `context-menu`) |
|
|
11
|
+
| **Renders** | `ContextMenuGlobal` overlay: one global `contextmenu` listener + one portalled menu |
|
|
12
|
+
| **Registry type** | `contextMenu` — key policy `route`, lifecycle `immediate` (entries are removed the moment the registering page unmounts; the declared 600 ms delay applies only if a registration opts into `graceful` / `route`), reserved keys `current-page` and `*` |
|
|
13
|
+
| **Page slice** | `usePage({ contextMenu })` → `page.modules.contextMenu` (`openMenu`, `closeMenu`, `bind`, `set`) |
|
|
14
|
+
| **Peers (`uses`)** | `dock` (the active dock card supplies the default menu header) |
|
|
15
|
+
| **Theme** | `contextMenuTheme` slots in `src/config/context-menu.module.theme.ts` |
|
|
16
|
+
| **Window event** | `context-menu:visibility` (`detail: { isOpen }`) |
|
|
17
|
+
| **z-index** | `Z_INDEX.CONTEXT_MENU_BACKDROP` / `Z_INDEX.CONTEXT_MENU` (set in the theme `styles`) |
|
|
18
|
+
| **Tests** | `tests/modules/context-menu.test.ts` |
|
|
19
|
+
|
|
20
|
+
## 2. Mental model
|
|
21
|
+
|
|
22
|
+
Menus are **declared, not mounted**. Anything can register a `ContextMenuConfig` (items, header, matching rules) in the registry. A **single global listener** (capture phase on `document`) intercepts every `contextmenu` event, scores all registered configs against the event, and opens the winner.
|
|
23
|
+
|
|
24
|
+
```
|
|
25
|
+
right-click ─▶ listener (document, capture)
|
|
26
|
+
│ for each registered config:
|
|
27
|
+
│ path allowed? enabled? when()? items non-empty? target selector matches?
|
|
28
|
+
│ score = priority×10000 + routeScore×100 + targetScore
|
|
29
|
+
└─ highest score wins ─▶ onOpen(event, ctx) ─▶ items resolved ─▶ menu at pointer
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
If **no** config matches, the event is left alone and the browser's native menu appears (except when an element-bound handler from `bind()` runs, see [§5.3](#53-binding-a-menu-to-an-element-with-a-payload)).
|
|
33
|
+
|
|
34
|
+
## 3. Quick start
|
|
35
|
+
|
|
36
|
+
```tsx
|
|
37
|
+
// Page-level menu, declarative
|
|
38
|
+
usePage({
|
|
39
|
+
contextMenu: {
|
|
40
|
+
items: [
|
|
41
|
+
{
|
|
42
|
+
key: "copy-link",
|
|
43
|
+
label: "Copy link",
|
|
44
|
+
icon: "solar:link-bold",
|
|
45
|
+
shortcut: "⌘L",
|
|
46
|
+
onSelect: () => navigator.clipboard.writeText(location.href),
|
|
47
|
+
},
|
|
48
|
+
"separator",
|
|
49
|
+
{ key: "report", label: "Report", danger: true, onSelect: openReport },
|
|
50
|
+
],
|
|
51
|
+
},
|
|
52
|
+
});
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
```ts
|
|
56
|
+
// A global menu available on every page (src/app/registry.tsx), key "*"
|
|
57
|
+
{ type: "contextMenu", items: { "*": { items: [{ key: "reload", label: "Reload", onSelect: () => location.reload() }] } } }
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
```tsx
|
|
61
|
+
// A region menu: only when the click is inside [data-post]; the payload is read from the DOM
|
|
62
|
+
useContextMenu({
|
|
63
|
+
target: "[data-post]",
|
|
64
|
+
resolvePayload: (event) =>
|
|
65
|
+
postsById[
|
|
66
|
+
(event.target as Element).closest<HTMLElement>("[data-post]")!.dataset
|
|
67
|
+
.post!
|
|
68
|
+
],
|
|
69
|
+
header: (ctx) => ({
|
|
70
|
+
title: ctx.payload.title,
|
|
71
|
+
description: ctx.payload.author,
|
|
72
|
+
}),
|
|
73
|
+
items: (ctx) => [
|
|
74
|
+
{
|
|
75
|
+
key: "delete",
|
|
76
|
+
label: "Delete",
|
|
77
|
+
danger: true,
|
|
78
|
+
onSelect: () => deletePost(ctx.payload.id),
|
|
79
|
+
},
|
|
80
|
+
],
|
|
81
|
+
});
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
## 4. Public API
|
|
85
|
+
|
|
86
|
+
| Export | Kind | Description |
|
|
87
|
+
| :-------------------------------------------------- | :-------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
88
|
+
| `contextMenuModule` | module | `defineModule` definition. |
|
|
89
|
+
| `ContextMenuProvider` | component | Menu state store, `openMenu` / `closeMenu` / `bind`, emits the visibility event. Mounted by the host. |
|
|
90
|
+
| `useContextMenu(config?, options?)` | hook | Registers `config` for this route (when given) **and** returns `state + actions`. Its `bind` has `config` pre-applied. `options`: `RegistryMetadata & { enabled? }`. |
|
|
91
|
+
| `useContextMenuActions()` | hook | Stable `{ openMenu, closeMenu, bind }`. Throws outside the provider. |
|
|
92
|
+
| `useContextMenuState()` | hook | `{ isOpen, config, context, items, position }`. |
|
|
93
|
+
| `useContextMenuRegistration(config, options?)` | hook | Register only (what `usePage` does). |
|
|
94
|
+
| `useContextMenuListener()` | hook | The global listener (used by `ContextMenuGlobal`; do not call it yourself). |
|
|
95
|
+
| `defineContextMenu(def)` | builder | `defineContextMenu({ id?, ...config }).use(overrides?)` — a reusable menu. |
|
|
96
|
+
| `ContextMenuContext` | context | For tests and custom providers. |
|
|
97
|
+
| `CONTEXT_MENU_VISIBILITY_EVENT`, `contextMenuTheme` | constants | |
|
|
98
|
+
| Types | | `ContextMenuConfig`, `ContextMenuItem`, `ContextMenuContextValue`, `ContextMenuActions`, `ContextMenuState`, `ContextMenuHeaderConfig`, `ContextMenuClassNames`, `ContextMenuPageConfig`, … |
|
|
99
|
+
|
|
100
|
+
### 4.1 `ContextMenuConfig`
|
|
101
|
+
|
|
102
|
+
| Field | Meaning |
|
|
103
|
+
| :-------------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
104
|
+
| `items` | Array of items / `"separator"`, **or** a function of the context returning that array. |
|
|
105
|
+
| `target` | CSS selector (or array). The menu applies only if the right-clicked element is inside a match (`element.closest`). Closer matches score higher. No selector = applies anywhere. |
|
|
106
|
+
| `path`, `paths`, `pathMatcher(path)` | Restrict to routes. If none match and the registry key is neither the pathname, `current-page` nor `*`, the config is skipped. |
|
|
107
|
+
| `priority` | Number (default 0). Dominates scoring: higher priority beats any route/target advantage. |
|
|
108
|
+
| `enabled` | `boolean \| (context) => boolean`. |
|
|
109
|
+
| `when` | `boolean \| (event, { pathname, target, context }) => boolean`; errors count as `false`. |
|
|
110
|
+
| `payload`, `resolvePayload(event, ctx)` | Data exposed as `context.payload`. |
|
|
111
|
+
| `resolveContext(event, ctx)` | Returns extra fields merged into the context. |
|
|
112
|
+
| `onOpen(event, ctx)` | Called just before opening. Return `false` to cancel, an object to merge into the context. |
|
|
113
|
+
| `onClose(ctx)` | Called when the menu closes. |
|
|
114
|
+
| `header` | `{ title, description, eyebrow, icon }`, a function of the context returning that, or `false`. |
|
|
115
|
+
| `showPageHeader` | `false` removes the default page header (see below). |
|
|
116
|
+
| `classNames` | Per-slot class additions (`backdrop`, `menu`, `header*`, `separator`, `item`, `itemIcon`, `itemLabel`, `itemShortcut`). |
|
|
117
|
+
| `menus` | Nested menu configs that inherit the parent's fields; the parent itself is a candidate only if it has its own `items`. |
|
|
118
|
+
| `id` | Identifier (for `defineContextMenu`). |
|
|
119
|
+
|
|
120
|
+
### 4.2 `ContextMenuItem`
|
|
121
|
+
|
|
122
|
+
| Field | Meaning |
|
|
123
|
+
| :--------------------------------------------- | :----------------------------------------------------------------------------------------------- |
|
|
124
|
+
| `key` | Stable key (default `item-<index>`). |
|
|
125
|
+
| `label` | **Required** — string/number or function of the context. An empty/non-text label drops the item. |
|
|
126
|
+
| `icon` | Iconify string or node. `itemIconClassName` styles it. |
|
|
127
|
+
| `shortcut` | Display string (or function). It is only a label; bind the actual key yourself. |
|
|
128
|
+
| `danger`, `disabled`, `hidden`, `visible` | `boolean` or function of the context. `hidden: true` / `visible: false` remove the item. |
|
|
129
|
+
| `closeOnSelect` | Default `true`; `false` keeps the menu open. |
|
|
130
|
+
| `className` | Extra classes for this item. |
|
|
131
|
+
| `onSelect(event, ctx)` / `onClick(event, ctx)` | Handler (`onSelect` wins). May be async: rejections and exceptions are **reported, not thrown**. |
|
|
132
|
+
| `type: "separator"` | Same as the string `"separator"`. |
|
|
133
|
+
|
|
134
|
+
Resolution cleans the list: leading, trailing and duplicate separators are removed, hidden items disappear, a menu with no items is **not shown**.
|
|
135
|
+
|
|
136
|
+
### 4.3 The context object
|
|
137
|
+
|
|
138
|
+
Passed to every function (`items`, `label`, `when`, handlers…):
|
|
139
|
+
|
|
140
|
+
```ts
|
|
141
|
+
{ pathname, point: { x, y }, event, target, currentTarget, payload?, page?, ...resolveContext() }
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
`page` is the default header source: `{ title, description, eyebrow, icon, path, titleText, descriptionText }` taken from the **active dock card** (not when a surface is open). A dock card can steer it with `contextMenuTitle`, `contextMenuDescription`, `contextMenuEyebrow`, `contextMenuIcon`.
|
|
145
|
+
|
|
146
|
+
## 5. Behaviour
|
|
147
|
+
|
|
148
|
+
### 5.1 How the winner is chosen
|
|
149
|
+
|
|
150
|
+
For every candidate (a registered config, or each nested `menus` entry):
|
|
151
|
+
|
|
152
|
+
1. **Path** — allowed if `config.path` equals the pathname; else, if `paths` is set, it must include it; else `pathMatcher(pathname)`; else the registry key must be the pathname, `current-page`, or `*`.
|
|
153
|
+
2. **Enabled / when / items** — `enabled` (default true), `when` (default true) and at least one resolved item.
|
|
154
|
+
3. **Target** — with `target` selectors the click must be inside a match, else the candidate is skipped.
|
|
155
|
+
4. **Score** — `priority × 10000 + routeScore × 100 + targetScore`:
|
|
156
|
+
|
|
157
|
+
| Part | Values |
|
|
158
|
+
| :------------ | :---------------------------------------------------------------------------------------------------------------------- |
|
|
159
|
+
| `routeScore` | `100` exact path (`path`, `paths`, or key = pathname); `70` key `current-page`; `40` key `*`; `10` otherwise (matcher). |
|
|
160
|
+
| `targetScore` | `0` without selector; `100 − depth` with a match (`depth` = ancestors between the clicked element and the matched one). |
|
|
161
|
+
|
|
162
|
+
Ties keep the first registered candidate.
|
|
163
|
+
|
|
164
|
+
Right-clicks that land on the menu's own overlay look through to the element underneath (`elementsFromPoint`), so a second right-click works as expected. Elements marked `data-context-menu-ignore` / `data-context-menu-overlay` (and `role="menu"`) are treated as part of the menu for this purpose: resolution uses the element underneath. They do **not** switch the module off.
|
|
165
|
+
|
|
166
|
+
### 5.2 Registry keys (where configs live)
|
|
167
|
+
|
|
168
|
+
| Source | Key |
|
|
169
|
+
| :------------------------------------------------------ | :---------------------------------------------------------------------- |
|
|
170
|
+
| `usePage({ contextMenu })` | the current **pathname** |
|
|
171
|
+
| `useContextMenuRegistration` / `useContextMenu(config)` | the current pathname (page-scoped, removed immediately on route change) |
|
|
172
|
+
| static entries in `src/app/registry.tsx` | any path, `current-page`, or `*` for global |
|
|
173
|
+
|
|
174
|
+
### 5.3 Binding a menu to an element with a payload
|
|
175
|
+
|
|
176
|
+
`bind(payload?, configOverride?)` returns `{ onContextMenu }` for a trigger element. It prevents the native menu, builds the context with your `payload`, and opens the menu from `configOverride` (or the last open config).
|
|
177
|
+
|
|
178
|
+
```tsx
|
|
179
|
+
const { bind } = useContextMenuActions();
|
|
180
|
+
<li
|
|
181
|
+
{...bind(item, {
|
|
182
|
+
items: (ctx) => [
|
|
183
|
+
{
|
|
184
|
+
key: "open",
|
|
185
|
+
label: "Open",
|
|
186
|
+
onSelect: () => open((ctx.payload as Item).id),
|
|
187
|
+
},
|
|
188
|
+
],
|
|
189
|
+
})}
|
|
190
|
+
>
|
|
191
|
+
…
|
|
192
|
+
</li>;
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
Important: the global capture listener runs **before** React's element handler. If a registered config also matches that element, the global listener opens it and the element handler never runs — and `context.payload` will be missing in that path. So for payload-driven menus either **do not register** a competing config (use `bind` with an explicit config as above) or read the data in `resolvePayload`.
|
|
196
|
+
|
|
197
|
+
### 5.4 Opening and dismissal
|
|
198
|
+
|
|
199
|
+
- Position is the pointer, clamped to keep a 10 px margin inside the viewport.
|
|
200
|
+
- While open the page is non-interactive: a full-screen overlay catches clicks, wheel/touch scrolling and scroll keys are blocked.
|
|
201
|
+
- Closes on: click outside, `Escape`, selecting an item (unless `closeOnSelect: false`), or `closeMenu()`.
|
|
202
|
+
- Keyboard: `↑` / `↓` move through enabled actions (wrapping, skipping separators and disabled items), `Enter` / `Space` select, the menu container is focused first.
|
|
203
|
+
- Header: shown when `header` is set, or when the active dock card provides page metadata, unless `header: false` / `showPageHeader: false`.
|
|
204
|
+
|
|
205
|
+
### 5.5 Visibility event
|
|
206
|
+
|
|
207
|
+
`window` receives `context-menu:visibility` with `detail: { isOpen }` whenever the menu opens or closes (use it to pause scroll-hijacking libraries or global shortcuts).
|
|
208
|
+
|
|
209
|
+
## 6. Theming
|
|
210
|
+
|
|
211
|
+
No classes or colors in the module, except the pointer-following `left/top/position: fixed`. Slots in `contextMenuTheme`: `backdrop`, `menu`, `header`, `headerIcon`, `headerText`, `headerEyebrow`, `headerTitle`, `headerDescription`, `separator`, `item`, `itemIcon`, `itemLabel`, `itemShortcut`, plus optional per-slot `styles` (the starter theme sets the backdrop/menu z-index there). A config's `classNames` (and an item's `className`) are appended on top of the theme. Active items get `data-active="true"`, handy for theme selectors. Motion (`menuPopVariants`, `menuContentVariants`, `menuItemVariants`, tap/spring) lives in `motion.ts`.
|
|
212
|
+
|
|
213
|
+
## 7. Recipes and gotchas
|
|
214
|
+
|
|
215
|
+
| I want to… | Do this |
|
|
216
|
+
| :------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
217
|
+
| One menu for the whole app | Register `"*"` in `src/app/registry.tsx`. |
|
|
218
|
+
| Override the app menu on a page | `usePage({ contextMenu })` (route score 100 beats `*`'s 40), or raise `priority`. |
|
|
219
|
+
| A menu only on certain elements | `target: "[data-x]"`. |
|
|
220
|
+
| Hide/disable items conditionally | `hidden` / `disabled` functions of the context. |
|
|
221
|
+
| Let the native menu through | Only possible where **no** config matches (use `when` / `target` to scope your menus). Returning `false` from `onOpen` cancels _your_ menu but the native one stays suppressed. |
|
|
222
|
+
| Open programmatically | `openMenu({ config, context, items, position })` (items must already be resolved) — normally prefer `bind`. |
|
|
223
|
+
|
|
224
|
+
- A label that resolves to empty text silently removes the item (useful for conditional labels, surprising for typos).
|
|
225
|
+
- Menus without items are never shown, even if they match everything else.
|
|
226
|
+
- Handlers should not assume the menu is still mounted: it closes before async work finishes.
|
|
227
|
+
|
|
228
|
+
## 8. File map
|
|
229
|
+
|
|
230
|
+
| File | Responsibility |
|
|
231
|
+
| :------------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
232
|
+
| `index.ts` | Public barrel. |
|
|
233
|
+
| `types.ts` | Config, item, header, context, state, actions, page and theme-slot contracts. |
|
|
234
|
+
| `constants.ts` | Registry keys (`current-page`, `*`), screen margin, visibility event, `contextMenuTheme`. |
|
|
235
|
+
| `utils.ts` | Value resolution, item/header resolution, page metadata from the dock card, safe invocation, positioning, keyboard index helper, visibility event emitter. |
|
|
236
|
+
| `resolver.ts` | Candidate normalization, path/target/route scoring, context building, `resolveContextMenu`, `prepareMenu`. |
|
|
237
|
+
| `state.ts` | Initial and next-open state helpers. |
|
|
238
|
+
| `context.tsx` | `ContextMenuProvider` (store, `openMenu`, `closeMenu`, `bind`), `useContextMenu*` hooks. |
|
|
239
|
+
| `hooks.ts` | `useContextMenuListener`, dismissal, content view model (keyboard, focus, class merging). |
|
|
240
|
+
| `overlay.tsx` | `ContextMenuGlobal`, renderer, content, header, item components. |
|
|
241
|
+
| `motion.ts` | Pop, content and item variants; tap and spring presets. |
|
|
242
|
+
| `module.tsx` | `contextMenuModule`, page API, `defineContextMenu`, kernel type augmentation. |
|
|
243
|
+
|
|
244
|
+
## 9. Dependencies
|
|
245
|
+
|
|
246
|
+
- **Uses:** `@omerdlw/base-framework/kernel` (`defineModule`, `useModuleRegistration`, `useRegistryEntries`, `useModuleState`), `@omerdlw/base-framework/utils`, `@omerdlw/base-framework/hooks`, `@omerdlw/base-framework/atoms` (`Icon`), `@omerdlw/base-framework/tokens`, `@omerdlw/base-framework/theme`, `next/navigation`, `motion/react`.
|
|
247
|
+
- **Peer:** `dock` — read with `useModuleState("dock", selectDockPageCard, null)`; works (without a default header) when the dock is not installed.
|
|
248
|
+
- **Used by:** `src/core/provider.tsx` (installs it), `page.modules.contextMenu`, and features that register menus.
|