gesso-framework 0.5.1 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,38 @@
1
1
  # gesso-framework
2
2
 
3
+ ## 0.6.0
4
+
5
+ ### Minor Changes
6
+
7
+ - bf21b17: A `Dialog` dims the page behind it, as a browser draws `<dialog>::backdrop`. Palettes have a new `scrim` token, a colour with alpha: the light page's ink at 40% in `lightColors`, black at 60% in `darkColors`, and left alone by high contrast. A custom palette written out in full needs one. Overlay entries take `scrim`, on by default for a `modal` entry and off for every other, so a menu or a list of suggestions never dims the page; the scrim takes its theme from the entry's `environment` and fades in at a dialog's pace, or appears at once under reduced motion. `Dialog` takes `scrim={false}` to leave the page undimmed; its backdrop still keeps the page from presses.
8
+ - 684b59a: A `Dialog` that can't be dismissed now keeps the page from presses, as one that can does: every dialog has a backdrop over the whole page, positioned panels with a `zIndex` included, and `dismissible` decides only whether a press or a wheel on it closes the dialog. Before, a dialog with `dismissible={false}` had no backdrop, so a press beside it reached a button in the page under the modal. Overlay entries take a new `modal` option for the same backdrop.
9
+ - b085707: `ShellService.redirect(url)` sends the page to a url, leaving the app: what a full-page sign-in or sign-out needs, which a component in a worker could not do. Relative urls resolve against the page, only `http:`, `https:` or the page's own scheme are followed (a `javascript:`, `data:` or `blob:` url is refused with a warning), and `createApp({ onRedirect })` lets a host such as a desktop window decide what a redirect means. It differs from the router's `navigate`, which moves within the app.
10
+
11
+ ### Patch Changes
12
+
13
+ - e9f86a2: An anchored overlay (a tooltip, a menu, a popover) now opens beside where its anchor is drawn when an ancestor of the anchor has a `transform`. A card on a panned and zoomed canvas used to get its tooltip where the card would be at zoom 1 with no pan, often nowhere near it; the anchor is now carried through every ancestor's translate, scale and rotation, as well as the scroll offsets it already followed, and an overlay that lives under a transform of its own is placed in that space. The accessibility mirror puts its elements over the same drawn boxes, so a screen reader's outline and touch exploration find the card where it is. A node's own transform still takes no part, so a turning spinner neither shakes its tooltip nor moves its element. `LayoutEngine.screenBox(node)` answers the question for anything else that needs it.
14
+ - b0233fa: A channel's `view` lists its declared keys: `Object.keys`, `in` and a spread now see them. With only a `get` trap, walking a view found nothing, and an app that snapshotted one by its keys got an empty object.
15
+ - 013e064: `color` cascades, as the property reference always said it did: text that names no colour takes the `color` of the nearest ancestor that set one, unless a nearer `textStyle` brings its own. A theme token travels as a name and is resolved where the text is painted, so `<box theme={darkTheme} color="text">` draws its plain text in the dark palette's `text`, and a card inside it that provides another theme draws in that theme's. Until now a node's own `color` reached nothing below it, and plain text under a dark root drew in the default style's near-black on the dark background. Text that already names a colour or a role is unchanged; text that names neither, under a container that sets a `color`, now takes that colour. Changing a cascaded colour repaints the subtree without laying it out again. `UiEnvironmentKeys.color` is the new key the colour travels on, and an overlay carries it from where it was declared.
16
+ - 820aee8: Everything that measures a node against the canvas now takes the `transform` of every ancestor into account, as painting and hit testing always have. Under a panned and zoomed parent (cards on a map inside a "camera" box) several things used to work from where the node would be drawn at zoom 1 with no pan:
17
+
18
+ - A press from the accessibility mirror or an automation tool, and Enter or Space on a focused button, now click the centre the node is drawn at rather than a point that could be off the node entirely.
19
+ - A modifier's `layoutBox()` and the box `onLayout` reports are the drawn box, so a slider, split pane or colour picker on a zoomed card turns a press into the right value, and `onLayout` hears when a pan above the node moves it on screen. Its size is the drawn size, so a fraction of it stays a fraction; `flowBox()` keeps the laid-out size, and the motion pivot, `breakpoint`, `sizeContainer` and `publishInset` now read that, so a zoom neither shifts a pivot nor crosses a breakpoint. The new `measureFlow` modifier is `measure` for the laid-out box; a virtual list's reveal and a `DataTable`'s sticky header use it, so they scroll by the right amount under a zoom.
20
+ - The caret rectangle handed to the shell, which positions the hidden text field and the IME candidate window, follows the caret where it is drawn.
21
+ - A press beside the fields of an editing group, a drag across them, and a text selection dragged past the end of a line find the field or line nearest the pointer on screen.
22
+ - The mirror's box for a focused node that is scrolled out of view, and the layout inspector's highlight and heatmap, are drawn over the node where it is.
23
+
24
+ `LayoutEngine.screenBox(node, part?)` takes an optional rectangle in the node's own coordinates and answers where that part of it is drawn. `EditingHost` and `SelectionHost` take an optional `screenBox`; a host without one behaves as before. With no transform above a node, every answer is the same as before.
25
+
26
+ - cc9e62b: The router writes `match` before `url`, so a subscriber to `url` that reads `match` sees the match for the url it was handed, not the previous one.
27
+ - Updated dependencies [bf21b17]
28
+ - Updated dependencies [3b20918]
29
+ - Updated dependencies [e9f86a2]
30
+ - Updated dependencies [013e064]
31
+ - Updated dependencies [a81d551]
32
+ - Updated dependencies [820aee8]
33
+ - Updated dependencies [684d68a]
34
+ - gesso-core@0.6.0
35
+
3
36
  ## 0.5.1
4
37
 
5
38
  ### Patch Changes
