@wildo-ai/presets-components-3d 1.1.6 → 1.1.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/esm/focal-plane/focal-plane-settings.d.ts +35 -0
- package/dist/esm/focal-plane/focal-plane-settings.d.ts.map +1 -0
- package/dist/esm/focal-plane/focal-plane-settings.js +68 -0
- package/dist/esm/focal-plane/focal-plane-settings.js.map +1 -0
- package/dist/esm/focal-plane/focal-plane-shell-container.d.ts +13 -0
- package/dist/esm/focal-plane/focal-plane-shell-container.d.ts.map +1 -0
- package/dist/esm/focal-plane/focal-plane-shell-container.js +147 -0
- package/dist/esm/focal-plane/focal-plane-shell-container.js.map +1 -0
- package/dist/esm/focal-plane/focal-plane-stylesheet.d.ts +14 -0
- package/dist/esm/focal-plane/focal-plane-stylesheet.d.ts.map +1 -0
- package/dist/esm/focal-plane/focal-plane-stylesheet.js +236 -0
- package/dist/esm/focal-plane/focal-plane-stylesheet.js.map +1 -0
- package/dist/esm/focal-plane/use-pane-depth.d.ts +20 -0
- package/dist/esm/focal-plane/use-pane-depth.d.ts.map +1 -0
- package/dist/esm/focal-plane/use-pane-depth.js +162 -0
- package/dist/esm/focal-plane/use-pane-depth.js.map +1 -0
- package/dist/esm/focal-plane/use-shared-vanishing-point.d.ts +35 -0
- package/dist/esm/focal-plane/use-shared-vanishing-point.d.ts.map +1 -0
- package/dist/esm/focal-plane/use-shared-vanishing-point.js +86 -0
- package/dist/esm/focal-plane/use-shared-vanishing-point.js.map +1 -0
- package/dist/esm/frame/frame-canvas.js +3 -3
- package/dist/esm/frame/frame-canvas.js.map +1 -1
- package/dist/esm/index.d.ts +1 -0
- package/dist/esm/index.d.ts.map +1 -1
- package/dist/esm/index.js +1 -0
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/layout/layout-3d-definition.d.ts +5 -6
- package/dist/esm/layout/layout-3d-definition.d.ts.map +1 -1
- package/dist/esm/layout/layout-3d-definition.js +13 -14
- package/dist/esm/layout/layout-3d-definition.js.map +1 -1
- package/dist/esm/layout/layout-3d-vocabulary.d.ts +13 -8
- package/dist/esm/layout/layout-3d-vocabulary.d.ts.map +1 -1
- package/dist/esm/layout/layout-3d-vocabulary.js +12 -7
- package/dist/esm/layout/layout-3d-vocabulary.js.map +1 -1
- package/dist/esm/layout-3d-main-container.d.ts +6 -0
- package/dist/esm/layout-3d-main-container.d.ts.map +1 -1
- package/dist/esm/layout-3d-main-container.js +8 -1
- package/dist/esm/layout-3d-main-container.js.map +1 -1
- package/dist/tsconfig.build.tsbuildinfo +1 -0
- package/package.json +3 -3
- package/dist/esm/spatial/spatial-camera.d.ts +0 -106
- package/dist/esm/spatial/spatial-camera.d.ts.map +0 -1
- package/dist/esm/spatial/spatial-camera.js +0 -151
- package/dist/esm/spatial/spatial-camera.js.map +0 -1
- package/dist/esm/spatial/spatial-environment.d.ts +0 -6
- package/dist/esm/spatial/spatial-environment.d.ts.map +0 -1
- package/dist/esm/spatial/spatial-environment.js +0 -166
- package/dist/esm/spatial/spatial-environment.js.map +0 -1
- package/dist/esm/spatial/spatial-environment.lazy.d.ts +0 -10
- package/dist/esm/spatial/spatial-environment.lazy.d.ts.map +0 -1
- package/dist/esm/spatial/spatial-environment.lazy.js +0 -13
- package/dist/esm/spatial/spatial-environment.lazy.js.map +0 -1
- package/dist/esm/spatial/spatial-shell-container.d.ts +0 -21
- package/dist/esm/spatial/spatial-shell-container.d.ts.map +0 -1
- package/dist/esm/spatial/spatial-shell-container.js +0 -231
- package/dist/esm/spatial/spatial-shell-container.js.map +0 -1
- package/dist/esm/spatial/spatial-stage-store.d.ts +0 -27
- package/dist/esm/spatial/spatial-stage-store.d.ts.map +0 -1
- package/dist/esm/spatial/spatial-stage-store.js +0 -33
- package/dist/esm/spatial/spatial-stage-store.js.map +0 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"focal-plane-stylesheet.js","sourceRoot":"","sources":["../../../../src/focal-plane/focal-plane-stylesheet.ts"],"names":[],"mappings":"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","sourcesContent":["import { ShellRegion } from '@wildo-ai/presets-components-models';\nimport { NavigationZone } from '@wildo-ai/saas-frontend-lib';\n\n/**\n * The focal plane's stylesheet (#2049), scoped to `[data-focal-plane]`, so it touches nothing outside the layout.\n *\n * ## One still camera, without a nested 3D context\n *\n * Every object carries its OWN `perspective(d) translate3d(0, 0, z)`, with its `transform-origin` set (per element, by\n * `useSharedVanishingPoint`) to the one vanishing point of the stage. That is the projection of a single still camera,\n * and it needs no `transform-style: preserve-3d` ancestor — which matters, because a nested 3D context disables\n * `backdrop-filter`, and the glass windows are made of it.\n *\n * ## What counts as an object\n *\n * - the shell panels this layout wraps (`[data-fp-object]`);\n * - each navigation pane inside the content region, by the engine's own markers: `[data-content-area]` (the static and\n * single-zone branches) and the resizable panel `[data-panel-id^=\"nav-\"]` (NOT its inner `[data-content-area]`, which\n * the panel's inline `overflow: hidden` would clip).\n *\n * Depth is a custom property (`--fp-z`); states set it, and the transform reads it, so rules compose instead of fighting\n * over `transform`.\n *\n * ## Why a constructable stylesheet\n *\n * It is installed through `document.adoptedStyleSheets`, which a content security policy without `'unsafe-inline'`\n * does not block, where a `<style>` element would be.\n */\nconst ROOT = '[data-focal-plane]';\nconst CONTENT = `[data-component-area=\"${ShellRegion.CONTENT}\"]`;\n/**\n * The navigation panes, by the engine's markers: the zone itself in the static and single-zone branches, the panel in\n * the resizable branch — never the zone INSIDE a panel, which would be moved twice.\n */\nexport const FOCAL_PLANE_PANE_SELECTOR = `${CONTENT} [data-content-area]:not([data-panel-id] *), ${CONTENT} [data-panel-id^=\"nav-\"]`;\n/** The zone names the navigation state focuses, and the markers each one is found by in either branch. */\nconst ZONES = [NavigationZone.PRIMARY, NavigationZone.SECONDARY, NavigationZone.COMPANION] as const;\nconst zoneMarker = (zone: (typeof ZONES)[number]): string => `:is([data-content-area=\"${zone}\"], [data-panel-id=\"nav-${zone}\"])`;\nconst VISIBLE_PANE = `:is(${FOCAL_PLANE_PANE_SELECTOR}):not([data-zone-hidden])`;\n/** Two panes or more are side by side (the other panes are siblings in both branches). */\nconst SPLIT = `${CONTENT}:has(${VISIBLE_PANE} ~ ${VISIBLE_PANE})`;\nconst PANES = `:is(${FOCAL_PLANE_PANE_SELECTOR})`;\nconst OBJECTS = `:is([data-fp-object], ${FOCAL_PLANE_PANE_SELECTOR})`;\n/**\n * The popups the focal plane moves, by the stable `data-slot` of their portalled content (Radix `data-state` on the\n * same element). Lifting popups carry the CSS variable Radix publishes as their trigger-relative origin.\n */\nconst POPUPS = {\n lift: [\n ['dropdown-menu-content', '--radix-dropdown-menu-content-transform-origin'],\n ['dropdown-menu-sub-content', '--radix-dropdown-menu-content-transform-origin'],\n ['context-menu-content', '--radix-context-menu-content-transform-origin'],\n ['context-menu-sub-content', '--radix-context-menu-content-transform-origin'],\n ['popover-content', '--radix-popover-content-transform-origin'],\n ['hover-card-content', '--radix-hover-card-content-transform-origin'],\n ['tooltip-content', '--radix-tooltip-content-transform-origin'],\n ['select-content', '--radix-select-content-transform-origin'],\n ['menubar-content', '--radix-menubar-content-transform-origin'],\n ['menubar-sub-content', '--radix-menubar-content-transform-origin'],\n ],\n rise: ['dialog-content', 'alert-dialog-content'],\n} as const;\n/**\n * A MODAL popup is open: a dialog, an alert dialog, a sheet or a drawer, by their content SLOT. Not by `role=\"dialog\"`:\n * a Radix popover's content carries that role too (date pickers, linked-record pickers, the notification centre), and\n * opening one must not step the form you are editing back. Not by `body[data-scroll-locked]` either, which dropdown\n * and context menus also set.\n */\nconst MODAL_SLOTS = ['dialog-content', 'alert-dialog-content', 'sheet-content', 'drawer-content'] as const;\nconst MODAL_OPEN = `:has([data-shell-overlay-layer] :is(${MODAL_SLOTS.map((slot) => `[data-slot=\"${slot}\"]`).join(', ')})[data-state=\"open\"])`;\n\n/** The modal content slots, exported for the tests that pin what counts as modal. */\nexport const FOCAL_PLANE_MODAL_SLOTS: readonly string[] = MODAL_SLOTS;\n\nexport const FOCAL_PLANE_CSS = `\n/* Positioned, never isolated: an isolated root would be one stacking context, and anything positioned outside the\n shell with a lower z-index (a consent prompt) would paint over the shell's own dialogs (#1830). */\n${ROOT} { position: relative; height: 100vh; width: 100vw; overflow: hidden; background: var(--background); }\n\n/* The ambient background: soft fields of the theme's own colours, drifting very slowly. */\n${ROOT} .fp-backdrop { position: absolute; inset: -20%; z-index: 0; pointer-events: none; }\n${ROOT} .fp-backdrop i { position: absolute; border-radius: 50%; filter: blur(70px); opacity: 0.8; }\n${ROOT} .fp-backdrop .fp-a { width: 58%; height: 62%; left: 6%; top: 4%; background: radial-gradient(closest-side, color-mix(in oklab, var(--primary) 38%, var(--background)), transparent); animation: fp-drift-a 48s ease-in-out infinite alternate; }\n${ROOT} .fp-backdrop .fp-b { width: 52%; height: 58%; right: 4%; top: 30%; background: radial-gradient(closest-side, color-mix(in oklab, var(--accent, var(--primary)) 30%, var(--background)), transparent); animation: fp-drift-b 61s ease-in-out infinite alternate; }\n${ROOT} .fp-backdrop .fp-c { width: 46%; height: 50%; left: 34%; bottom: 0; background: radial-gradient(closest-side, color-mix(in oklab, var(--primary) 22%, var(--foreground) 8%), transparent); animation: fp-drift-c 55s ease-in-out infinite alternate; }\n@keyframes fp-drift-a { to { transform: translate(9%, 7%) scale(1.08); } }\n@keyframes fp-drift-b { to { transform: translate(-8%, -6%) scale(0.94); } }\n@keyframes fp-drift-c { to { transform: translate(-6%, -9%) scale(1.1); } }\n\n/* The arrangement: floating windows with a margin and gaps, never edge to edge. */\n${ROOT} .fp-grid { position: absolute; inset: 0; z-index: 1; display: flex; flex-direction: column; gap: 12px; padding: 14px; }\n${ROOT} .fp-row { display: flex; gap: 12px; min-height: 0; min-width: 0; }\n${ROOT} .fp-middle { flex: 1; }\n${ROOT} .fp-column { flex: 1; display: flex; flex-direction: column; gap: 12px; min-width: 0; min-height: 0; }\n${ROOT} .fp-content { flex: 1; min-height: 0; min-width: 0; position: relative; display: flex; flex-direction: column; }\n${ROOT} [data-fp-object]:empty { display: none; }\n${ROOT} [data-fp-object] { position: relative; display: flex; min-height: 0; min-width: 0; }\n${ROOT} [data-fp-object] > * { flex: 1; min-width: 0; min-height: 0; }\n\n/* Every object: seen through the one still camera, at its depth. */\n${ROOT} ${OBJECTS} {\n transform-origin: var(--fp-ox, 50%) var(--fp-oy, 50%);\n transform: perspective(var(--fp-distance)) translate3d(0, 0, var(--fp-z, 0px));\n filter: var(--fp-filter, none);\n transition: transform var(--fp-motion) var(--fp-ease), filter var(--fp-motion) var(--fp-ease), box-shadow var(--fp-motion) var(--fp-ease);\n}\n\n/* Glass: the theme's card colour, translucent, over the background blurred through it. */\n${ROOT} :is([data-fp-object=\"shell\"], ${FOCAL_PLANE_PANE_SELECTOR}) {\n background: color-mix(in oklab, var(--card, var(--background)) var(--fp-glass), transparent);\n -webkit-backdrop-filter: blur(26px) saturate(1.5);\n backdrop-filter: blur(26px) saturate(1.5);\n border: 1px solid color-mix(in oklab, var(--border) 55%, transparent);\n border-radius: 14px;\n box-shadow: 0 18px 44px -22px rgb(0 0 0 / 0.30), 0 1px 3px rgb(0 0 0 / 0.05);\n}\n${ROOT} ${CONTENT} { background: transparent; }\n${ROOT} ${CONTENT} :is([data-content-area=\"${NavigationZone.SECONDARY}\"], [data-content-area=\"${NavigationZone.COMPANION}\"]):not([data-panel-id] *) { margin-left: 12px; }\n\n/* Side by side, the pane you work in rests on the focal plane; every other pane steps back and dims. The focused\n zone comes from the navigation state, written on the root as data-fp-focus. Clicking a pane focuses it (the\n engine does that), which brings it forward. */\n${ZONES.map((zone) => `${ROOT}[data-fp-focus=\"${zone}\"] ${SPLIT} ${VISIBLE_PANE}:not(${zoneMarker(zone)})`).join(', ')} {\n --fp-z: calc(var(--fp-step) * -0.55);\n --fp-filter: brightness(calc(1 - var(--fp-dim) * 0.55)) saturate(calc(1 - var(--fp-dim) * 0.5)) blur(calc(var(--fp-recede-blur) * 0.55));\n}\n\n/* A side pane arrives from depth when it opens. */\n${ROOT} ${CONTENT} :is(${zoneMarker(NavigationZone.SECONDARY)}, ${zoneMarker(NavigationZone.COMPANION)}):is(${FOCAL_PLANE_PANE_SELECTOR}) {\n animation: fp-pane-arrive var(--fp-motion) var(--fp-ease);\n}\n@keyframes fp-pane-arrive {\n from { transform: perspective(var(--fp-distance)) translate3d(0, 0, calc(var(--fp-step) * -1.2)); opacity: 0; }\n}\n\n/* Ghosts: a discarded pane flying away, and the still image of the panes an expanded pane stands in front of. */\n${ROOT} .fp-ghosts { position: absolute; inset: 0; z-index: 0; pointer-events: none; }\n${ROOT} .fp-ghosts-front { z-index: 2; }\n${ROOT} .fp-ghost {\n position: absolute; box-sizing: border-box; overflow: hidden;\n transform-origin: var(--fp-ox) var(--fp-oy);\n transform: perspective(var(--fp-distance)) translate3d(0, 0, var(--fp-z, 0px));\n filter: var(--fp-filter, none);\n transition: transform var(--fp-motion) var(--fp-ease), filter var(--fp-motion) var(--fp-ease), opacity var(--fp-motion) var(--fp-ease);\n background: color-mix(in oklab, var(--card, var(--background)) var(--fp-glass), transparent);\n border: 1px solid color-mix(in oklab, var(--border) 55%, transparent);\n border-radius: 14px;\n box-shadow: 0 18px 44px -22px rgb(0 0 0 / 0.30);\n}\n\n/* The shell panel under the pointer rises toward the camera; its items zoom a little. */\n${ROOT} [data-fp-object=\"shell\"]:hover { --fp-z: var(--fp-hover-lift); }\n${ROOT} [data-fp-object=\"shell\"] :is(button, a):not(:disabled) { transition: transform 180ms var(--fp-ease); }\n${ROOT} [data-fp-object=\"shell\"] :is(button, a):not(:disabled):hover { transform: scale(1.045); }\n\n/* Behind a modal popup, everything steps back, dims and blurs lightly. A menu is not modal: it only lifts. */\n${ROOT}${MODAL_OPEN} ${OBJECTS} {\n --fp-z: calc(var(--fp-step) * -0.6);\n --fp-filter: brightness(calc(1 - var(--fp-dim) * 0.6)) saturate(0.9) blur(var(--fp-modal-blur));\n}\n\n/* Popups are objects too. Their recipe animations (tw-animate's enter/exit) are replaced by depth KEYFRAMES —\n keyframes, because the overlay library keeps a closing popup mounted only until its exit ANIMATION ends; a\n transition would be cut off. Each popup rests at its true size: depth shows while it travels.\n - A menu, popover or tooltip lifts off its trigger: the library publishes the trigger point as the transform\n origin, and perspective() grows the popup out of it.\n - A dialog (the command palette is one) rises from depth to the focal plane.\n - A sheet arrives from in front of the plane and leaves back into depth.\n Drawers and toasts keep their own motion: their libraries position and stack them with transforms. */\n${POPUPS.lift.map(([slot, origin]) => `${ROOT} [data-shell-overlay-layer] [data-slot=\"${slot}\"] { transform-origin: var(${origin}); }`).join('\\n')}\n${ROOT} [data-shell-overlay-layer] :is(${POPUPS.lift.map(([slot]) => `[data-slot=\"${slot}\"]`).join(', ')}):is([data-state=\"open\"], [data-state=\"delayed-open\"], [data-state=\"instant-open\"]) { animation: fp-lift-in calc(var(--fp-motion) * 0.6) var(--fp-ease) both; }\n${ROOT} [data-shell-overlay-layer] :is(${POPUPS.lift.map(([slot]) => `[data-slot=\"${slot}\"]`).join(', ')})[data-state=\"closed\"] { animation: fp-lift-out calc(var(--fp-motion) * 0.45) var(--fp-ease) both; }\n${ROOT} [data-shell-overlay-layer] :is(${POPUPS.rise.map((slot) => `[data-slot=\"${slot}\"]`).join(', ')})[data-state=\"open\"] { animation: fp-rise-in var(--fp-motion) var(--fp-ease) both; }\n${ROOT} [data-shell-overlay-layer] :is(${POPUPS.rise.map((slot) => `[data-slot=\"${slot}\"]`).join(', ')})[data-state=\"closed\"] { animation: fp-rise-out calc(var(--fp-motion) * 0.7) var(--fp-ease) both; }\n${ROOT} [data-shell-overlay-layer] [data-slot=\"sheet-content\"][data-state=\"open\"] { animation: fp-arrive-in var(--fp-motion) var(--fp-ease) both; }\n${ROOT} [data-shell-overlay-layer] [data-slot=\"sheet-content\"][data-state=\"closed\"] { animation: fp-arrive-out calc(var(--fp-motion) * 0.7) var(--fp-ease) both; }\n/* Popups are glass, a little more opaque than windows (they carry text you act on at once). Tooltips keep their own\n inverse colours. */\n${ROOT} [data-shell-overlay-layer] :is(${[...POPUPS.lift.filter(([slot]) => slot !== 'tooltip-content').map(([slot]) => slot), ...POPUPS.rise, 'sheet-content'].map((slot) => `[data-slot=\"${slot}\"]`).join(', ')}) {\n background: color-mix(in oklab, var(--popover, var(--card, var(--background))) calc(var(--fp-glass) + 26%), transparent);\n -webkit-backdrop-filter: blur(24px) saturate(1.5);\n backdrop-filter: blur(24px) saturate(1.5);\n border-color: color-mix(in oklab, var(--border) 60%, transparent);\n}\n@keyframes fp-lift-in { from { transform: perspective(var(--fp-distance)) translate3d(0, 0, calc(var(--fp-step) * -0.35)); opacity: 0; } to { transform: none; opacity: 1; } }\n@keyframes fp-lift-out { from { transform: none; opacity: 1; } to { transform: perspective(var(--fp-distance)) translate3d(0, 0, calc(var(--fp-step) * -0.35)); opacity: 0; } }\n@keyframes fp-rise-in { from { transform: perspective(var(--fp-distance)) translate3d(0, 0, calc(var(--fp-step) * -1.4)); opacity: 0; } to { transform: none; opacity: 1; } }\n@keyframes fp-rise-out { from { transform: none; opacity: 1; } to { transform: perspective(var(--fp-distance)) translate3d(0, 0, calc(var(--fp-step) * -1.4)); opacity: 0; } }\n@keyframes fp-arrive-in { from { transform: perspective(var(--fp-distance)) translate3d(0, 0, calc(var(--fp-step) * 0.9)); opacity: 0; } to { transform: none; opacity: 1; } }\n@keyframes fp-arrive-out { from { transform: none; opacity: 1; } to { transform: perspective(var(--fp-distance)) translate3d(0, 0, calc(var(--fp-step) * -0.5)); opacity: 0; } }\n\n/* The modal scrim: in the focal plane, stepping back and the light blur above ARE the scrim, so the engine's dark\n overlay (and its own backdrop blur) would double it into a heavy grey. Keep the overlay element (it still catches\n the outside click), just transparent. */\n${ROOT} [data-shell-overlay-layer] :is([data-slot=\"dialog-overlay\"], [data-slot=\"alert-dialog-overlay\"], [data-slot=\"sheet-overlay\"], [data-slot=\"drawer-overlay\"]) {\n background: transparent;\n -webkit-backdrop-filter: none;\n backdrop-filter: none;\n}\n\n@media (prefers-reduced-motion: reduce) {\n /* !important: the motion rules above are more specific (the drifting fields, the item zoom), and the reader's\n setting must win over every one of them. */\n ${ROOT} ${OBJECTS}, ${ROOT} .fp-ghost, ${ROOT} [data-fp-object=\"shell\"] :is(button, a), ${ROOT} [data-shell-overlay-layer] [data-slot] { transition-duration: 0ms !important; animation: none !important; }\n ${ROOT} .fp-backdrop i { animation: none !important; }\n}\n`;\n\nlet installed: CSSStyleSheet | null = null;\nlet users = 0;\n\n/**\n * Installs the stylesheet while at least one focal-plane layout is mounted, and removes it when the last one unmounts.\n * Returns the release function. Falls back to a `<style>` element where constructable stylesheets are missing.\n */\nexport function installFocalPlaneStylesheet(doc: Document = document): () => void {\n users += 1;\n if (users === 1) {\n if ('adoptedStyleSheets' in doc && typeof CSSStyleSheet !== 'undefined' && 'replaceSync' in CSSStyleSheet.prototype) {\n installed = new CSSStyleSheet();\n installed.replaceSync(FOCAL_PLANE_CSS);\n doc.adoptedStyleSheets = [...doc.adoptedStyleSheets, installed];\n } else {\n const style = doc.createElement('style');\n style.dataset.focalPlane = '';\n style.textContent = FOCAL_PLANE_CSS;\n doc.head.appendChild(style);\n }\n }\n return () => {\n users -= 1;\n if (users > 0) return;\n if (installed) doc.adoptedStyleSheets = doc.adoptedStyleSheets.filter((sheet) => sheet !== installed);\n doc.head.querySelector('style[data-focal-plane]')?.remove();\n installed = null;\n };\n}\n"]}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { type RefObject } from 'react';
|
|
2
|
+
import type { FocalPlaneSettings } from './focal-plane-settings';
|
|
3
|
+
/**
|
|
4
|
+
* What the stylesheet cannot do alone, because the engine unmounts or hides a pane at once (#2049 step 2):
|
|
5
|
+
*
|
|
6
|
+
* - **Discard.** A side pane dismissed by its close control is removed from the DOM immediately. The removed node,
|
|
7
|
+
* no longer React's, is re-parented into a ghost layer at the box it last had, and flies away into depth and fades.
|
|
8
|
+
* - **Expand.** An emphasized side pane takes the width, and the engine HIDES the other panes (`hidden`). A still,
|
|
9
|
+
* inert image of each hidden pane is left behind, stepped back, visible through the expanded pane's glass. It is a
|
|
10
|
+
* picture, not a control: stepped back, it shrinks inside the expanded pane's outline, which covers it, so a click
|
|
11
|
+
* could never reach it (an earlier version offered one; a live probe found it unreachable). Returning to side by
|
|
12
|
+
* side is the pane's own restore control, or Escape. The image travels forward and goes as the emphasis ends.
|
|
13
|
+
* - **Swap.** A pane whose content changes in place dips back and returns (`dip`).
|
|
14
|
+
*
|
|
15
|
+
* Ghosts are aria-hidden and inert: they are pictures of panes, never a second copy anyone can use.
|
|
16
|
+
*/
|
|
17
|
+
export declare function usePaneDepth(rootRef: RefObject<HTMLElement | null>, settings: FocalPlaneSettings, reducedMotion: boolean): {
|
|
18
|
+
dip: (zone: string) => void;
|
|
19
|
+
};
|
|
20
|
+
//# sourceMappingURL=use-pane-depth.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"use-pane-depth.d.ts","sourceRoot":"","sources":["../../../../src/focal-plane/use-pane-depth.ts"],"names":[],"mappings":"AAAA,OAAO,EAAsC,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAM3E,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,wBAAwB,CAAC;AAGjE;;;;;;;;;;;;;GAaG;AACH,wBAAgB,YAAY,CAC1B,OAAO,EAAE,SAAS,CAAC,WAAW,GAAG,IAAI,CAAC,EACtC,QAAQ,EAAE,kBAAkB,EAC5B,aAAa,EAAE,OAAO,GACrB;IAAE,GAAG,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAA;CAAE,CAqHjC"}
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import { useEffect, useLayoutEffect, useRef } from 'react';
|
|
2
|
+
import { ShellRegion } from '@wildo-ai/presets-components-models';
|
|
3
|
+
import { NavigationZone } from '@wildo-ai/saas-frontend-lib';
|
|
4
|
+
import { FOCAL_PLANE_PANE_SELECTOR } from './focal-plane-stylesheet.js';
|
|
5
|
+
import { lastStageRects, VANISHING_POINT } from './use-shared-vanishing-point.js';
|
|
6
|
+
/**
|
|
7
|
+
* What the stylesheet cannot do alone, because the engine unmounts or hides a pane at once (#2049 step 2):
|
|
8
|
+
*
|
|
9
|
+
* - **Discard.** A side pane dismissed by its close control is removed from the DOM immediately. The removed node,
|
|
10
|
+
* no longer React's, is re-parented into a ghost layer at the box it last had, and flies away into depth and fades.
|
|
11
|
+
* - **Expand.** An emphasized side pane takes the width, and the engine HIDES the other panes (`hidden`). A still,
|
|
12
|
+
* inert image of each hidden pane is left behind, stepped back, visible through the expanded pane's glass. It is a
|
|
13
|
+
* picture, not a control: stepped back, it shrinks inside the expanded pane's outline, which covers it, so a click
|
|
14
|
+
* could never reach it (an earlier version offered one; a live probe found it unreachable). Returning to side by
|
|
15
|
+
* side is the pane's own restore control, or Escape. The image travels forward and goes as the emphasis ends.
|
|
16
|
+
* - **Swap.** A pane whose content changes in place dips back and returns (`dip`).
|
|
17
|
+
*
|
|
18
|
+
* Ghosts are aria-hidden and inert: they are pictures of panes, never a second copy anyone can use.
|
|
19
|
+
*/
|
|
20
|
+
export function usePaneDepth(rootRef, settings, reducedMotion) {
|
|
21
|
+
useLayoutEffect(() => {
|
|
22
|
+
const root = rootRef.current;
|
|
23
|
+
if (!root)
|
|
24
|
+
return undefined;
|
|
25
|
+
const back = root.querySelector('.fp-ghosts:not(.fp-ghosts-front)');
|
|
26
|
+
const front = root.querySelector('.fp-ghosts-front');
|
|
27
|
+
if (!back || !front)
|
|
28
|
+
return undefined;
|
|
29
|
+
const behind = new Map();
|
|
30
|
+
const motion = reducedMotion ? 0 : settings.motionMs;
|
|
31
|
+
const place = (ghost, rect) => {
|
|
32
|
+
const vx = root.clientWidth * VANISHING_POINT.x;
|
|
33
|
+
const vy = root.clientHeight * VANISHING_POINT.y;
|
|
34
|
+
Object.assign(ghost.style, { left: `${rect.left}px`, top: `${rect.top}px`, width: `${rect.width}px`, height: `${rect.height}px` });
|
|
35
|
+
ghost.style.setProperty('--fp-ox', `${vx - rect.left}px`);
|
|
36
|
+
ghost.style.setProperty('--fp-oy', `${vy - rect.top}px`);
|
|
37
|
+
};
|
|
38
|
+
const inertCopy = (node) => {
|
|
39
|
+
const copy = node.cloneNode(true);
|
|
40
|
+
sanitise(copy);
|
|
41
|
+
return copy;
|
|
42
|
+
};
|
|
43
|
+
const discard = (node) => {
|
|
44
|
+
const rect = lastStageRects.get(node);
|
|
45
|
+
// Moved rather than removed (taken out and put back in one batch): it is still on screen, not discarded.
|
|
46
|
+
if (!rect || motion === 0 || node.isConnected)
|
|
47
|
+
return;
|
|
48
|
+
const ghost = document.createElement('div');
|
|
49
|
+
ghost.className = 'fp-ghost';
|
|
50
|
+
sanitise(node);
|
|
51
|
+
ghost.appendChild(node);
|
|
52
|
+
place(ghost, rect);
|
|
53
|
+
front.appendChild(ghost);
|
|
54
|
+
requestAnimationFrame(() => requestAnimationFrame(() => {
|
|
55
|
+
ghost.style.setProperty('--fp-z', `${-settings.layerDepthPx * 1.5}px`);
|
|
56
|
+
ghost.style.opacity = '0';
|
|
57
|
+
}));
|
|
58
|
+
setTimeout(() => ghost.remove(), motion + 60);
|
|
59
|
+
};
|
|
60
|
+
const syncBehind = () => {
|
|
61
|
+
const emphasized = root.querySelector('[data-zone-emphasized]');
|
|
62
|
+
const hidden = emphasized ? [...root.querySelectorAll(FOCAL_PLANE_PANE_SELECTOR)].filter((pane) => pane.hasAttribute('data-zone-hidden')) : [];
|
|
63
|
+
for (const [pane, ghost] of behind) {
|
|
64
|
+
if (hidden.includes(pane))
|
|
65
|
+
continue;
|
|
66
|
+
behind.delete(pane);
|
|
67
|
+
ghost.style.setProperty('--fp-z', '0px');
|
|
68
|
+
ghost.style.opacity = '0';
|
|
69
|
+
setTimeout(() => ghost.remove(), motion);
|
|
70
|
+
}
|
|
71
|
+
for (const pane of hidden) {
|
|
72
|
+
if (behind.has(pane))
|
|
73
|
+
continue;
|
|
74
|
+
const rect = lastStageRects.get(pane);
|
|
75
|
+
if (!rect)
|
|
76
|
+
continue;
|
|
77
|
+
const ghost = document.createElement('div');
|
|
78
|
+
ghost.className = 'fp-ghost fp-ghost-behind';
|
|
79
|
+
const copy = inertCopy(pane);
|
|
80
|
+
copy.classList.remove('hidden');
|
|
81
|
+
copy.removeAttribute('data-zone-hidden');
|
|
82
|
+
copy.style.width = '100%';
|
|
83
|
+
copy.style.height = '100%';
|
|
84
|
+
ghost.appendChild(copy);
|
|
85
|
+
place(ghost, rect);
|
|
86
|
+
ghost.style.opacity = '0';
|
|
87
|
+
back.appendChild(ghost);
|
|
88
|
+
behind.set(pane, ghost);
|
|
89
|
+
requestAnimationFrame(() => requestAnimationFrame(() => {
|
|
90
|
+
ghost.style.setProperty('--fp-z', `${-settings.layerDepthPx}px`);
|
|
91
|
+
ghost.style.setProperty('--fp-filter', `brightness(${1 - settings.recedeDim}) saturate(${1 - settings.recedeDim * 0.9})`);
|
|
92
|
+
ghost.style.opacity = '1';
|
|
93
|
+
}));
|
|
94
|
+
}
|
|
95
|
+
};
|
|
96
|
+
const observer = new MutationObserver((records) => {
|
|
97
|
+
let emphasisMayHaveChanged = false;
|
|
98
|
+
for (const record of records) {
|
|
99
|
+
if (record.type === 'attributes') {
|
|
100
|
+
emphasisMayHaveChanged = true;
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
103
|
+
for (const node of record.removedNodes) {
|
|
104
|
+
if (!(node instanceof HTMLElement))
|
|
105
|
+
continue;
|
|
106
|
+
// A detached node matches no selector that names its ancestors, so the pane is recognised by the box the
|
|
107
|
+
// stage last measured for it: only objects (a zone, or a resizable panel) have one.
|
|
108
|
+
// Only a SIDE pane is ever dismissed. The primary pane leaves only when the content remounts (a breakpoint
|
|
109
|
+
// flip to the single-zone branch), which is not a discard and must not fly a ghost away.
|
|
110
|
+
if (lastStageRects.has(node) && isSidePane(node))
|
|
111
|
+
discard(node);
|
|
112
|
+
}
|
|
113
|
+
emphasisMayHaveChanged = true;
|
|
114
|
+
}
|
|
115
|
+
if (emphasisMayHaveChanged)
|
|
116
|
+
syncBehind();
|
|
117
|
+
});
|
|
118
|
+
const content = root.querySelector(`[data-component-area="${ShellRegion.CONTENT}"]`) ?? root;
|
|
119
|
+
observer.observe(content, { childList: true, subtree: true, attributes: true, attributeFilter: ['data-zone-hidden', 'data-zone-emphasized'] });
|
|
120
|
+
syncBehind();
|
|
121
|
+
return () => {
|
|
122
|
+
observer.disconnect();
|
|
123
|
+
for (const ghost of behind.values())
|
|
124
|
+
ghost.remove();
|
|
125
|
+
};
|
|
126
|
+
}, [rootRef, settings, reducedMotion]);
|
|
127
|
+
const dipRef = useRef(() => undefined);
|
|
128
|
+
useEffect(() => {
|
|
129
|
+
dipRef.current = (zone) => {
|
|
130
|
+
const root = rootRef.current;
|
|
131
|
+
if (!root || reducedMotion)
|
|
132
|
+
return;
|
|
133
|
+
const pane = root.querySelector(`[data-component-area="${ShellRegion.CONTENT}"] :is([data-content-area="${zone}"]:not([data-panel-id] *), [data-panel-id="nav-${zone}"])`);
|
|
134
|
+
if (!pane || typeof pane.animate !== 'function')
|
|
135
|
+
return;
|
|
136
|
+
// Implicit first and last keyframes: the pane dips from, and returns to, whatever depth it rests at.
|
|
137
|
+
pane.animate([{ offset: 0.45, transform: `perspective(${settings.cameraDistancePx}px) translate3d(0, 0, ${-settings.layerDepthPx * 0.5}px)`, opacity: 0.55 }], { duration: settings.motionMs, easing: 'cubic-bezier(0.2, 0.9, 0.25, 1)' });
|
|
138
|
+
};
|
|
139
|
+
}, [rootRef, settings, reducedMotion]);
|
|
140
|
+
return { dip: (zone) => dipRef.current(zone) };
|
|
141
|
+
}
|
|
142
|
+
const SIDE_ZONES = new Set([NavigationZone.SECONDARY, NavigationZone.COMPANION]);
|
|
143
|
+
function isSidePane(element) {
|
|
144
|
+
const zone = element.dataset.contentArea ?? element.dataset.panelId?.replace(/^nav-/, '');
|
|
145
|
+
return zone !== undefined && SIDE_ZONES.has(zone);
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Makes a pane a picture: no ids, no focus, invisible to assistive technology, and none of the markers a query for the
|
|
149
|
+
* REAL pane would find (a document-level lookup of the primary pane must never return its image). Embedded frames and
|
|
150
|
+
* media are dropped rather than loaded a second time.
|
|
151
|
+
*/
|
|
152
|
+
function sanitise(element) {
|
|
153
|
+
element.setAttribute('aria-hidden', 'true');
|
|
154
|
+
element.setAttribute('inert', '');
|
|
155
|
+
for (const node of [element, ...element.querySelectorAll('*')]) {
|
|
156
|
+
for (const attribute of ['id', 'data-content-area', 'data-panel-id', 'data-zone-hidden', 'data-zone-emphasized', 'data-component-area'])
|
|
157
|
+
node.removeAttribute(attribute);
|
|
158
|
+
}
|
|
159
|
+
for (const media of element.querySelectorAll('iframe, video, audio'))
|
|
160
|
+
media.remove();
|
|
161
|
+
}
|
|
162
|
+
//# sourceMappingURL=use-pane-depth.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"use-pane-depth.js","sourceRoot":"","sources":["../../../../src/focal-plane/use-pane-depth.ts"],"names":[],"mappings":"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","sourcesContent":["import { useEffect, useLayoutEffect, useRef, type RefObject } from 'react';\n\nimport { ShellRegion } from '@wildo-ai/presets-components-models';\nimport { NavigationZone } from '@wildo-ai/saas-frontend-lib';\n\nimport { FOCAL_PLANE_PANE_SELECTOR } from './focal-plane-stylesheet';\nimport type { FocalPlaneSettings } from './focal-plane-settings';\nimport { lastStageRects, VANISHING_POINT, type StageRect } from './use-shared-vanishing-point';\n\n/**\n * What the stylesheet cannot do alone, because the engine unmounts or hides a pane at once (#2049 step 2):\n *\n * - **Discard.** A side pane dismissed by its close control is removed from the DOM immediately. The removed node,\n * no longer React's, is re-parented into a ghost layer at the box it last had, and flies away into depth and fades.\n * - **Expand.** An emphasized side pane takes the width, and the engine HIDES the other panes (`hidden`). A still,\n * inert image of each hidden pane is left behind, stepped back, visible through the expanded pane's glass. It is a\n * picture, not a control: stepped back, it shrinks inside the expanded pane's outline, which covers it, so a click\n * could never reach it (an earlier version offered one; a live probe found it unreachable). Returning to side by\n * side is the pane's own restore control, or Escape. The image travels forward and goes as the emphasis ends.\n * - **Swap.** A pane whose content changes in place dips back and returns (`dip`).\n *\n * Ghosts are aria-hidden and inert: they are pictures of panes, never a second copy anyone can use.\n */\nexport function usePaneDepth(\n rootRef: RefObject<HTMLElement | null>,\n settings: FocalPlaneSettings,\n reducedMotion: boolean,\n): { dip: (zone: string) => void } {\n useLayoutEffect(() => {\n const root = rootRef.current;\n if (!root) return undefined;\n const back = root.querySelector<HTMLElement>('.fp-ghosts:not(.fp-ghosts-front)');\n const front = root.querySelector<HTMLElement>('.fp-ghosts-front');\n if (!back || !front) return undefined;\n const behind = new Map<Element, HTMLElement>();\n const motion = reducedMotion ? 0 : settings.motionMs;\n\n const place = (ghost: HTMLElement, rect: StageRect): void => {\n const vx = root.clientWidth * VANISHING_POINT.x;\n const vy = root.clientHeight * VANISHING_POINT.y;\n Object.assign(ghost.style, { left: `${rect.left}px`, top: `${rect.top}px`, width: `${rect.width}px`, height: `${rect.height}px` });\n ghost.style.setProperty('--fp-ox', `${vx - rect.left}px`);\n ghost.style.setProperty('--fp-oy', `${vy - rect.top}px`);\n };\n const inertCopy = (node: Element): HTMLElement => {\n const copy = node.cloneNode(true) as HTMLElement;\n sanitise(copy);\n return copy;\n };\n\n const discard = (node: HTMLElement): void => {\n const rect = lastStageRects.get(node);\n // Moved rather than removed (taken out and put back in one batch): it is still on screen, not discarded.\n if (!rect || motion === 0 || node.isConnected) return;\n const ghost = document.createElement('div');\n ghost.className = 'fp-ghost';\n sanitise(node);\n ghost.appendChild(node);\n place(ghost, rect);\n front.appendChild(ghost);\n requestAnimationFrame(() => requestAnimationFrame(() => {\n ghost.style.setProperty('--fp-z', `${-settings.layerDepthPx * 1.5}px`);\n ghost.style.opacity = '0';\n }));\n setTimeout(() => ghost.remove(), motion + 60);\n };\n\n const syncBehind = (): void => {\n const emphasized = root.querySelector('[data-zone-emphasized]');\n const hidden = emphasized ? [...root.querySelectorAll<HTMLElement>(FOCAL_PLANE_PANE_SELECTOR)].filter((pane) => pane.hasAttribute('data-zone-hidden')) : [];\n for (const [pane, ghost] of behind) {\n if (hidden.includes(pane as HTMLElement)) continue;\n behind.delete(pane);\n ghost.style.setProperty('--fp-z', '0px');\n ghost.style.opacity = '0';\n setTimeout(() => ghost.remove(), motion);\n }\n for (const pane of hidden) {\n if (behind.has(pane)) continue;\n const rect = lastStageRects.get(pane);\n if (!rect) continue;\n const ghost = document.createElement('div');\n ghost.className = 'fp-ghost fp-ghost-behind';\n const copy = inertCopy(pane);\n copy.classList.remove('hidden');\n copy.removeAttribute('data-zone-hidden');\n copy.style.width = '100%';\n copy.style.height = '100%';\n ghost.appendChild(copy);\n place(ghost, rect);\n ghost.style.opacity = '0';\n back.appendChild(ghost);\n behind.set(pane, ghost);\n requestAnimationFrame(() => requestAnimationFrame(() => {\n ghost.style.setProperty('--fp-z', `${-settings.layerDepthPx}px`);\n ghost.style.setProperty('--fp-filter', `brightness(${1 - settings.recedeDim}) saturate(${1 - settings.recedeDim * 0.9})`);\n ghost.style.opacity = '1';\n }));\n }\n };\n\n const observer = new MutationObserver((records) => {\n let emphasisMayHaveChanged = false;\n for (const record of records) {\n if (record.type === 'attributes') {\n emphasisMayHaveChanged = true;\n continue;\n }\n for (const node of record.removedNodes) {\n if (!(node instanceof HTMLElement)) continue;\n // A detached node matches no selector that names its ancestors, so the pane is recognised by the box the\n // stage last measured for it: only objects (a zone, or a resizable panel) have one.\n // Only a SIDE pane is ever dismissed. The primary pane leaves only when the content remounts (a breakpoint\n // flip to the single-zone branch), which is not a discard and must not fly a ghost away.\n if (lastStageRects.has(node) && isSidePane(node)) discard(node);\n }\n emphasisMayHaveChanged = true;\n }\n if (emphasisMayHaveChanged) syncBehind();\n });\n const content = root.querySelector(`[data-component-area=\"${ShellRegion.CONTENT}\"]`) ?? root;\n observer.observe(content, { childList: true, subtree: true, attributes: true, attributeFilter: ['data-zone-hidden', 'data-zone-emphasized'] });\n syncBehind();\n return () => {\n observer.disconnect();\n for (const ghost of behind.values()) ghost.remove();\n };\n }, [rootRef, settings, reducedMotion]);\n\n const dipRef = useRef<(zone: string) => void>(() => undefined);\n useEffect(() => {\n dipRef.current = (zone: string): void => {\n const root = rootRef.current;\n if (!root || reducedMotion) return;\n const pane = root.querySelector<HTMLElement>(`[data-component-area=\"${ShellRegion.CONTENT}\"] :is([data-content-area=\"${zone}\"]:not([data-panel-id] *), [data-panel-id=\"nav-${zone}\"])`);\n if (!pane || typeof pane.animate !== 'function') return;\n // Implicit first and last keyframes: the pane dips from, and returns to, whatever depth it rests at.\n pane.animate(\n [{ offset: 0.45, transform: `perspective(${settings.cameraDistancePx}px) translate3d(0, 0, ${-settings.layerDepthPx * 0.5}px)`, opacity: 0.55 }],\n { duration: settings.motionMs, easing: 'cubic-bezier(0.2, 0.9, 0.25, 1)' },\n );\n };\n }, [rootRef, settings, reducedMotion]);\n return { dip: (zone) => dipRef.current(zone) };\n}\n\nconst SIDE_ZONES: ReadonlySet<string> = new Set([NavigationZone.SECONDARY, NavigationZone.COMPANION]);\nfunction isSidePane(element: HTMLElement): boolean {\n const zone = element.dataset.contentArea ?? element.dataset.panelId?.replace(/^nav-/, '');\n return zone !== undefined && SIDE_ZONES.has(zone);\n}\n\n/**\n * Makes a pane a picture: no ids, no focus, invisible to assistive technology, and none of the markers a query for the\n * REAL pane would find (a document-level lookup of the primary pane must never return its image). Embedded frames and\n * media are dropped rather than loaded a second time.\n */\nfunction sanitise(element: HTMLElement): void {\n element.setAttribute('aria-hidden', 'true');\n element.setAttribute('inert', '');\n for (const node of [element, ...element.querySelectorAll<HTMLElement>('*')]) {\n for (const attribute of ['id', 'data-content-area', 'data-panel-id', 'data-zone-hidden', 'data-zone-emphasized', 'data-component-area']) node.removeAttribute(attribute);\n }\n for (const media of element.querySelectorAll('iframe, video, audio')) media.remove();\n}\n"]}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { type RefObject } from 'react';
|
|
2
|
+
/** Where the vanishing point sits in the stage: centred, a little above the middle (the eye line of the prototype). */
|
|
3
|
+
export declare const VANISHING_POINT: {
|
|
4
|
+
x: number;
|
|
5
|
+
y: number;
|
|
6
|
+
};
|
|
7
|
+
/** An object's laid-out box in the stage (untransformed), as last measured. */
|
|
8
|
+
export interface StageRect {
|
|
9
|
+
left: number;
|
|
10
|
+
top: number;
|
|
11
|
+
width: number;
|
|
12
|
+
height: number;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* The last laid-out box of every object, kept so a pane that has just been REMOVED or HIDDEN can still be drawn where
|
|
16
|
+
* it was (its ghost flies into depth from there). A zero-size reading never replaces a real one: a hidden pane measures
|
|
17
|
+
* as nothing, and its last visible box is the one that matters.
|
|
18
|
+
*/
|
|
19
|
+
export declare const lastStageRects: WeakMap<Element, StageRect>;
|
|
20
|
+
/**
|
|
21
|
+
* Keeps every object's `transform-origin` on the stage's ONE vanishing point, so objects that each carry their own
|
|
22
|
+
* `perspective()` are seen through a single still camera (see `focal-plane-stylesheet.ts`).
|
|
23
|
+
*
|
|
24
|
+
* The origin is written as two custom properties on each object (`--fp-ox`, `--fp-oy`): the vanishing point minus the
|
|
25
|
+
* object's own offset in the stage. Offsets are read from `offsetLeft`/`offsetTop`, which ignore transforms, so an object
|
|
26
|
+
* that is currently stepped back still reports where it is laid out. Custom properties written this way are left alone
|
|
27
|
+
* by React, which only manages the style properties it set itself.
|
|
28
|
+
*/
|
|
29
|
+
export declare function useSharedVanishingPoint(rootRef: RefObject<HTMLElement | null>): void;
|
|
30
|
+
/** An element's layout offset inside `root`, walking the offset-parent chain (transforms are not part of it). */
|
|
31
|
+
export declare function offsetWithin(element: HTMLElement, root: HTMLElement): {
|
|
32
|
+
left: number;
|
|
33
|
+
top: number;
|
|
34
|
+
};
|
|
35
|
+
//# sourceMappingURL=use-shared-vanishing-point.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"use-shared-vanishing-point.d.ts","sourceRoot":"","sources":["../../../../src/focal-plane/use-shared-vanishing-point.ts"],"names":[],"mappings":"AAAA,OAAO,EAAmB,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAIxD,uHAAuH;AACvH,eAAO,MAAM,eAAe;;;CAAsB,CAAC;AAEnD,+EAA+E;AAC/E,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;CAChB;AAED;;;;GAIG;AACH,eAAO,MAAM,cAAc,6BAAoC,CAAC;AAEhE;;;;;;;;GAQG;AACH,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,SAAS,CAAC,WAAW,GAAG,IAAI,CAAC,GAAG,IAAI,CAuCpF;AAED,iHAAiH;AACjH,wBAAgB,YAAY,CAAC,OAAO,EAAE,WAAW,EAAE,IAAI,EAAE,WAAW,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,CAYnG"}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { useLayoutEffect } from 'react';
|
|
2
|
+
import { FOCAL_PLANE_PANE_SELECTOR } from './focal-plane-stylesheet.js';
|
|
3
|
+
/** Where the vanishing point sits in the stage: centred, a little above the middle (the eye line of the prototype). */
|
|
4
|
+
export const VANISHING_POINT = { x: 0.5, y: 0.42 };
|
|
5
|
+
/**
|
|
6
|
+
* The last laid-out box of every object, kept so a pane that has just been REMOVED or HIDDEN can still be drawn where
|
|
7
|
+
* it was (its ghost flies into depth from there). A zero-size reading never replaces a real one: a hidden pane measures
|
|
8
|
+
* as nothing, and its last visible box is the one that matters.
|
|
9
|
+
*/
|
|
10
|
+
export const lastStageRects = new WeakMap();
|
|
11
|
+
/**
|
|
12
|
+
* Keeps every object's `transform-origin` on the stage's ONE vanishing point, so objects that each carry their own
|
|
13
|
+
* `perspective()` are seen through a single still camera (see `focal-plane-stylesheet.ts`).
|
|
14
|
+
*
|
|
15
|
+
* The origin is written as two custom properties on each object (`--fp-ox`, `--fp-oy`): the vanishing point minus the
|
|
16
|
+
* object's own offset in the stage. Offsets are read from `offsetLeft`/`offsetTop`, which ignore transforms, so an object
|
|
17
|
+
* that is currently stepped back still reports where it is laid out. Custom properties written this way are left alone
|
|
18
|
+
* by React, which only manages the style properties it set itself.
|
|
19
|
+
*/
|
|
20
|
+
export function useSharedVanishingPoint(rootRef) {
|
|
21
|
+
useLayoutEffect(() => {
|
|
22
|
+
const root = rootRef.current;
|
|
23
|
+
if (!root)
|
|
24
|
+
return undefined;
|
|
25
|
+
let frame = 0;
|
|
26
|
+
// Absent in some environments (a test DOM): the window resize and the mutations below still re-measure.
|
|
27
|
+
const resize = typeof ResizeObserver === 'undefined' ? null : new ResizeObserver(() => schedule());
|
|
28
|
+
const observed = new Set();
|
|
29
|
+
const measure = () => {
|
|
30
|
+
frame = 0;
|
|
31
|
+
const vx = root.clientWidth * VANISHING_POINT.x;
|
|
32
|
+
const vy = root.clientHeight * VANISHING_POINT.y;
|
|
33
|
+
const objects = new Set(root.querySelectorAll(`[data-fp-object], ${FOCAL_PLANE_PANE_SELECTOR}`));
|
|
34
|
+
// Every object is observed, not only the stage: a sidebar collapsing MOVES the content column without resizing
|
|
35
|
+
// the stage, and a moved object with a stale origin would be seen from the wrong point.
|
|
36
|
+
for (const element of objects)
|
|
37
|
+
if (!observed.has(element)) {
|
|
38
|
+
resize?.observe(element);
|
|
39
|
+
observed.add(element);
|
|
40
|
+
}
|
|
41
|
+
for (const element of observed)
|
|
42
|
+
if (!objects.has(element)) {
|
|
43
|
+
resize?.unobserve(element);
|
|
44
|
+
observed.delete(element);
|
|
45
|
+
}
|
|
46
|
+
for (const element of objects) {
|
|
47
|
+
const { left, top } = offsetWithin(element, root);
|
|
48
|
+
element.style.setProperty('--fp-ox', `${(vx - left).toFixed(1)}px`);
|
|
49
|
+
element.style.setProperty('--fp-oy', `${(vy - top).toFixed(1)}px`);
|
|
50
|
+
if (element.offsetWidth > 0 && element.offsetHeight > 0)
|
|
51
|
+
lastStageRects.set(element, { left, top, width: element.offsetWidth, height: element.offsetHeight });
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
const schedule = () => {
|
|
55
|
+
if (!frame)
|
|
56
|
+
frame = requestAnimationFrame(measure);
|
|
57
|
+
};
|
|
58
|
+
measure();
|
|
59
|
+
resize?.observe(root);
|
|
60
|
+
const mutations = new MutationObserver(schedule);
|
|
61
|
+
mutations.observe(root, { childList: true, subtree: true });
|
|
62
|
+
window.addEventListener('resize', schedule);
|
|
63
|
+
return () => {
|
|
64
|
+
cancelAnimationFrame(frame);
|
|
65
|
+
resize?.disconnect();
|
|
66
|
+
mutations.disconnect();
|
|
67
|
+
window.removeEventListener('resize', schedule);
|
|
68
|
+
};
|
|
69
|
+
}, [rootRef]);
|
|
70
|
+
}
|
|
71
|
+
/** An element's layout offset inside `root`, walking the offset-parent chain (transforms are not part of it). */
|
|
72
|
+
export function offsetWithin(element, root) {
|
|
73
|
+
let left = 0;
|
|
74
|
+
let top = 0;
|
|
75
|
+
let node = element;
|
|
76
|
+
while (node && node !== root) {
|
|
77
|
+
left += node.offsetLeft;
|
|
78
|
+
top += node.offsetTop;
|
|
79
|
+
const parent = node.offsetParent;
|
|
80
|
+
if (!parent || !root.contains(parent) && parent !== root)
|
|
81
|
+
break;
|
|
82
|
+
node = parent;
|
|
83
|
+
}
|
|
84
|
+
return { left, top };
|
|
85
|
+
}
|
|
86
|
+
//# sourceMappingURL=use-shared-vanishing-point.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"use-shared-vanishing-point.js","sourceRoot":"","sources":["../../../../src/focal-plane/use-shared-vanishing-point.ts"],"names":[],"mappings":"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","sourcesContent":["import { useLayoutEffect, type RefObject } from 'react';\n\nimport { FOCAL_PLANE_PANE_SELECTOR } from './focal-plane-stylesheet';\n\n/** Where the vanishing point sits in the stage: centred, a little above the middle (the eye line of the prototype). */\nexport const VANISHING_POINT = { x: 0.5, y: 0.42 };\n\n/** An object's laid-out box in the stage (untransformed), as last measured. */\nexport interface StageRect {\n left: number;\n top: number;\n width: number;\n height: number;\n}\n\n/**\n * The last laid-out box of every object, kept so a pane that has just been REMOVED or HIDDEN can still be drawn where\n * it was (its ghost flies into depth from there). A zero-size reading never replaces a real one: a hidden pane measures\n * as nothing, and its last visible box is the one that matters.\n */\nexport const lastStageRects = new WeakMap<Element, StageRect>();\n\n/**\n * Keeps every object's `transform-origin` on the stage's ONE vanishing point, so objects that each carry their own\n * `perspective()` are seen through a single still camera (see `focal-plane-stylesheet.ts`).\n *\n * The origin is written as two custom properties on each object (`--fp-ox`, `--fp-oy`): the vanishing point minus the\n * object's own offset in the stage. Offsets are read from `offsetLeft`/`offsetTop`, which ignore transforms, so an object\n * that is currently stepped back still reports where it is laid out. Custom properties written this way are left alone\n * by React, which only manages the style properties it set itself.\n */\nexport function useSharedVanishingPoint(rootRef: RefObject<HTMLElement | null>): void {\n useLayoutEffect(() => {\n const root = rootRef.current;\n if (!root) return undefined;\n let frame = 0;\n // Absent in some environments (a test DOM): the window resize and the mutations below still re-measure.\n const resize = typeof ResizeObserver === 'undefined' ? null : new ResizeObserver(() => schedule());\n const observed = new Set<HTMLElement>();\n const measure = (): void => {\n frame = 0;\n const vx = root.clientWidth * VANISHING_POINT.x;\n const vy = root.clientHeight * VANISHING_POINT.y;\n const objects = new Set(root.querySelectorAll<HTMLElement>(`[data-fp-object], ${FOCAL_PLANE_PANE_SELECTOR}`));\n // Every object is observed, not only the stage: a sidebar collapsing MOVES the content column without resizing\n // the stage, and a moved object with a stale origin would be seen from the wrong point.\n for (const element of objects) if (!observed.has(element)) { resize?.observe(element); observed.add(element); }\n for (const element of observed) if (!objects.has(element)) { resize?.unobserve(element); observed.delete(element); }\n for (const element of objects) {\n const { left, top } = offsetWithin(element, root);\n element.style.setProperty('--fp-ox', `${(vx - left).toFixed(1)}px`);\n element.style.setProperty('--fp-oy', `${(vy - top).toFixed(1)}px`);\n if (element.offsetWidth > 0 && element.offsetHeight > 0) lastStageRects.set(element, { left, top, width: element.offsetWidth, height: element.offsetHeight });\n }\n };\n const schedule = (): void => {\n if (!frame) frame = requestAnimationFrame(measure);\n };\n measure();\n resize?.observe(root);\n const mutations = new MutationObserver(schedule);\n mutations.observe(root, { childList: true, subtree: true });\n window.addEventListener('resize', schedule);\n return () => {\n cancelAnimationFrame(frame);\n resize?.disconnect();\n mutations.disconnect();\n window.removeEventListener('resize', schedule);\n };\n }, [rootRef]);\n}\n\n/** An element's layout offset inside `root`, walking the offset-parent chain (transforms are not part of it). */\nexport function offsetWithin(element: HTMLElement, root: HTMLElement): { left: number; top: number } {\n let left = 0;\n let top = 0;\n let node: HTMLElement | null = element;\n while (node && node !== root) {\n left += node.offsetLeft;\n top += node.offsetTop;\n const parent = node.offsetParent as HTMLElement | null;\n if (!parent || !root.contains(parent) && parent !== root) break;\n node = parent;\n }\n return { left, top };\n}\n"]}
|
|
@@ -35,9 +35,9 @@ export function canvasCameraSettings(camera) {
|
|
|
35
35
|
case Layout3DCamera.FLAT_ORTHOGRAPHIC:
|
|
36
36
|
// Orthographic with zoom 1: one scene unit is one CSS pixel, so a slab lies exactly on its region's box.
|
|
37
37
|
return { orthographic: true, camera: { position: [0, 0, 10], zoom: 1 } };
|
|
38
|
-
case Layout3DCamera.
|
|
39
|
-
//
|
|
40
|
-
throw new Error('The 3D frame does not draw a
|
|
38
|
+
case Layout3DCamera.STILL_PERSPECTIVE:
|
|
39
|
+
// A still-perspective layout carries its depth in its own HTML objects; the frame is never mounted for it.
|
|
40
|
+
throw new Error('The 3D frame does not draw a still-perspective layout: its placement stages the objects itself.');
|
|
41
41
|
default: {
|
|
42
42
|
const unsupported = camera;
|
|
43
43
|
throw new Error(`The 3D frame has no camera '${String(unsupported)}'.`);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"frame-canvas.js","sourceRoot":"","sources":["../../../../src/frame/frame-canvas.tsx"],"names":[],"mappings":"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","sourcesContent":["import React, { useLayoutEffect, useRef } from 'react';\nimport { Canvas, useThree, type CanvasProps, type EventManager } from '@react-three/fiber';\nimport { Group } from 'three';\n\nimport { createFrameDriver, type FrameDriver } from './frame-driver';\nimport { FrameRenderer, type FrameTransitionTrigger } from './frame-renderer';\nimport type { FrameAppearance } from './frame-appearance';\nimport { Layout3DCamera, type Layout3DFrameChoreography, type Layout3DZoneFocusTransition } from '../layout/layout-3d-vocabulary';\n\n/**\n * The frame receives no pointer events and handles none. fiber's wrapper would otherwise set\n * `pointer-events: auto` inline and attach its listeners, making a full-viewport canvas a hit target wherever\n * the shell root does not cover it. Hover is read from the DOCUMENT instead (see {@link FrameScene}).\n */\nconst NO_POINTER_EVENTS = (): EventManager<HTMLElement> => ({ enabled: false, priority: 0 });\n\n/**\n * Every `<Canvas>` setting the frame depends on, in one place so a test pins them.\n *\n * - `flat`: no tone mapping. The shaders write sRGB-encoded colours as given; tone mapping would move every\n * colour after the contrast bound was checked.\n * - `frameloop: 'never'`: fiber renders nothing on its own. The scene renders when a region moved, when the\n * appearance changed, and per animation frame only while something animates — so an idle shell costs zero frames.\n * - `powerPreference: 'low-power'`: a decorative frame must not wake a discrete GPU.\n */\nexport const FRAME_CANVAS_SETTINGS = {\n flat: true,\n frameloop: 'never',\n gl: { alpha: true, antialias: true, powerPreference: 'low-power' },\n events: NO_POINTER_EVENTS,\n style: { pointerEvents: 'none' },\n} satisfies Omit<CanvasProps, 'children'>;\n\nexport interface FrameCanvasProps {\n /** Computed in the DOM tree (`useFrameAppearance`); the canvas reads no context of its own. */\n appearance: FrameAppearance | null;\n /** The layout's camera. */\n camera: Layout3DCamera;\n /** Whether the layout's transitions cross-fade an appearance change (`crossFadesAppearance`). */\n crossFadeAppearance: boolean;\n /** What the layout plays on a navigation (#1826). */\n navigationTransition: Layout3DFrameChoreography;\n /** What the layout plays when the sidebar collapses, expands or is resized (#1826). */\n sidebarResizeTransition: Layout3DFrameChoreography;\n /** What the layout shows for the focused zone (#1826). */\n zoneFocusTransition: Layout3DZoneFocusTransition;\n /** The content's identity, from the navigation state: a change plays the navigation transition. */\n navigationSignature: string;\n /** The navigation state's focused zone, or null. */\n focusedZone: string | null;\n}\n\n/**\n * The `<Canvas>` camera for a layout's camera. Exhaustive over {@link Layout3DCamera}: a camera added for #1826\n * fails to compile here until the frame can draw through it.\n */\nexport function canvasCameraSettings(camera: Layout3DCamera): Pick<CanvasProps, 'orthographic' | 'camera'> {\n switch (camera) {\n case Layout3DCamera.FLAT_ORTHOGRAPHIC:\n // Orthographic with zoom 1: one scene unit is one CSS pixel, so a slab lies exactly on its region's box.\n return { orthographic: true, camera: { position: [0, 0, 10], zoom: 1 } };\n case Layout3DCamera.SPATIAL_PERSPECTIVE:\n // The spatial layout draws its own environment through its own camera (`spatial/`); the frame is never mounted.\n throw new Error('The 3D frame does not draw a spatial layout: its placement renders the spatial environment itself.');\n default: {\n const unsupported: never = camera;\n throw new Error(`The 3D frame has no camera '${String(unsupported)}'.`);\n }\n }\n}\n\n/**\n * The 3D frame (#1823): an in-scene background derived from the theme tokens, and a frosted slab under every\n * shell region, aligned to the region's box. Everything inside the regions stays the 2D shell's HTML; this\n * canvas draws only what is behind them, and holds meshes only.\n */\nexport default function FrameCanvas({ camera, ...scene }: FrameCanvasProps): React.JSX.Element {\n return (\n <div aria-hidden=\"true\" data-frame-3d=\"frame\" className=\"fixed inset-0 pointer-events-none\">\n <Canvas {...FRAME_CANVAS_SETTINGS} {...canvasCameraSettings(camera)}>\n <FrameScene {...scene} />\n </Canvas>\n </div>\n );\n}\n\n/**\n * The scene host: creates the renderer and the scheduling driver (`createFrameDriver`, which decides WHEN to draw)\n * and forwards the appearance prop to it. Everything about timing lives in the driver, where it is unit-tested.\n */\nfunction FrameScene({\n appearance,\n crossFadeAppearance,\n navigationTransition,\n sidebarResizeTransition,\n zoneFocusTransition,\n navigationSignature,\n focusedZone,\n}: Omit<FrameCanvasProps, 'camera'>): React.JSX.Element {\n const groupRef = useRef<Group>(null);\n const gl = useThree((state) => state.gl);\n const scene = useThree((state) => state.scene);\n const camera = useThree((state) => state.camera);\n const get = useThree((state) => state.get);\n const size = useThree((state) => state.size);\n const dpr = useThree((state) => state.viewport.dpr);\n const driverRef = useRef<FrameDriver | null>(null);\n // The latest inputs, so a driver re-created for a new renderer or camera starts from them (not from nothing): its\n // first navigation signature only records, so a re-creation never plays a navigation that did not happen.\n const appearanceRef = useRef(appearance);\n const navigationRef = useRef(navigationSignature);\n const focusedZoneRef = useRef(focusedZone);\n\n useLayoutEffect(() => {\n const group = groupRef.current;\n if (!group) return undefined;\n const renderer = new FrameRenderer(group, { navigation: navigationTransition, sidebarResize: sidebarResizeTransition, zoneFocus: zoneFocusTransition });\n const driver = createFrameDriver(\n {\n renderer,\n renderScene: () => gl.render(scene, camera),\n // The canvas' own size from fiber's store: the number the camera frustum was fitted to.\n viewport: () => ({ width: get().size.width, height: get().size.height }),\n canvas: gl.domElement,\n document,\n requestFrame: (callback) => requestAnimationFrame(callback),\n cancelFrame: (handle) => cancelAnimationFrame(handle),\n now: () => performance.now(),\n createResizeObserver: (callback) => new ResizeObserver(callback),\n createMutationObserver: (callback) => new MutationObserver(callback),\n crossFadeAppearance,\n onTransitionStarted: (trigger) => countTransition(gl.domElement, trigger),\n },\n appearanceRef.current,\n );\n driver.setNavigation(navigationRef.current);\n driver.setFocusedZone(focusedZoneRef.current);\n driverRef.current = driver;\n return () => {\n driver.dispose();\n renderer.dispose();\n driverRef.current = null;\n };\n }, [gl, scene, camera, get, crossFadeAppearance, navigationTransition, sidebarResizeTransition, zoneFocusTransition]);\n\n // The appearance changed (theme, mode, reduced motion). Declared AFTER the scene effect, so the driver exists.\n useLayoutEffect(() => {\n if (appearanceRef.current === appearance) return;\n appearanceRef.current = appearance;\n driverRef.current?.setAppearance(appearance);\n }, [appearance]);\n\n // The content or the focused zone changed (#1826). After the scene effect, so the driver exists.\n useLayoutEffect(() => {\n navigationRef.current = navigationSignature;\n driverRef.current?.setNavigation(navigationSignature);\n }, [navigationSignature]);\n useLayoutEffect(() => {\n focusedZoneRef.current = focusedZone;\n driverRef.current?.setFocusedZone(focusedZone);\n }, [focusedZone]);\n\n // The canvas resized, or its pixel ratio changed: fiber has re-fitted the camera and cleared the drawing buffer,\n // so redraw even if every region is where it was.\n useLayoutEffect(() => {\n driverRef.current?.redraw();\n }, [size.width, size.height, dpr]);\n\n return <group ref={groupRef} />;\n}\n\n/**\n * Counts each started transition on the canvas element, as `data-frame-3d-transitions-<trigger>`. The frame's only\n * observable surface for WHICH choreography ran: a live probe (`scripts/presets-3d-live-probe`) reads it, because\n * counting draws cannot tell a choreography from a hover or a re-layout. Writes a number, nothing else.\n */\nexport function countTransition(canvas: HTMLElement, trigger: FrameTransitionTrigger): void {\n const attribute = `data-frame-3d-transitions-${trigger}`;\n canvas.setAttribute(attribute, String(Number(canvas.getAttribute(attribute) ?? '0') + 1));\n}\n"]}
|
|
1
|
+
{"version":3,"file":"frame-canvas.js","sourceRoot":"","sources":["../../../../src/frame/frame-canvas.tsx"],"names":[],"mappings":"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","sourcesContent":["import React, { useLayoutEffect, useRef } from 'react';\nimport { Canvas, useThree, type CanvasProps, type EventManager } from '@react-three/fiber';\nimport { Group } from 'three';\n\nimport { createFrameDriver, type FrameDriver } from './frame-driver';\nimport { FrameRenderer, type FrameTransitionTrigger } from './frame-renderer';\nimport type { FrameAppearance } from './frame-appearance';\nimport { Layout3DCamera, type Layout3DFrameChoreography, type Layout3DZoneFocusTransition } from '../layout/layout-3d-vocabulary';\n\n/**\n * The frame receives no pointer events and handles none. fiber's wrapper would otherwise set\n * `pointer-events: auto` inline and attach its listeners, making a full-viewport canvas a hit target wherever\n * the shell root does not cover it. Hover is read from the DOCUMENT instead (see {@link FrameScene}).\n */\nconst NO_POINTER_EVENTS = (): EventManager<HTMLElement> => ({ enabled: false, priority: 0 });\n\n/**\n * Every `<Canvas>` setting the frame depends on, in one place so a test pins them.\n *\n * - `flat`: no tone mapping. The shaders write sRGB-encoded colours as given; tone mapping would move every\n * colour after the contrast bound was checked.\n * - `frameloop: 'never'`: fiber renders nothing on its own. The scene renders when a region moved, when the\n * appearance changed, and per animation frame only while something animates — so an idle shell costs zero frames.\n * - `powerPreference: 'low-power'`: a decorative frame must not wake a discrete GPU.\n */\nexport const FRAME_CANVAS_SETTINGS = {\n flat: true,\n frameloop: 'never',\n gl: { alpha: true, antialias: true, powerPreference: 'low-power' },\n events: NO_POINTER_EVENTS,\n style: { pointerEvents: 'none' },\n} satisfies Omit<CanvasProps, 'children'>;\n\nexport interface FrameCanvasProps {\n /** Computed in the DOM tree (`useFrameAppearance`); the canvas reads no context of its own. */\n appearance: FrameAppearance | null;\n /** The layout's camera. */\n camera: Layout3DCamera;\n /** Whether the layout's transitions cross-fade an appearance change (`crossFadesAppearance`). */\n crossFadeAppearance: boolean;\n /** What the layout plays on a navigation (#1826). */\n navigationTransition: Layout3DFrameChoreography;\n /** What the layout plays when the sidebar collapses, expands or is resized (#1826). */\n sidebarResizeTransition: Layout3DFrameChoreography;\n /** What the layout shows for the focused zone (#1826). */\n zoneFocusTransition: Layout3DZoneFocusTransition;\n /** The content's identity, from the navigation state: a change plays the navigation transition. */\n navigationSignature: string;\n /** The navigation state's focused zone, or null. */\n focusedZone: string | null;\n}\n\n/**\n * The `<Canvas>` camera for a layout's camera. Exhaustive over {@link Layout3DCamera}: a camera added for #1826\n * fails to compile here until the frame can draw through it.\n */\nexport function canvasCameraSettings(camera: Layout3DCamera): Pick<CanvasProps, 'orthographic' | 'camera'> {\n switch (camera) {\n case Layout3DCamera.FLAT_ORTHOGRAPHIC:\n // Orthographic with zoom 1: one scene unit is one CSS pixel, so a slab lies exactly on its region's box.\n return { orthographic: true, camera: { position: [0, 0, 10], zoom: 1 } };\n case Layout3DCamera.STILL_PERSPECTIVE:\n // A still-perspective layout carries its depth in its own HTML objects; the frame is never mounted for it.\n throw new Error('The 3D frame does not draw a still-perspective layout: its placement stages the objects itself.');\n default: {\n const unsupported: never = camera;\n throw new Error(`The 3D frame has no camera '${String(unsupported)}'.`);\n }\n }\n}\n\n/**\n * The 3D frame (#1823): an in-scene background derived from the theme tokens, and a frosted slab under every\n * shell region, aligned to the region's box. Everything inside the regions stays the 2D shell's HTML; this\n * canvas draws only what is behind them, and holds meshes only.\n */\nexport default function FrameCanvas({ camera, ...scene }: FrameCanvasProps): React.JSX.Element {\n return (\n <div aria-hidden=\"true\" data-frame-3d=\"frame\" className=\"fixed inset-0 pointer-events-none\">\n <Canvas {...FRAME_CANVAS_SETTINGS} {...canvasCameraSettings(camera)}>\n <FrameScene {...scene} />\n </Canvas>\n </div>\n );\n}\n\n/**\n * The scene host: creates the renderer and the scheduling driver (`createFrameDriver`, which decides WHEN to draw)\n * and forwards the appearance prop to it. Everything about timing lives in the driver, where it is unit-tested.\n */\nfunction FrameScene({\n appearance,\n crossFadeAppearance,\n navigationTransition,\n sidebarResizeTransition,\n zoneFocusTransition,\n navigationSignature,\n focusedZone,\n}: Omit<FrameCanvasProps, 'camera'>): React.JSX.Element {\n const groupRef = useRef<Group>(null);\n const gl = useThree((state) => state.gl);\n const scene = useThree((state) => state.scene);\n const camera = useThree((state) => state.camera);\n const get = useThree((state) => state.get);\n const size = useThree((state) => state.size);\n const dpr = useThree((state) => state.viewport.dpr);\n const driverRef = useRef<FrameDriver | null>(null);\n // The latest inputs, so a driver re-created for a new renderer or camera starts from them (not from nothing): its\n // first navigation signature only records, so a re-creation never plays a navigation that did not happen.\n const appearanceRef = useRef(appearance);\n const navigationRef = useRef(navigationSignature);\n const focusedZoneRef = useRef(focusedZone);\n\n useLayoutEffect(() => {\n const group = groupRef.current;\n if (!group) return undefined;\n const renderer = new FrameRenderer(group, { navigation: navigationTransition, sidebarResize: sidebarResizeTransition, zoneFocus: zoneFocusTransition });\n const driver = createFrameDriver(\n {\n renderer,\n renderScene: () => gl.render(scene, camera),\n // The canvas' own size from fiber's store: the number the camera frustum was fitted to.\n viewport: () => ({ width: get().size.width, height: get().size.height }),\n canvas: gl.domElement,\n document,\n requestFrame: (callback) => requestAnimationFrame(callback),\n cancelFrame: (handle) => cancelAnimationFrame(handle),\n now: () => performance.now(),\n createResizeObserver: (callback) => new ResizeObserver(callback),\n createMutationObserver: (callback) => new MutationObserver(callback),\n crossFadeAppearance,\n onTransitionStarted: (trigger) => countTransition(gl.domElement, trigger),\n },\n appearanceRef.current,\n );\n driver.setNavigation(navigationRef.current);\n driver.setFocusedZone(focusedZoneRef.current);\n driverRef.current = driver;\n return () => {\n driver.dispose();\n renderer.dispose();\n driverRef.current = null;\n };\n }, [gl, scene, camera, get, crossFadeAppearance, navigationTransition, sidebarResizeTransition, zoneFocusTransition]);\n\n // The appearance changed (theme, mode, reduced motion). Declared AFTER the scene effect, so the driver exists.\n useLayoutEffect(() => {\n if (appearanceRef.current === appearance) return;\n appearanceRef.current = appearance;\n driverRef.current?.setAppearance(appearance);\n }, [appearance]);\n\n // The content or the focused zone changed (#1826). After the scene effect, so the driver exists.\n useLayoutEffect(() => {\n navigationRef.current = navigationSignature;\n driverRef.current?.setNavigation(navigationSignature);\n }, [navigationSignature]);\n useLayoutEffect(() => {\n focusedZoneRef.current = focusedZone;\n driverRef.current?.setFocusedZone(focusedZone);\n }, [focusedZone]);\n\n // The canvas resized, or its pixel ratio changed: fiber has re-fitted the camera and cleared the drawing buffer,\n // so redraw even if every region is where it was.\n useLayoutEffect(() => {\n driverRef.current?.redraw();\n }, [size.width, size.height, dpr]);\n\n return <group ref={groupRef} />;\n}\n\n/**\n * Counts each started transition on the canvas element, as `data-frame-3d-transitions-<trigger>`. The frame's only\n * observable surface for WHICH choreography ran: a live probe (`scripts/presets-3d-live-probe`) reads it, because\n * counting draws cannot tell a choreography from a hover or a re-layout. Writes a number, nothing else.\n */\nexport function countTransition(canvas: HTMLElement, trigger: FrameTransitionTrigger): void {\n const attribute = `data-frame-3d-transitions-${trigger}`;\n canvas.setAttribute(attribute, String(Number(canvas.getAttribute(attribute) ?? '0') + 1));\n}\n"]}
|
package/dist/esm/index.d.ts
CHANGED
|
@@ -11,4 +11,5 @@ export * from './layout/layout-3d-vocabulary';
|
|
|
11
11
|
export * from './layout/layout-3d-definition';
|
|
12
12
|
export * from './layout/layout-3d-transition-choreography';
|
|
13
13
|
export * from './layout/header-first-shell-container';
|
|
14
|
+
export * from './focal-plane/focal-plane-settings';
|
|
14
15
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/esm/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,sBAAsB,CAAC;AACrC,cAAc,4BAA4B,CAAC;AAC3C,cAAc,4BAA4B,CAAC;AAC3C,cAAc,wBAAwB,CAAC;AACvC,cAAc,0BAA0B,CAAC;AACzC,cAAc,8BAA8B,CAAC;AAC7C,cAAc,kCAAkC,CAAC;AACjD,cAAc,sCAAsC,CAAC;AACrD,cAAc,iCAAiC,CAAC;AAChD,cAAc,+BAA+B,CAAC;AAC9C,cAAc,+BAA+B,CAAC;AAC9C,cAAc,4CAA4C,CAAC;AAC3D,cAAc,uCAAuC,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,sBAAsB,CAAC;AACrC,cAAc,4BAA4B,CAAC;AAC3C,cAAc,4BAA4B,CAAC;AAC3C,cAAc,wBAAwB,CAAC;AACvC,cAAc,0BAA0B,CAAC;AACzC,cAAc,8BAA8B,CAAC;AAC7C,cAAc,kCAAkC,CAAC;AACjD,cAAc,sCAAsC,CAAC;AACrD,cAAc,iCAAiC,CAAC;AAChD,cAAc,+BAA+B,CAAC;AAC9C,cAAc,+BAA+B,CAAC;AAC9C,cAAc,4CAA4C,CAAC;AAC3D,cAAc,uCAAuC,CAAC;AACtD,cAAc,oCAAoC,CAAC"}
|
package/dist/esm/index.js
CHANGED
|
@@ -11,4 +11,5 @@ export * from './layout/layout-3d-vocabulary.js';
|
|
|
11
11
|
export * from './layout/layout-3d-definition.js';
|
|
12
12
|
export * from './layout/layout-3d-transition-choreography.js';
|
|
13
13
|
export * from './layout/header-first-shell-container.js';
|
|
14
|
+
export * from './focal-plane/focal-plane-settings.js';
|
|
14
15
|
//# sourceMappingURL=index.js.map
|
package/dist/esm/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,sBAAsB,CAAC;AACrC,cAAc,4BAA4B,CAAC;AAC3C,cAAc,4BAA4B,CAAC;AAC3C,cAAc,wBAAwB,CAAC;AACvC,cAAc,0BAA0B,CAAC;AACzC,cAAc,8BAA8B,CAAC;AAC7C,cAAc,kCAAkC,CAAC;AACjD,cAAc,sCAAsC,CAAC;AACrD,cAAc,iCAAiC,CAAC;AAChD,cAAc,+BAA+B,CAAC;AAC9C,cAAc,+BAA+B,CAAC;AAC9C,cAAc,4CAA4C,CAAC;AAC3D,cAAc,uCAAuC,CAAC","sourcesContent":["export * from './register-3d-layout';\nexport * from './layout-3d-main-container';\nexport * from './shell-region-measurement';\nexport * from './frame/frame-3d-style';\nexport * from './frame/frame-appearance';\nexport * from './frame/use-frame-appearance';\nexport * from './tokens/design-token-resolution';\nexport * from './tokens/use-design-token-resolution';\nexport * from './layout/shell-layout-placement';\nexport * from './layout/layout-3d-vocabulary';\nexport * from './layout/layout-3d-definition';\nexport * from './layout/layout-3d-transition-choreography';\nexport * from './layout/header-first-shell-container';\n"]}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,sBAAsB,CAAC;AACrC,cAAc,4BAA4B,CAAC;AAC3C,cAAc,4BAA4B,CAAC;AAC3C,cAAc,wBAAwB,CAAC;AACvC,cAAc,0BAA0B,CAAC;AACzC,cAAc,8BAA8B,CAAC;AAC7C,cAAc,kCAAkC,CAAC;AACjD,cAAc,sCAAsC,CAAC;AACrD,cAAc,iCAAiC,CAAC;AAChD,cAAc,+BAA+B,CAAC;AAC9C,cAAc,+BAA+B,CAAC;AAC9C,cAAc,4CAA4C,CAAC;AAC3D,cAAc,uCAAuC,CAAC;AACtD,cAAc,oCAAoC,CAAC","sourcesContent":["export * from './register-3d-layout';\nexport * from './layout-3d-main-container';\nexport * from './shell-region-measurement';\nexport * from './frame/frame-3d-style';\nexport * from './frame/frame-appearance';\nexport * from './frame/use-frame-appearance';\nexport * from './tokens/design-token-resolution';\nexport * from './tokens/use-design-token-resolution';\nexport * from './layout/shell-layout-placement';\nexport * from './layout/layout-3d-vocabulary';\nexport * from './layout/layout-3d-definition';\nexport * from './layout/layout-3d-transition-choreography';\nexport * from './layout/header-first-shell-container';\nexport * from './focal-plane/focal-plane-settings';\n"]}
|
|
@@ -34,14 +34,13 @@ export declare const INHERIT_LAYOUT_3D: Layout3DDefinition;
|
|
|
34
34
|
* and returns; when the sidebar collapses or expands, the content slab lifts and settles.
|
|
35
35
|
*/
|
|
36
36
|
export declare const HEADER_FIRST_LAYOUT_3D: Layout3DDefinition;
|
|
37
|
-
/** @wildo_source:part:end presets.3d.layout-contract */
|
|
38
37
|
/**
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
* seen and judged in the running application before it is designed into a production layout.
|
|
38
|
+
* The focal plane (#2049): the shell regions, each navigation pane and each popup are glass objects in depth under one
|
|
39
|
+
* still camera. Whatever you work with rests on the focal plane; the rest steps back. No WebGL frame: the depth is in
|
|
40
|
+
* the HTML objects themselves, so the frame styles and frame transitions below are not drawn.
|
|
43
41
|
*/
|
|
44
|
-
export declare const
|
|
42
|
+
export declare const FOCAL_PLANE_LAYOUT_3D: Layout3DDefinition;
|
|
43
|
+
/** @wildo_source:part:end presets.3d.layout-contract */
|
|
45
44
|
/** Every built-in layout, keyed by its id: total over {@link Layout3DId}, so a new member must ship a layout. */
|
|
46
45
|
export declare const BUILT_IN_LAYOUTS_3D: Record<Layout3DId, Layout3DDefinition>;
|
|
47
46
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"layout-3d-definition.d.ts","sourceRoot":"","sources":["../../../../src/layout/layout-3d-definition.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,qCAAqC,CAAC;AAEtE,OAAO,EAAmD,KAAK,mBAAmB,EAAE,MAAM,yBAAyB,CAAC;AAGpH,OAAO,EAA4B,KAAK,oBAAoB,EAAE,MAAM,0BAA0B,CAAC;AAE/F,OAAO,EAEL,cAAc,EACd,UAAU,EAEV,gBAAgB,EAEhB,KAAK,mBAAmB,EACzB,MAAM,wBAAwB,CAAC;AAEhC,mIAAmI;AACnI;;;GAGG;AACH,MAAM,WAAW,kBAAkB;IACjC,4FAA4F;IAC5F,EAAE,EAAE,MAAM,CAAC;IACX,SAAS,EAAE,oBAAoB,CAAC;IAChC,KAAK,EAAE;QACL,MAAM,EAAE,cAAc,CAAC;QACvB,+FAA+F;QAC/F,WAAW,EAAE,mBAAmB,CAAC;KAClC,CAAC;IACF,qGAAqG;IACrG,WAAW,EAAE,mBAAmB,CAAC;IACjC,WAAW,EAAE;QACX,uHAAuH;QACvH,uBAAuB,EAAE,eAAe,GAAG,IAAI,CAAC;QAChD,QAAQ,EAAE,gBAAgB,CAAC;KAC5B,CAAC;CACH;AAED;;;GAGG;AACH,eAAO,MAAM,iBAAiB,EAAE,kBAW/B,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,sBAAsB,EAAE,kBAWpC,CAAC;AACF
|
|
1
|
+
{"version":3,"file":"layout-3d-definition.d.ts","sourceRoot":"","sources":["../../../../src/layout/layout-3d-definition.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,qCAAqC,CAAC;AAEtE,OAAO,EAAmD,KAAK,mBAAmB,EAAE,MAAM,yBAAyB,CAAC;AAGpH,OAAO,EAA4B,KAAK,oBAAoB,EAAE,MAAM,0BAA0B,CAAC;AAE/F,OAAO,EAEL,cAAc,EACd,UAAU,EAEV,gBAAgB,EAEhB,KAAK,mBAAmB,EACzB,MAAM,wBAAwB,CAAC;AAEhC,mIAAmI;AACnI;;;GAGG;AACH,MAAM,WAAW,kBAAkB;IACjC,4FAA4F;IAC5F,EAAE,EAAE,MAAM,CAAC;IACX,SAAS,EAAE,oBAAoB,CAAC;IAChC,KAAK,EAAE;QACL,MAAM,EAAE,cAAc,CAAC;QACvB,+FAA+F;QAC/F,WAAW,EAAE,mBAAmB,CAAC;KAClC,CAAC;IACF,qGAAqG;IACrG,WAAW,EAAE,mBAAmB,CAAC;IACjC,WAAW,EAAE;QACX,uHAAuH;QACvH,uBAAuB,EAAE,eAAe,GAAG,IAAI,CAAC;QAChD,QAAQ,EAAE,gBAAgB,CAAC;KAC5B,CAAC;CACH;AAED;;;GAGG;AACH,eAAO,MAAM,iBAAiB,EAAE,kBAW/B,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,sBAAsB,EAAE,kBAWpC,CAAC;AACF;;;;GAIG;AACH,eAAO,MAAM,qBAAqB,EAAE,kBAWnC,CAAC;AACF,wDAAwD;AAExD,iHAAiH;AACjH,eAAO,MAAM,mBAAmB,EAAE,MAAM,CAAC,UAAU,EAAE,kBAAkB,CAItE,CAAC;AAEF;;;;;GAKG;AACH,wBAAgB,uBAAuB,CAAC,MAAM,EAAE,kBAAkB,GAAG,IAAI,CAkCxE"}
|