@@ -1184,6 +1184,26 @@ interface OverlayEntry {
1184
1184
  * stays open and follows its anchor through scrolling.
1185
1185
  */
1186
1186
  readonly dismissOnOutsidePress?: boolean;
1187
+ /**
1188
+ * Put the backdrop underneath even when a press outside doesn't close
1189
+ * the entry, as a modal does: everything beneath it, positioned
1190
+ * panels with a zIndex included, is drawn under the backdrop and takes
1191
+ * no press or wheel through it, as the page under a browser's modal
1192
+ * `<dialog>` is inert. Without it, an entry that doesn't close on an
1193
+ * outside press has no backdrop, and a press beside it reaches the
1194
+ * page.
1195
+ */
1196
+ readonly modal?: boolean;
1197
+ /**
1198
+ * Dim the page under the backdrop with the theme's `scrim` colour, as
1199
+ * a browser draws `<dialog>::backdrop`. It fades in as the entry
1200
+ * opens, or appears at once under reduced motion.
1201
+ *
1202
+ * On by default for a modal entry, and only there: a menu or a list
1203
+ * of suggestions closes on a press outside it without saying the page
1204
+ * can't be used. False keeps a modal's backdrop clear.
1205
+ */
1206
+ readonly scrim?: boolean;
1187
1207
  /** Order among open entries; later entries paint on top by default. */
1188
1208
  readonly zIndex?: number;
1189
1209
  /**
@@ -1229,7 +1249,10 @@ declare class OverlayService {
1229
1249
  * positioned box the layout engine places — beside its anchor, by its
1230
1250
  * edge offsets, or centred in the viewport — and, when the entry asks
1231
1251
  * for it, a full-size
1232
- * backdrop beneath it that closes the entry on pointer down or wheel.
1252
+ * backdrop beneath it that takes every press and wheel outside the
1253
+ * entry, and closes the entry on them when it is dismissible. A modal
1254
+ * entry's backdrop is dimmed with the theme's `scrim` unless it asks
1255
+ * for none.
1233
1256
  * Entries without a backdrop stay open and follow their anchor when
1234
1257
  * the content underneath scrolls.
1235
1258
  *
@@ -1641,6 +1664,9 @@ type ShellRequest = {
1641
1664
  } | {
1642
1665
  type: 'openUrl';
1643
1666
  url: string;
1667
+ } | {
1668
+ type: 'redirect';
1669
+ url: string;
1644
1670
  } | {
1645
1671
  type: 'fullscreen';
1646
1672
  enter: boolean;
@@ -1923,6 +1949,33 @@ declare class ShellService {
1923
1949
  settleClipboard(id: number, copied: boolean): void;
1924
1950
  /** Opens a URL in the user's browser, in a new tab or window. */
1925
1951
  openUrl(url: string): void;
1952
+ /**
1953
+ * Replaces the page with `url`, leaving the application.
1954
+ *
1955
+ * Not the router's `navigate`, which moves between screens *inside*
1956
+ * the app and keeps it running: this unloads the page, and whatever
1957
+ * the app held in memory goes with it. It is for the places a page
1958
+ * has to be handed to somebody else's server and wait to be sent
1959
+ * back — an OAuth sign-in that goes to `/api/auth/login`, is
1960
+ * redirected to an identity provider and returns with a session, or
1961
+ * a sign-out at `/api/auth/logout` that clears a cookie only the
1962
+ * server can clear. It is also where a sign-in falls back to when
1963
+ * `openPopup` reports the popup blocked.
1964
+ *
1965
+ * A request rather than a call because `window.location` belongs to
1966
+ * the main thread and a render worker cannot reach it. A relative
1967
+ * url resolves against the page, so `/api/auth/login` means the
1968
+ * app's own server. The shell refuses anything that is not http,
1969
+ * https or the page's own scheme — a `javascript:` url, above all,
1970
+ * which would run in the page rather than go anywhere — so a url
1971
+ * built from data cannot be turned into script.
1972
+ *
1973
+ * Nothing comes back, and there is nothing to wait for: if the
1974
+ * redirect happens, the code that would have read the answer is
1975
+ * gone. Leaving the page is not cancellable from here either, so a
1976
+ * component with unsaved work should settle it before asking.
1977
+ */
1978
+ redirect(url: string): void;
1926
1979
  /**
1927
1980
  * Whether the application's surface is filling the screen.
1928
1981
  *
@@ -1958,8 +2011,8 @@ declare class ShellService {
1958
2011
  * The answer is a promise, the one place in the framework where a
1959
2012
  * shell request has a reply, because there is nothing useful an
1960
2013
  * application can do with a popup it cannot see the fate of. A sign-in
1961
- * flow that is blocked falls back to a full-page redirect, and it can
1962
- * only choose that if it is told.
2014
+ * flow that is blocked falls back to a full-page redirect — `redirect`,
2015
+ * below — and it can only choose that if it is told.
1963
2016
  *
1964
2017
  * Two browser rules shape the contract and both were measured in
1965
2018
  * Chrome before this existed:
@@ -4062,7 +4115,9 @@ declare class GessoRuntime {
4062
4115
  * goes down from the root carrying the scroll and sticky offsets that
4063
4116
  * `visibleBox` adds up per node, and passes over any subtree whose
4064
4117
  * bounds (the hit tester's, see `subtreeBoundsFor`) are off screen.
4065
- * A node it passes over is one this skipped before anyway.
4118
+ * A node it passes over is one this skipped before anyway. Below a
4119
+ * `transform` the walk asks `screenBox` instead, which carries the
4120
+ * box through it.
4066
4121
  */
4067
4122
  private collectSemanticsBoxes;
4068
4123
  /**
@@ -4814,6 +4869,16 @@ type RuntimeToShellMessage = {
4814
4869
  {
4815
4870
  type: 'openUrl';
4816
4871
  url: string;
4872
+ } |
4873
+ /**
4874
+ * Replace the page with a url, leaving the app (ShellService.redirect).
4875
+ * `url` is as the component gave it, relative or not; the shell
4876
+ * resolves it against the page and refuses a scheme that is not
4877
+ * http, https or the page's own.
4878
+ */
4879
+ {
4880
+ type: 'redirect';
4881
+ url: string;
4817
4882
  } | {
4818
4883
  type: 'fullscreen';
4819
4884
  enter: boolean;
@@ -5365,6 +5430,23 @@ interface WorkerAppOptions {
5365
5430
  * reach. `gesso-electrobun`'s bridge is what goes here.
5366
5431
  */
5367
5432
  onOpenUrl?: (url: string) => void;
5433
+ /**
5434
+ * Replaces the page with a url the application asked for
5435
+ * (`ShellService.redirect`), in place of `window.location.assign`.
5436
+ *
5437
+ * A page wants `location.assign`, which is the default, and it is
5438
+ * usually right in a desktop window too: the webview navigates, the
5439
+ * way a page would, and comes back when the server redirects it back.
5440
+ * A host that has to do something else — reload the bundled app
5441
+ * rather than leave it, or send a sign-in to the person's browser and
5442
+ * wait for a deep link — handles it here.
5443
+ *
5444
+ * Called with the url resolved against the page, and only once the
5445
+ * shell has admitted it: a `javascript:` url, or any scheme that is
5446
+ * not http, https or the page's own, is refused before this is
5447
+ * consulted, so a handler need not check again.
5448
+ */
5449
+ onRedirect?: (url: string) => void;
5368
5450
  /**
5369
5451
  * Offer the application's channels to an AI agent in the browser,
5370
5452
  * through WebMCP (default false). Each channel's view and commands
@@ -8212,4 +8294,4 @@ declare class RenderWorkerApp {
8212
8294
  }
8213
8295
  //#endregion
8214
8296
  export { StorageAdapter as $, UiOwnerReport as $n, Resource as $r, FontFaceDeclaration as $t, Presence as A, AudioState as An, Channel as Ar, ActionCause as At, PersistedOptions as B, ShellFileType as Bn, bind as Br, UiTreeSnapshot as Bt, FocusService as C, formatUrl as Cn, pickKeys as Cr, AppLogicEndpoint as Ct, FindService as D, AudioRequest as Dn, findUnplainPath as Dr, ShellHistoryMode as Dt, TextService as E, AudioMetadata as En, createChannelRegistry as Er, ShellHistory as Et, AudioSinkOptions as F, UiDuration as Fn, ControlledOptions as Fr, DevtoolsEvent as Ft, IndexedDbStorage as G, ShellStorageResult as Gn, EachKey as Gr, FrameMetrics as Gt, persisted as H, ShellRequest as Hn, ShowProps as Hr, RuntimeErrorSource as Ht, AudioSinkOutput as I, UiEasingChoice as In, ControlledValue as Ir, DevtoolsRequest as It, OpfsFileHandle as J, observeColorScheme as Jn, debounced as Jr, GessoRuntimeOptions as Jt, IndexedDbStorageOptions as K, ColorScheme as Kn, EachProps as Kr, FramePhaseTimings as Kt, MediaSessionLike as L, ShellFile as Ln, controlled as Lr, FrameEntry as Lt, RouterOutlet as M, AnimateOptions as Mn, Inject as Mr, ChannelErrorEntry as Mt, AudioElementLike as N, AnimationService as Nn, Input as Nr, CommandEntry as Nt, observeMediaQuery as O, AudioSample as On, requirePlainData as Or, ShellHistoryOptions as Ot, AudioSink as P, SpringOptions as Pn, Output as Pr, ConsoleEntry as Pt, MemoryStorage as Q, UiNodeReport as Qn, mutate as Qr, UiFramePhase as Qt, audioClock as R, ShellFileRequest as Rn, ThemeTokenCell as Rr, PatchEntry as Rt, writeClipboard as S, buildPath as Sn, pick as Sr, createApp as St, EditingService as T, AudioAction as Tn, ChannelRegistryHandle as Tr, WorkerAppOptions as Tt, ShellStorage as U, ShellService as Un, show as Ur, RuntimeToShellMessage as Ut, PersistedState as V, ShellRecentFile as Vn, Show as Vr, treeText as Vt, ShellStorageOptions as W, ShellStorageOp as Wn, Each as Wr, ShellToRuntimeMessage as Wt, OpfsStorageOptions as X, NodePathTarget as Xn, MutateOptions as Xr, RuntimeInput as Xt, OpfsStorage as Y, BoundStream as Yn, throttled as Yr, RendererChoice as Yt, OpfsWritable as Z, UiEnvironmentReport as Zn, Mutation as Zr, UI_FRAME_PHASES as Zt, EditingMirrorTarget as _, derive as _i, RouteOptions as _n, createComponent as _r, GessoAppOptions as _t, UiRole as a, select as ai, MediaOptions as an, formatAge as ar, storageReadValue as at, EditingProxy as b, to as bn, isComponentElement as br, WebMcpChoice as bt, UiSemanticsPatch as c, FanOut as ci, RouteMatch as cn, formatStream as cr, UndoableOptions as ct, markNow as d, ComputedCell as di, RouterService as dn, ComponentHost as dr, UndoStackOptions as dt, ResourceOptions as ei, FontFaceLike as en, UiPropOrigin as er, StorageOutcome as et, measureSpan as f, ComputedOptions as fi, RouteState as fn, OverlayLayer as fr, UndoTransaction as ft, renderRoot as g, Equality as gi, RouteGuard as gn, OverlayService as gr, GessoApp as gt, RenderWorkerApp as h, DeriveOptions as hi, RouteDefinition as hn, OverlayRect as hr, shellStorageDenied as ht, UI_SEMANTIC_STATES as i, SelectOptions as ii, FontService as in, describeStream as ir, storageReadFailure as it, PresenceProps as j, AudioStatus as jn, Define as jr, ActionEntry as jt, observeReducedMotion as k, AudioService as kn, ChannelRegistry as kr, createShellHistory as kt, UiSemanticsRecord$1 as l, FanOutOptions as li, RouterHistorySink as ln, printPropValue as lr, undoable as lt, setPerformanceMarks$1 as m, computed as mi, RouteContext as mn, OverlayPlacement as mr, performShellStorage as mt, MARK_PREFIX as n, ResourceStatus as ni, FontFamilyStatus as nn, UiSemanticsReport as nr, classifyStorageError as nt, UiSemanticState as o, FanCell as oi, MediaService as on, formatNodePath as or, UndoShortcutOptions as ot, performanceMarksEnabled as p, ReadSource as pi, OutletProps as pn, OverlayEntry as pr, ShellLocalStore as pt, OpfsDirectory as q, ColorSchemePreference as qn, each as qr, GessoRuntime as qt, UI_ROLES as r, resource as ri, FontHost as rn, UiStreamReport as rr, storageErrorMessage as rt, UiSemanticsMap$1 as s, FanKey as si, RouteAnswer as sn, formatNodeReport as sr, registerUndoShortcuts as st, EditingState$1 as t, ResourceState as ti, FontFamilyDeclaration as tn, UiPropReport as tr, StorageRead as tt, markInstant as u, fanOut as ui, RouterRoutes as un, ComponentHostResolver as ur, UndoStack as ut, SemanticsMirror as v, ServiceRegistry as vi, RouteTarget as vn, ComponentElement as vr, createSyncApp as vt, ScrollService as w, parseUrl as wn, ChannelRegistration as wr, WorkerApp as wt, EditingProxySink as x, RouteParams as xn, structurallyEqual as xr, CreateAppOptions as xt, SemanticsMirrorSink as y, route as yn, FrameworkChild as yr, GessoAppBuilder as yt, FrameService as z, ShellFileResult as zn, themeTokenCell as zr, UiTreeNode as zt };
8215
- //# sourceMappingURL=index-Sff7qPGU.d.ts.map
8297
+ //# sourceMappingURL=index-DidpPgDT.d.ts.map
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { _ as channel, a as isChannelHostMessage, c as applyPatch, d as ChannelSpec, f as ChannelToken, g as ViewOf, h as CommandsOf, i as isChannelClientMessage, l as applyPatches, m as CommandMap, n as ChannelHostMessage, o as Patch, p as Command, r as ChannelPort, s as PatchPath, t as ChannelClientMessage, u as diffProjection, v as defineChannel, y as viewKeys } from "./ChannelProtocol-ByNoHujM.js";
2
2
  import { A as portHandle, C as MessageEndpoint, D as isHubMessage, E as WorkerHandle, F as JsonSchema, I as channelSchema, L as describeChannel, M as workerHandle, N as ChannelSchema, O as isPortErrorMessage, P as CommandSchema, S as APPLICATION_WORKER, T as PortHost, _ as serve, b as ProvidedChannel, g as ServedChannel, j as servePorts, k as isPortHandshake, v as serveChannels, w as PortHandshake, x as provide, y as ChannelSource } from "./ui-U39HjNFA.js";
3
3
  import { C as internalState, S as InternalState, _ as ReadableCell, a as ComponentType, b as isOutputTarget, c as isClassComponent, d as bounds, f as EmitArgs, g as OutputTarget, h as OutputCell, i as ComponentProps, l as ChannelReplica, m as InputCell, o as FunctionComponent, p as EmitValue, r as ComponentContext, s as Inputs, t as ClassComponent, u as BoundsCell, v as input, w as Component, x as output, y as into } from "./FunctionComponent-fYMdePtH.js";
4
- import { $ as StorageAdapter, $n as UiOwnerReport, $r as Resource, $t as FontFaceDeclaration, A as Presence, An as AudioState, Ar as Channel, At as ActionCause, B as PersistedOptions, Bn as ShellFileType, Br as bind, Bt as UiTreeSnapshot, C as FocusService, Cn as formatUrl, Cr as pickKeys, Ct as AppLogicEndpoint, D as FindService, Dn as AudioRequest, Dr as findUnplainPath, Dt as ShellHistoryMode, E as TextService, En as AudioMetadata, Er as createChannelRegistry, Et as ShellHistory, F as AudioSinkOptions, Fn as UiDuration, Fr as ControlledOptions, Ft as DevtoolsEvent, G as IndexedDbStorage, Gn as ShellStorageResult, Gr as EachKey, Gt as FrameMetrics, H as persisted, Hn as ShellRequest, Hr as ShowProps, Ht as RuntimeErrorSource, I as AudioSinkOutput, In as UiEasingChoice, Ir as ControlledValue, It as DevtoolsRequest, J as OpfsFileHandle, Jn as observeColorScheme, Jr as debounced, Jt as GessoRuntimeOptions, K as IndexedDbStorageOptions, Kn as ColorScheme, Kr as EachProps, Kt as FramePhaseTimings, L as MediaSessionLike, Ln as ShellFile, Lr as controlled, Lt as FrameEntry, M as RouterOutlet, Mn as AnimateOptions, Mr as Inject, Mt as ChannelErrorEntry, N as AudioElementLike, Nn as AnimationService, Nr as Input, Nt as CommandEntry, O as observeMediaQuery, On as AudioSample, Or as requirePlainData, Ot as ShellHistoryOptions, P as AudioSink, Pn as SpringOptions, Pr as Output, Pt as ConsoleEntry, Q as MemoryStorage, Qn as UiNodeReport, Qr as mutate, Qt as UiFramePhase, R as audioClock, Rn as ShellFileRequest, Rr as ThemeTokenCell, Rt as PatchEntry, S as writeClipboard, Sn as buildPath, Sr as pick, St as createApp, T as EditingService, Tn as AudioAction, Tr as ChannelRegistryHandle, Tt as WorkerAppOptions, U as ShellStorage, Un as ShellService, Ur as show, Ut as RuntimeToShellMessage, V as PersistedState, Vn as ShellRecentFile, Vr as Show, Vt as treeText, W as ShellStorageOptions, Wn as ShellStorageOp, Wr as Each, Wt as ShellToRuntimeMessage, X as OpfsStorageOptions, Xn as NodePathTarget, Xr as MutateOptions, Xt as RuntimeInput, Y as OpfsStorage, Yn as BoundStream, Yr as throttled, Yt as RendererChoice, Z as OpfsWritable, Zn as UiEnvironmentReport, Zr as Mutation, Zt as UI_FRAME_PHASES, _ as EditingMirrorTarget, _i as derive, _n as RouteOptions, _r as createComponent, _t as GessoAppOptions, a as UiRole, ai as select, an as MediaOptions, ar as formatAge, at as storageReadValue, b as EditingProxy, bn as to, br as isComponentElement, bt as WebMcpChoice, c as UiSemanticsPatch, ci as FanOut, cn as RouteMatch, cr as formatStream, ct as UndoableOptions, d as markNow, di as ComputedCell, dn as RouterService, dr as ComponentHost, dt as UndoStackOptions, ei as ResourceOptions, en as FontFaceLike, er as UiPropOrigin, et as StorageOutcome, f as measureSpan, fi as ComputedOptions, fn as RouteState, fr as OverlayLayer, ft as UndoTransaction, g as renderRoot, gi as Equality, gn as RouteGuard, gr as OverlayService, gt as GessoApp, h as RenderWorkerApp, hi as DeriveOptions, hn as RouteDefinition, hr as OverlayRect, ht as shellStorageDenied, i as UI_SEMANTIC_STATES, ii as SelectOptions, in as FontService, ir as describeStream, it as storageReadFailure, j as PresenceProps, jn as AudioStatus, jr as Define, jt as ActionEntry, k as observeReducedMotion, kn as AudioService, kr as ChannelRegistry, kt as createShellHistory, l as UiSemanticsRecord, li as FanOutOptions, ln as RouterHistorySink, lr as printPropValue, lt as undoable, m as setPerformanceMarks, mi as computed, mn as RouteContext, mr as OverlayPlacement, mt as performShellStorage, n as MARK_PREFIX, ni as ResourceStatus, nn as FontFamilyStatus, nr as UiSemanticsReport, nt as classifyStorageError, o as UiSemanticState, oi as FanCell, on as MediaService, or as formatNodePath, ot as UndoShortcutOptions, p as performanceMarksEnabled, pi as ReadSource, pn as OutletProps, pr as OverlayEntry, pt as ShellLocalStore, q as OpfsDirectory, qn as ColorSchemePreference, qr as each, qt as GessoRuntime, r as UI_ROLES, ri as resource, rn as FontHost, rr as UiStreamReport, rt as storageErrorMessage, s as UiSemanticsMap, si as FanKey, sn as RouteAnswer, sr as formatNodeReport, st as registerUndoShortcuts, t as EditingState, ti as ResourceState, tn as FontFamilyDeclaration, tr as UiPropReport, tt as StorageRead, u as markInstant, ui as fanOut, un as RouterRoutes, ur as ComponentHostResolver, ut as UndoStack, v as SemanticsMirror, vi as ServiceRegistry, vn as RouteTarget, vr as ComponentElement, vt as createSyncApp, w as ScrollService, wn as parseUrl, wr as ChannelRegistration, wt as WorkerApp, x as EditingProxySink, xn as RouteParams, xr as structurallyEqual, xt as CreateAppOptions, y as SemanticsMirrorSink, yn as route, yr as FrameworkChild, yt as GessoAppBuilder, z as FrameService, zn as ShellFileResult, zr as themeTokenCell, zt as UiTreeNode } from "./index-Sff7qPGU.js";
4
+ import { $ as StorageAdapter, $n as UiOwnerReport, $r as Resource, $t as FontFaceDeclaration, A as Presence, An as AudioState, Ar as Channel, At as ActionCause, B as PersistedOptions, Bn as ShellFileType, Br as bind, Bt as UiTreeSnapshot, C as FocusService, Cn as formatUrl, Cr as pickKeys, Ct as AppLogicEndpoint, D as FindService, Dn as AudioRequest, Dr as findUnplainPath, Dt as ShellHistoryMode, E as TextService, En as AudioMetadata, Er as createChannelRegistry, Et as ShellHistory, F as AudioSinkOptions, Fn as UiDuration, Fr as ControlledOptions, Ft as DevtoolsEvent, G as IndexedDbStorage, Gn as ShellStorageResult, Gr as EachKey, Gt as FrameMetrics, H as persisted, Hn as ShellRequest, Hr as ShowProps, Ht as RuntimeErrorSource, I as AudioSinkOutput, In as UiEasingChoice, Ir as ControlledValue, It as DevtoolsRequest, J as OpfsFileHandle, Jn as observeColorScheme, Jr as debounced, Jt as GessoRuntimeOptions, K as IndexedDbStorageOptions, Kn as ColorScheme, Kr as EachProps, Kt as FramePhaseTimings, L as MediaSessionLike, Ln as ShellFile, Lr as controlled, Lt as FrameEntry, M as RouterOutlet, Mn as AnimateOptions, Mr as Inject, Mt as ChannelErrorEntry, N as AudioElementLike, Nn as AnimationService, Nr as Input, Nt as CommandEntry, O as observeMediaQuery, On as AudioSample, Or as requirePlainData, Ot as ShellHistoryOptions, P as AudioSink, Pn as SpringOptions, Pr as Output, Pt as ConsoleEntry, Q as MemoryStorage, Qn as UiNodeReport, Qr as mutate, Qt as UiFramePhase, R as audioClock, Rn as ShellFileRequest, Rr as ThemeTokenCell, Rt as PatchEntry, S as writeClipboard, Sn as buildPath, Sr as pick, St as createApp, T as EditingService, Tn as AudioAction, Tr as ChannelRegistryHandle, Tt as WorkerAppOptions, U as ShellStorage, Un as ShellService, Ur as show, Ut as RuntimeToShellMessage, V as PersistedState, Vn as ShellRecentFile, Vr as Show, Vt as treeText, W as ShellStorageOptions, Wn as ShellStorageOp, Wr as Each, Wt as ShellToRuntimeMessage, X as OpfsStorageOptions, Xn as NodePathTarget, Xr as MutateOptions, Xt as RuntimeInput, Y as OpfsStorage, Yn as BoundStream, Yr as throttled, Yt as RendererChoice, Z as OpfsWritable, Zn as UiEnvironmentReport, Zr as Mutation, Zt as UI_FRAME_PHASES, _ as EditingMirrorTarget, _i as derive, _n as RouteOptions, _r as createComponent, _t as GessoAppOptions, a as UiRole, ai as select, an as MediaOptions, ar as formatAge, at as storageReadValue, b as EditingProxy, bn as to, br as isComponentElement, bt as WebMcpChoice, c as UiSemanticsPatch, ci as FanOut, cn as RouteMatch, cr as formatStream, ct as UndoableOptions, d as markNow, di as ComputedCell, dn as RouterService, dr as ComponentHost, dt as UndoStackOptions, ei as ResourceOptions, en as FontFaceLike, er as UiPropOrigin, et as StorageOutcome, f as measureSpan, fi as ComputedOptions, fn as RouteState, fr as OverlayLayer, ft as UndoTransaction, g as renderRoot, gi as Equality, gn as RouteGuard, gr as OverlayService, gt as GessoApp, h as RenderWorkerApp, hi as DeriveOptions, hn as RouteDefinition, hr as OverlayRect, ht as shellStorageDenied, i as UI_SEMANTIC_STATES, ii as SelectOptions, in as FontService, ir as describeStream, it as storageReadFailure, j as PresenceProps, jn as AudioStatus, jr as Define, jt as ActionEntry, k as observeReducedMotion, kn as AudioService, kr as ChannelRegistry, kt as createShellHistory, l as UiSemanticsRecord, li as FanOutOptions, ln as RouterHistorySink, lr as printPropValue, lt as undoable, m as setPerformanceMarks, mi as computed, mn as RouteContext, mr as OverlayPlacement, mt as performShellStorage, n as MARK_PREFIX, ni as ResourceStatus, nn as FontFamilyStatus, nr as UiSemanticsReport, nt as classifyStorageError, o as UiSemanticState, oi as FanCell, on as MediaService, or as formatNodePath, ot as UndoShortcutOptions, p as performanceMarksEnabled, pi as ReadSource, pn as OutletProps, pr as OverlayEntry, pt as ShellLocalStore, q as OpfsDirectory, qn as ColorSchemePreference, qr as each, qt as GessoRuntime, r as UI_ROLES, ri as resource, rn as FontHost, rr as UiStreamReport, rt as storageErrorMessage, s as UiSemanticsMap, si as FanKey, sn as RouteAnswer, sr as formatNodeReport, st as registerUndoShortcuts, t as EditingState, ti as ResourceState, tn as FontFamilyDeclaration, tr as UiPropReport, tt as StorageRead, u as markInstant, ui as fanOut, un as RouterRoutes, ur as ComponentHostResolver, ut as UndoStack, v as SemanticsMirror, vi as ServiceRegistry, vn as RouteTarget, vr as ComponentElement, vt as createSyncApp, w as ScrollService, wn as parseUrl, wr as ChannelRegistration, wt as WorkerApp, x as EditingProxySink, xn as RouteParams, xr as structurallyEqual, xt as CreateAppOptions, y as SemanticsMirrorSink, yn as route, yr as FrameworkChild, yt as GessoAppBuilder, z as FrameService, zn as ShellFileResult, zr as themeTokenCell, zt as UiTreeNode } from "./index-DidpPgDT.js";
5
5
  export { APPLICATION_WORKER, type ActionCause, type ActionEntry, type AnimateOptions, AnimationService, type AppLogicEndpoint, type AudioAction, type AudioElementLike, type AudioMetadata, type AudioRequest, type AudioSample, AudioService, AudioSink, type AudioSinkOptions, type AudioSinkOutput, type AudioState, type AudioStatus, type BoundStream, BoundsCell, Channel, type ChannelClientMessage, type ChannelErrorEntry, type ChannelHostMessage, type ChannelPort, type ChannelRegistration, ChannelRegistry, type ChannelRegistryHandle, ChannelReplica, type ChannelSchema, type ChannelSource, type ChannelSpec, type ChannelToken, type ClassComponent, type ColorScheme, type ColorSchemePreference, type Command, type CommandEntry, type CommandMap, type CommandSchema, type CommandsOf, Component, type ComponentContext, type ComponentElement, ComponentHost, ComponentHostResolver, type ComponentProps, type ComponentType, ComputedCell, type ComputedOptions, type ConsoleEntry, type ControlledOptions, type ControlledValue, type CreateAppOptions, Define, type DeriveOptions, type DevtoolsEvent, type DevtoolsRequest, Each, type EachKey, type EachProps, type EditingMirrorTarget, EditingProxy, type EditingProxySink, EditingService, type EditingState, type EmitArgs, type EmitValue, type Equality, FanCell, type FanKey, FanOut, type FanOutOptions, FindService, FocusService, type FontFaceDeclaration, type FontFaceLike, type FontFamilyDeclaration, type FontFamilyStatus, type FontHost, FontService, type FrameEntry, type FrameMetrics, type FramePhaseTimings, FrameService, type FrameworkChild, type FunctionComponent, GessoApp, GessoAppBuilder, type GessoAppOptions, GessoRuntime, type GessoRuntimeOptions, IndexedDbStorage, type IndexedDbStorageOptions, Inject, Input, InputCell, type Inputs, InternalState, type JsonSchema, MARK_PREFIX, type MediaOptions, MediaService, type MediaSessionLike, MemoryStorage, type MessageEndpoint, type MutateOptions, type Mutation, type NodePathTarget, type OpfsDirectory, type OpfsFileHandle, OpfsStorage, type OpfsStorageOptions, type OpfsWritable, type OutletProps, Output, type OutputCell, type OutputTarget, type OverlayEntry, OverlayLayer, type OverlayPlacement, type OverlayRect, OverlayService, type Patch, type PatchEntry, type PatchPath, type PersistedOptions, PersistedState, type PortHandshake, type PortHost, Presence, type PresenceProps, ProvidedChannel, type ReadSource, type ReadableCell, RenderWorkerApp, type RendererChoice, Resource, type ResourceOptions, type ResourceState, type ResourceStatus, type RouteAnswer, type RouteContext, type RouteDefinition, type RouteGuard, type RouteMatch, type RouteOptions, type RouteParams, RouteState, type RouteTarget, type RouterHistorySink, RouterOutlet, type RouterRoutes, RouterService, type RuntimeErrorSource, type RuntimeInput, type RuntimeToShellMessage, ScrollService, type SelectOptions, SemanticsMirror, type SemanticsMirrorSink, type ServedChannel, ServiceRegistry, type ShellFile, type ShellFileRequest, type ShellFileResult, type ShellFileType, type ShellHistory, type ShellHistoryMode, type ShellHistoryOptions, type ShellLocalStore, type ShellRecentFile, type ShellRequest, ShellService, ShellStorage, type ShellStorageOp, type ShellStorageOptions, type ShellStorageResult, type ShellToRuntimeMessage, Show, type ShowProps, type SpringOptions, type StorageAdapter, type StorageOutcome, type StorageRead, TextService, ThemeTokenCell, UI_FRAME_PHASES, UI_ROLES, UI_SEMANTIC_STATES, type UiDuration, type UiEasingChoice, type UiEnvironmentReport, type UiFramePhase, type UiNodeReport, type UiOwnerReport, type UiPropOrigin, type UiPropReport, type UiRole, type UiSemanticState, type UiSemanticsMap, type UiSemanticsPatch, type UiSemanticsRecord, type UiSemanticsReport, type UiStreamReport, type UiTreeNode, type UiTreeSnapshot, type UndoShortcutOptions, UndoStack, type UndoStackOptions, type UndoTransaction, type UndoableOptions, type ViewOf, type WebMcpChoice, WorkerApp, type WorkerAppOptions, type WorkerHandle, applyPatch, applyPatches, audioClock, bind, bounds, buildPath, channel, channelSchema, classifyStorageError, computed, controlled, createApp, createChannelRegistry, createComponent, createShellHistory, createSyncApp, debounced, defineChannel, derive, describeChannel, describeStream, diffProjection, each, fanOut, findUnplainPath, formatAge, formatNodePath, formatNodeReport, formatStream, formatUrl, input, internalState, into, isChannelClientMessage, isChannelHostMessage, isClassComponent, isComponentElement, isHubMessage, isOutputTarget, isPortErrorMessage, isPortHandshake, markInstant, markNow, measureSpan, mutate, observeColorScheme, observeMediaQuery, observeReducedMotion, output, parseUrl, performShellStorage, performanceMarksEnabled, persisted, pick, pickKeys, portHandle, printPropValue, provide, registerUndoShortcuts, renderRoot, requirePlainData, resource, route, select, serve, serveChannels, servePorts, setPerformanceMarks, shellStorageDenied, show, storageErrorMessage, storageReadFailure, storageReadValue, structurallyEqual, themeTokenCell, throttled, to, treeText, undoable, viewKeys, workerHandle, writeClipboard };
package/dist/index.js CHANGED
@@ -3,7 +3,7 @@ import { a as Each, i as show, n as getComponentMetadata, o as each, r as Show,
3
3
  import { c as channelSchema, l as describeChannel, t as AGENT_PORT } from "./remote-xxij8zXe.js";
4
4
  import { n as serveApplicationAgent } from "./app-BvlIO1G9.js";
5
5
  import { BehaviorSubject, EMPTY, Observable, Subject, Subscription, distinctUntilChanged, map } from "rxjs";
6
- import { AnimationDriver, Box, Canvas2DRenderer, CanvasPlatformSurface, CanvasTextMeasurer, Constraints, DefaultImageResolver, DefaultVideoResolver, DirtyFlags, EnvironmentNotifier, FocusNotifier, IconRasterizer, LayoutEngine, LayoutInspector, LayoutNotifier, MARK_PREFIX, Stack, TEXT_RUN_ID_SEPARATOR, UI_ROLES, UI_SEMANTIC_STATES, UiAnimationFrameClock, UiEditingController, UiEnvironmentKeys, UiEventType, UiFindController, UiFocusManager, UiGestureRecognizer, UiGraph, UiGraphBuilder, UiHitTester, UiHostFrameClock, UiInputDispatcher, UiInsetRegistry, UiKeyboardController, UiNodeType, UiPlatformAdapter, UiPointerController, UiPointerEvent, UiScheduler, UiSelectionController, UiSharedElements, UiSpring, UiTimerFrameClock, UiTouchScroller, UiVirtualSheet, UiVirtualWindow, UiWheelController, VIRTUAL_INDEX_PROP, VIRTUAL_LEAD_PROP, VIRTUAL_SHEET_PROP, VIRTUAL_WINDOW_PROP, WebGPURenderer, buildSemanticsSubtree, buildSemanticsTree, bumpFontStack, capturePointer, createCanvasSurface, createTween, createWebGPUSurface, defaultMotion, defineModifier, describeOverrides, diffSemantics, dragSessionFor, dropIndexShifts, easings, formatExplanation, insetsEqual, isComponentLikeElement, isObservable, isWebGPUAvailable, linear, linkOf, markInstant, markNow, measure, measureSpan, motion, noInsets, observeViewportInsets, percent, performanceMarksEnabled, pointerDeviceOf, prepareInputSurface, propertyEffects, recordsEqual, registerFontStack, resolveCursor, rewalkSemantics, scrollRange, scrollbarThumb, semanticsInertAbove, semanticsMemory, setPerformanceMarks, setPerformanceMarks as setPerformanceMarks$1, textRunOfRecordId, themeTokens, touchActionFor, wheelDeltaYOf } from "gesso-core";
6
+ import { AnimationDriver, Box, Canvas2DRenderer, CanvasPlatformSurface, CanvasTextMeasurer, Constraints, DefaultImageResolver, DefaultVideoResolver, DirtyFlags, EnvironmentNotifier, FocusNotifier, IconRasterizer, LayoutEngine, LayoutInspector, LayoutNotifier, MARK_PREFIX, Stack, TEXT_RUN_ID_SEPARATOR, UI_ROLES, UI_SEMANTIC_STATES, UiAnimationFrameClock, UiEditingController, UiEnvironmentKeys, UiEventType, UiFindController, UiFocusManager, UiGestureRecognizer, UiGraph, UiGraphBuilder, UiHitTester, UiHostFrameClock, UiInputDispatcher, UiInsetRegistry, UiKeyboardController, UiNodeType, UiPlatformAdapter, UiPointerController, UiPointerEvent, UiScheduler, UiSelectionController, UiSharedElements, UiSpring, UiTimerFrameClock, UiTouchScroller, UiVirtualSheet, UiVirtualWindow, UiWheelController, VIRTUAL_INDEX_PROP, VIRTUAL_LEAD_PROP, VIRTUAL_SHEET_PROP, VIRTUAL_WINDOW_PROP, WebGPURenderer, buildSemanticsSubtree, buildSemanticsTree, bumpFontStack, capturePointer, createCanvasSurface, createTween, createWebGPUSurface, defaultMotion, defineModifier, describeOverrides, diffSemantics, dragSessionFor, dropIndexShifts, easings, fade, formatExplanation, insetsEqual, isComponentLikeElement, isObservable, isWebGPUAvailable, linear, linkOf, markInstant, markNow, measure, measureSpan, motion, noInsets, observeViewportInsets, percent, performanceMarksEnabled, pointerDeviceOf, prepareInputSurface, propertyEffects, recordsEqual, registerFontStack, resolveCursor, rewalkSemantics, scrollRange, scrollbarThumb, semanticsInertAbove, semanticsMemory, setPerformanceMarks, setPerformanceMarks as setPerformanceMarks$1, textRunOfRecordId, themeTokens, touchActionFor, wheelDeltaYOf } from "gesso-core";
7
7
  //#region src/Component.ts
8
8
  /**
9
9
  * Base class for all Gesso framework components.
@@ -587,15 +587,28 @@ var ChannelReplica = class {
587
587
  get send() {
588
588
  return this.commandProxy;
589
589
  }
590
- viewProxy = new Proxy({}, { get: (_target, property) => {
591
- if (typeof property !== "string") return;
592
- const cell = this.cells.get(property);
593
- if (cell === void 0) {
594
- const names = [...this.cells.keys()].sort().join(", ");
595
- throw new Error(`'${property}' is not a view key on channel '${this.token.name}'. Declared keys: ${names.length > 0 ? names : "(none)"}.`);
590
+ viewProxy = new Proxy({}, {
591
+ get: (_target, property) => {
592
+ if (typeof property !== "string") return;
593
+ const cell = this.cells.get(property);
594
+ if (cell === void 0) {
595
+ const names = [...this.cells.keys()].sort().join(", ");
596
+ throw new Error(`'${property}' is not a view key on channel '${this.token.name}'. Declared keys: ${names.length > 0 ? names : "(none)"}.`);
597
+ }
598
+ return cell;
599
+ },
600
+ has: (_target, property) => typeof property === "string" && this.cells.has(property),
601
+ ownKeys: () => [...this.cells.keys()],
602
+ getOwnPropertyDescriptor: (_target, property) => {
603
+ const cell = typeof property === "string" ? this.cells.get(property) : void 0;
604
+ return cell === void 0 ? void 0 : {
605
+ value: cell,
606
+ enumerable: true,
607
+ configurable: true,
608
+ writable: false
609
+ };
596
610
  }
597
- return cell;
598
- } });
611
+ });
599
612
  createCommandProxy() {
600
613
  return new Proxy({}, { get: (_target, property) => {
601
614
  if (typeof property !== "string") return;
@@ -841,14 +854,22 @@ let OverlayLayer = class OverlayLayer extends Component {
841
854
  renderEntries(entries) {
842
855
  const elements = [];
843
856
  for (const entry of entries) {
844
- if (entry.dismissOnOutsidePress) elements.push(Box({
845
- key: `${entry.id}\0backdrop`,
846
- position: "absolute",
847
- inset: 0,
848
- zIndex: entry.zIndex,
849
- onPointerDown: () => this.overlays.close(entry.id),
850
- onWheel: () => this.overlays.close(entry.id)
851
- }));
857
+ if (entry.dismissOnOutsidePress === true || entry.modal === true) {
858
+ const dismiss = entry.dismissOnOutsidePress === true;
859
+ const environment = entry.environment ?? entry.anchor ?? null;
860
+ elements.push(Box({
861
+ key: `${entry.id}\0backdrop`,
862
+ position: "absolute",
863
+ inset: 0,
864
+ zIndex: entry.zIndex,
865
+ ...inheritedFrom(environment),
866
+ modifiers: followEnvironment(environment),
867
+ ...dismiss ? {
868
+ onPointerDown: () => this.overlays.close(entry.id),
869
+ onWheel: () => this.overlays.close(entry.id)
870
+ } : {}
871
+ }, ...entry.scrim ?? entry.modal === true ? [scrim()] : []));
872
+ }
852
873
  elements.push(Box({
853
874
  key: entry.id,
854
875
  position: "absolute",
@@ -873,6 +894,28 @@ let OverlayLayer = class OverlayLayer extends Component {
873
894
  __decorate([Inject(OverlayService)], OverlayLayer.prototype, "overlays", void 0);
874
895
  OverlayLayer = __decorate([Define("gesso-overlay-layer")], OverlayLayer);
875
896
  /**
897
+ * The dim over the page, inside the backdrop rather than on it.
898
+ *
899
+ * A node at opacity 0 takes no presses, so a backdrop fading in would
900
+ * let a press through to the page on the frame it opens. The fade is
901
+ * this child's; the backdrop under it is solid to input from the start,
902
+ * and the child takes none of its own. It comes in at the pace a
903
+ * `Dialog` does, and under reduced motion it is simply there.
904
+ */
905
+ function scrim() {
906
+ return Box({
907
+ position: "absolute",
908
+ inset: 0,
909
+ hitTestable: false,
910
+ backgroundColor: "scrim",
911
+ modifiers: [motion({
912
+ initial: fade,
913
+ duration: "slow",
914
+ easing: "decelerate"
915
+ })]
916
+ });
917
+ }
918
+ /**
876
919
  * `center` as the layout engine takes it.
877
920
  *
878
921
  * Both edges of the axis are pinned, which stretches the entry's box
@@ -915,6 +958,7 @@ function inheritedFrom(node) {
915
958
  return {
916
959
  theme: environment.get(UiEnvironmentKeys.theme),
917
960
  textStyle: environment.get(UiEnvironmentKeys.textStyle),
961
+ color: environment.get(UiEnvironmentKeys.color),
918
962
  contentColor: environment.get(UiEnvironmentKeys.contentColor)
919
963
  };
920
964
  }
@@ -933,6 +977,7 @@ const followEnvironmentKind = defineModifier({
933
977
  host.onEnvironment(() => {
934
978
  host.set("theme", host.environment(UiEnvironmentKeys.theme, from));
935
979
  host.set("textStyle", host.environment(UiEnvironmentKeys.textStyle, from));
980
+ host.set("color", host.environment(UiEnvironmentKeys.color, from));
936
981
  host.set("contentColor", host.environment(UiEnvironmentKeys.contentColor, from));
937
982
  }, from);
938
983
  }
@@ -1904,6 +1949,38 @@ var ShellService = class {
1904
1949
  });
1905
1950
  }
1906
1951
  /**
1952
+ * Replaces the page with `url`, leaving the application.
1953
+ *
1954
+ * Not the router's `navigate`, which moves between screens *inside*
1955
+ * the app and keeps it running: this unloads the page, and whatever
1956
+ * the app held in memory goes with it. It is for the places a page
1957
+ * has to be handed to somebody else's server and wait to be sent
1958
+ * back — an OAuth sign-in that goes to `/api/auth/login`, is
1959
+ * redirected to an identity provider and returns with a session, or
1960
+ * a sign-out at `/api/auth/logout` that clears a cookie only the
1961
+ * server can clear. It is also where a sign-in falls back to when
1962
+ * `openPopup` reports the popup blocked.
1963
+ *
1964
+ * A request rather than a call because `window.location` belongs to
1965
+ * the main thread and a render worker cannot reach it. A relative
1966
+ * url resolves against the page, so `/api/auth/login` means the
1967
+ * app's own server. The shell refuses anything that is not http,
1968
+ * https or the page's own scheme — a `javascript:` url, above all,
1969
+ * which would run in the page rather than go anywhere — so a url
1970
+ * built from data cannot be turned into script.
1971
+ *
1972
+ * Nothing comes back, and there is nothing to wait for: if the
1973
+ * redirect happens, the code that would have read the answer is
1974
+ * gone. Leaving the page is not cancellable from here either, so a
1975
+ * component with unsaved work should settle it before asking.
1976
+ */
1977
+ redirect(url) {
1978
+ this.handler?.({
1979
+ type: "redirect",
1980
+ url
1981
+ });
1982
+ }
1983
+ /**
1907
1984
  * Whether the application's surface is filling the screen.
1908
1985
  *
1909
1986
  * Read rather than assumed: the person can leave fullscreen with
@@ -1945,8 +2022,8 @@ var ShellService = class {
1945
2022
  * The answer is a promise, the one place in the framework where a
1946
2023
  * shell request has a reply, because there is nothing useful an
1947
2024
  * application can do with a popup it cannot see the fate of. A sign-in
1948
- * flow that is blocked falls back to a full-page redirect, and it can
1949
- * only choose that if it is told.
2025
+ * flow that is blocked falls back to a full-page redirect — `redirect`,
2026
+ * below — and it can only choose that if it is told.
1950
2027
  *
1951
2028
  * Two browser rules shape the contract and both were measured in
1952
2029
  * Chrome before this existed:
@@ -2483,6 +2560,89 @@ function browserFilesHost(view) {
2483
2560
  };
2484
2561
  }
2485
2562
  //#endregion
2563
+ //#region src/app/shellRedirect.ts
2564
+ /**
2565
+ * The shell's half of `ShellService.redirect`: the page is replaced
2566
+ * with a url a component asked for.
2567
+ *
2568
+ * Both hosts answer the request — `WorkerApp` when it arrives as a
2569
+ * message, `GessoApp` when the runtime hands it over on the same
2570
+ * thread — and both go through here, so the one rule that matters is
2571
+ * written once.
2572
+ *
2573
+ * That rule is the scheme. `location.assign('javascript:…')` does not
2574
+ * go anywhere; it runs the script in the page, with everything the
2575
+ * page can reach. A component builds the url it redirects to, often
2576
+ * from data — a `returnTo` from the address bar, a link from the
2577
+ * server — and a shell that assigned whatever it was given would make
2578
+ * every such url a way to run script. So a url is resolved against
2579
+ * the page first, the way the browser would resolve it, and only an
2580
+ * http or https result, or one in the page's own scheme, is followed.
2581
+ * The page's own scheme is there for a page that is not served over
2582
+ * http at all — an Electrobun window loads from `views://` — where a
2583
+ * relative url resolves to that scheme and is no less the app's own.
2584
+ */
2585
+ /** Schemes a redirect may always go to, whatever the page was served from. */
2586
+ const WEB_SCHEMES = /* @__PURE__ */ new Set(["http:", "https:"]);
2587
+ /**
2588
+ * Schemes refused even when the page itself has one of them.
2589
+ *
2590
+ * A page served from `data:` or `blob:` is unusual but real — a
2591
+ * preview, a sandboxed frame — and "the page's own scheme" must not
2592
+ * then admit a `data:` url built from data, which is script by
2593
+ * another name.
2594
+ */
2595
+ const REFUSED_SCHEMES = /* @__PURE__ */ new Set([
2596
+ "javascript:",
2597
+ "data:",
2598
+ "blob:",
2599
+ "about:"
2600
+ ]);
2601
+ /**
2602
+ * The absolute url `url` names, resolved against `base` (the page's
2603
+ * address), or null when it is not one a redirect may follow.
2604
+ *
2605
+ * Null as well for a url that does not parse, or a base that does
2606
+ * not: a redirect nobody can resolve is refused rather than guessed.
2607
+ */
2608
+ function redirectTarget(url, base) {
2609
+ let page;
2610
+ let target;
2611
+ try {
2612
+ page = new URL(base);
2613
+ target = new URL(url, page);
2614
+ } catch {
2615
+ return null;
2616
+ }
2617
+ if (WEB_SCHEMES.has(target.protocol)) return target.href;
2618
+ if (target.protocol === page.protocol && !REFUSED_SCHEMES.has(target.protocol)) return target.href;
2619
+ return null;
2620
+ }
2621
+ /**
2622
+ * Follows a redirect, or refuses it, and says which.
2623
+ *
2624
+ * The url is checked before the handler is called as well as before
2625
+ * the default: a desktop host's `onRedirect` navigates a webview, and
2626
+ * a `javascript:` url is no safer there. The handler is given the
2627
+ * resolved, absolute url, so it never has to know what page the
2628
+ * request came from.
2629
+ *
2630
+ * A refusal is a warning on the console and nothing else. There is no
2631
+ * one to report it to — the request carries no answer — and throwing
2632
+ * on the main thread would take down a message handler over a url
2633
+ * that was simply ignored.
2634
+ */
2635
+ function redirectWith(handler, url, location) {
2636
+ const target = redirectTarget(url, location.href);
2637
+ if (target === null) {
2638
+ console.warn(`[gesso] ShellService.redirect refused ${JSON.stringify(url)}: only an http, https or relative url can replace the page.`);
2639
+ return false;
2640
+ }
2641
+ if (handler !== void 0) handler(target);
2642
+ else location.assign(target);
2643
+ return true;
2644
+ }
2645
+ //#endregion
2486
2646
  //#region src/app/EditingProxy.ts
2487
2647
  /** Composition-owned input types: the composition events carry these. */
2488
2648
  const COMPOSITION_INPUT_TYPES = /* @__PURE__ */ new Set([
@@ -4388,6 +4548,10 @@ var WorkerApp = class {
4388
4548
  openUrlWith(this.options.onOpenUrl, message.url);
4389
4549
  return;
4390
4550
  }
4551
+ if (message.type === "redirect") {
4552
+ redirectWith(this.options.onRedirect, message.url, window.location);
4553
+ return;
4554
+ }
4391
4555
  if (message.type === "fullscreen") {
4392
4556
  if (this.canvas !== void 0) setElementFullscreen(this.canvas, message.enter);
4393
4557
  return;
@@ -5580,8 +5744,8 @@ var RouterService = class {
5580
5744
  const settled = match?.url ?? normalize$1(url);
5581
5745
  if (options.replace === true) this.history?.replace(settled);
5582
5746
  else if (options.push) this.history?.push(settled);
5583
- this.url.value = settled;
5584
5747
  this.match.value = match;
5748
+ this.url.value = settled;
5585
5749
  }
5586
5750
  /** First declared route whose pattern describes this url wins. */
5587
5751
  resolve(url) {
@@ -7023,7 +7187,7 @@ var GessoRuntime = class {
7023
7187
  onChange: (node, listener) => this.environmentNotifier.add(node, listener)
7024
7188
  },
7025
7189
  layout: {
7026
- box: (node) => this.engine.recordFor(node) === void 0 ? null : this.engine.visibleBox(node),
7190
+ box: (node) => this.engine.recordFor(node) === void 0 ? null : this.engine.screenBox(node),
7027
7191
  flowBox: (node) => this.engine.recordFor(node) === void 0 ? null : this.engine.worldBox(node),
7028
7192
  scroll: (node) => {
7029
7193
  const record = this.engine.recordFor(node);
@@ -8101,6 +8265,7 @@ var GessoRuntime = class {
8101
8265
  const editing = new UiEditingController({
8102
8266
  recordFor: (node) => this.engine.recordFor(node),
8103
8267
  visibleBox: (node) => this.engine.visibleBox(node),
8268
+ screenBox: (node, part) => this.engine.screenBox(node, part),
8104
8269
  toLocal: (node, x, y) => hitTester.toLocal(node, x, y),
8105
8270
  measurer: this.textMeasurer,
8106
8271
  markDirty: (node, flags) => this.graph.markDirty(node, flags),
@@ -8110,6 +8275,7 @@ var GessoRuntime = class {
8110
8275
  const selection = new UiSelectionController({
8111
8276
  recordFor: (node) => this.engine.recordFor(node),
8112
8277
  visibleBox: (node) => this.engine.visibleBox(node),
8278
+ screenBox: (node) => this.engine.screenBox(node),
8113
8279
  measurer: this.textMeasurer,
8114
8280
  markDirty: (node, flags) => this.graph.markDirty(node, flags),
8115
8281
  root: () => this.layoutRoot(),
@@ -8158,7 +8324,7 @@ var GessoRuntime = class {
8158
8324
  selection,
8159
8325
  find,
8160
8326
  activation: { clickAt: (node) => {
8161
- const box = this.engine.visibleBox(node);
8327
+ const box = this.engine.screenBox(node);
8162
8328
  return {
8163
8329
  x: box.x + box.width / 2,
8164
8330
  y: box.y + box.height / 2
@@ -8300,7 +8466,7 @@ var GessoRuntime = class {
8300
8466
  return;
8301
8467
  }
8302
8468
  this.focusManager.focus(node);
8303
- const box = this.engine.visibleBox(node);
8469
+ const box = this.engine.screenBox(node);
8304
8470
  this.dispatcher.dispatch(new UiPointerEvent(UiEventType.Click, box.x + box.width / 2, box.y + box.height / 2, 1), node);
8305
8471
  }
8306
8472
  /**
@@ -8673,7 +8839,9 @@ var GessoRuntime = class {
8673
8839
  * goes down from the root carrying the scroll and sticky offsets that
8674
8840
  * `visibleBox` adds up per node, and passes over any subtree whose
8675
8841
  * bounds (the hit tester's, see `subtreeBoundsFor`) are off screen.
8676
- * A node it passes over is one this skipped before anyway.
8842
+ * A node it passes over is one this skipped before anyway. Below a
8843
+ * `transform` the walk asks `screenBox` instead, which carries the
8844
+ * box through it.
8677
8845
  */
8678
8846
  collectSemanticsBoxes() {
8679
8847
  const changed = [];
@@ -8694,14 +8862,25 @@ var GessoRuntime = class {
8694
8862
  const stack = [[
8695
8863
  root,
8696
8864
  0,
8697
- 0
8865
+ 0,
8866
+ false
8698
8867
  ]];
8699
8868
  while (stack.length > 0) {
8700
- const [node, dx, dy] = stack.pop();
8869
+ const [node, dx, dy, inherited] = stack.pop();
8701
8870
  const rec = this.engine.recordFor(node);
8702
8871
  let childX = dx;
8703
8872
  let childY = dy;
8704
- if (rec !== void 0) {
8873
+ const own = node.properties.get("transform");
8874
+ const transformed = inherited || typeof own === "object" && own !== null;
8875
+ if (rec !== void 0 && inherited) {
8876
+ if (this.semantics.has(node.id)) {
8877
+ const box = this.engine.screenBox(node);
8878
+ if (node === focused) {
8879
+ focusedSeen = true;
8880
+ offer(node.id, box);
8881
+ } else if (this.onScreen(box)) offer(node.id, box);
8882
+ }
8883
+ } else if (rec !== void 0) {
8705
8884
  const x = dx + rec.stickyOffsetX;
8706
8885
  const y = dy + rec.stickyOffsetY;
8707
8886
  if (!rec.boundsUnbounded && node !== focused && !this.onScreen({
@@ -8728,12 +8907,13 @@ var GessoRuntime = class {
8728
8907
  for (let child = node.lastChild; child !== null; child = child.previousSibling) stack.push([
8729
8908
  child,
8730
8909
  childX,
8731
- childY
8910
+ childY,
8911
+ transformed
8732
8912
  ]);
8733
8913
  }
8734
8914
  }
8735
8915
  if (focused !== null && focused !== void 0 && !focusedSeen && this.semantics.has(focused.id)) {
8736
- if (this.engine.recordFor(focused) !== void 0) offer(focused.id, this.engine.visibleBox(focused));
8916
+ if (this.engine.recordFor(focused) !== void 0) offer(focused.id, this.engine.screenBox(focused));
8737
8917
  }
8738
8918
  if (this.semanticsBoxes.size > this.semantics.size) {
8739
8919
  for (const id of this.semanticsBoxes.keys()) if (!this.semantics.has(id)) this.semanticsBoxes.delete(id);
@@ -8999,7 +9179,7 @@ var GessoRuntime = class {
8999
9179
  const read = (node) => {
9000
9180
  const record = this.engine.recordFor(node);
9001
9181
  return {
9002
- box: this.engine.visibleBox(node),
9182
+ box: this.engine.screenBox(node),
9003
9183
  scrollX: record?.scrollX ?? 0,
9004
9184
  scrollY: record?.scrollY ?? 0
9005
9185
  };
@@ -9845,6 +10025,10 @@ var GessoApp = class {
9845
10025
  this.runtime.settlePopup(request.id, opened);
9846
10026
  return;
9847
10027
  }
10028
+ if (request.type === "redirect") {
10029
+ if (view !== null) redirectWith(void 0, request.url, view.location);
10030
+ return;
10031
+ }
9848
10032
  view?.open(request.url, "_blank", "noopener,noreferrer");
9849
10033
  }
9850
10034
  observeResize() {
@@ -11153,6 +11337,10 @@ var RenderWorkerApp = class {
11153
11337
  type: "openUrl",
11154
11338
  url: request.url
11155
11339
  });
11340
+ else if (request.type === "redirect") this.host.postMessage({
11341
+ type: "redirect",
11342
+ url: request.url
11343
+ });
11156
11344
  else if (request.type === "fullscreen") this.host.postMessage({
11157
11345
  type: "fullscreen",
11158
11346
  enter: request.enter