@gjermundgaraba-designs/pixel-paper 0.19.0 → 0.20.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/DESIGN.md CHANGED
@@ -1073,7 +1073,7 @@ The printed button: a paper rectangle in a 2px ink frame that casts a 3px shadow
1073
1073
  **Card** → `div.card` (`.narrow`; `form.card` from `Card.formView`, named by its title (`aria-label`), so a form landmark; no role of its own, the Dialog around it is the dialog): a paper panel with the card shadow, `min(680px, 100%)` (440px narrow), entering in 3 steps over 0.14s from `scale(0.97)`. An inverted title bar `header.card-tb` (`6px 8px 6px 12px`, 13px uppercase 0.06em (`--tracking-head`), a close IconButton when it has `closeAttributes`, held by `Card.formView`'s `isBusy` while work the reader may not walk away from runs: `aria-disabled`, in the keyboard, its `closeAttributes` and so its handler dropped, faded to 0.6 on the bar, as a held button in a panel's title bar), a scrolling `div.card-body` (16px, 14px gap; a word longer than the card, a path or an id, breaks rather than widening it), and `div.card-actions` (`12px 16px`) over a 2px ink rule: `lead` first, then `span.card-acts` holding the actions at the right, which drop below the lead together when the bar wraps. Inside a Dialog, where the page's keys wait, the bar moves focus between its actions: `h` or ← to the one before, `l` or → to the one after, in the order they print, stopping at the ends, never pressing one (Enter, Space and Tab stay the browser's). The keys are bound by a Mount on the bar (`BindActionKeys`), which reads the real key event: only a key whose target is one of that bar's own buttons or links, unmodified, outside an IME and not already handled, and only when there is an action that way, moves focus and has its default prevented. A field, the title bar's close, a menu's items and a nested dialog's bar keep their keys. A disabled action is passed over; an `aria-disabled` one (held: busy, done or disabled) is still a stop and still ignores presses. **Card.formView** is the one way to make a form card: its `body` in one Fields group, then the action bar's `lead` (a form's own delete, before the push), cancel (`cancel`, closing through `closeAttributes`) and the primary default submit button (`submit`, its id `Card.submitId(id)`). One flag, `isBusy`, holds every way the form could change or leave while its work runs: the submit button prints busy and turns `type="button"`, the close glyph and cancel are held with their close attributes dropped, and the Fields group is natively disabled and `aria-busy`, so nothing changes under the send and nothing sends twice. In a Dialog, the state that sets `isBusy` is the `isCloseHeld` the parent passes the Dialog's update, so Escape and the veil are held with it, and a failed send frees both, the reason on the field (the specimen's rename refuses a name with a slash). `notices` print above the Fields group, outside the hold: what the page says while the form is open (a lost connection with its retry), whose controls stay live while it sends. `isSubmitDisabled` holds the submit button alone while there is nothing to send (a name unchanged), the fields and cancel live. The fields leave the keyboard as they are held, so the update moves focus to the submit button as it sends (`Focus.Focus({ id: Card.submitId(id) })`), and it still ignores `onSubmit` while busy, for an Enter that raced the render. A ConfirmDialog with `isDanger` still opens on cancel.
1074
1074
 
1075
1075
  - **ConfirmDialog** → a Submodel over Dialog (an `alertdialog` when `isDanger`, a `dialog` otherwise) holding a narrow Card whose body is an Outcome (warn, or error when `isDanger`), the Outcome's body describing the dialog. Its view inputs follow the system's words: `title` (the title bar), `question` (the Outcome's title), `body`, and `label` (the confirming button's word). Without `isDanger` the confirm is primary and default; with it the confirm is the red button and cancel is the default, where focus starts. `Confirmed` does not close it: work that takes a while stays in the dialog. `isBusy` holds the confirming button (its word, the working cursor, focus kept, a second press ignored) and cancel (`aria-disabled`, in the keyboard, no ring and no shadow while the busy commit keeps focus); the parent calls `ConfirmDialog.close` when the work lands, or keeps it open while it is not done. `isDisabled` holds it the same way without the working cursor, for an action that is blocked rather than running (writes not accepted yet). `toDetailView` prints the application's own content under the sentence, inside the Outcome's body and so in the dialog's description: what the work said back (a refused delete's reason as an error Notice) or a notice with its own retry. Cancel, Escape and the veil report `Cancelled`; the parent decides whether that also stops work already running, and ignores the work's result once cancelled. The parent passes the same state as `isCloseHeld` to `ConfirmDialog.update`, so cancel, Escape and a press on the veil change nothing while the work runs: the dialog stays open and reports no `Cancelled`, and the reader sees how it ends.
1076
- - **CommandPalette** (Submodel over Dialog and `@foldkit/ui`'s Input with a listbox of its own, since a combobox would not let the cursor land on held rows or Escape reach the Dialog) → a card at 18vh holding `div.palette-q` (a `span.palette-scope` and a combobox input), `div.palette-detail` when the app gives `toDetailView`, `div.palette-list[role="listbox"]` of `div.palette-group` headings and `div.palette-row[role="option"][aria-selected]` rows (control register; `isAuthored` titles, `.authored`, set in sans with a glyph and wait for a query), a `div.sr-only[role="status"]`, and `div.palette-more`. Once a query is typed the status says how many rows it finds ("3 results.", "Nothing matches."); it is empty as the card opens and on a fresh page, so it is never mounted holding its words (rule 12). Labels and hints wrap within their row, including long names and destination paths. One cursor: the inverted row, moved by the keys only. ↑ ↓ move it and stop at the ends, each key one row from where the last left it however fast they come (the field's own handler, in the view, reports `PressedArrowKey` with the rows its render showed, and update steps from the Model's cursor); Home and End stay the field's, moving the caret; Enter does what a click on its row does, decided in update from what the render showed each row to be (held, opening a page, a choice on a page): a command closes the palette first so what it opens takes focus, and reports `SelectedCommand`. An arrow or an Enter pressed before the list has caught up with the keys before it (straight after a letter, or before the arrows already waiting have moved the cursor) cannot name a row yet, so it waits for the next render: the row that render shows the cursor on becomes the cursor, and a waiting Enter acts on it as on any row. So arrows typed straight after a letter step through the new query's rows, and an Enter acts on the keys before it, never on letters typed after it. While the palette is open, ↑ ↓ and Enter pressed before its field has focus (straight after the key that opened it) are its own too: `CommandPalette.subscriptions(keyPrefix)` takes them on the window before the page's bindings see them, and they wait as above. A key an IME is composing stays the IME's. The pointer shades the row under it (Header Grey, as a list's row) and a click runs that row; it never moves the cursor and never scrolls. The cursor comes into view on every key that moves it, when the view or the query changes, and when its row leaves the list (it falls back to the first): the app, which changes the list, returns `CommandPalette.ScrollToCursor` with the palette's id; a row that only reflows is not followed. The cursor holds to its row by id while the app updates the list; `CommandPalette.maybeCursorId` reads the row it shows for a list of commands, so the app can compare it before and after its change. A row with `isDisabled` prints muted (`[aria-disabled="true"]`), its `reason` in the hint, and the cursor still lands on it but Enter and a click do nothing. **Pages:** a command with a `page` (a status, a date, a destination) ends in a caret and opens that page in place instead of closing: `div.palette-q` holds a Back IconButton, the scope chip (what the commands act on, kept on every page), the page's title (`span.palette-title`, pixel caps), then the page's own field (placeholder `find`, else "type to narrow"), focused. A long scope wraps inside its chip and the field takes its own line when too little is left beside it, so nothing leaves the card at 390px. The choices are found in like the commands. The choice in force now (`current`) carries a `current` tag and cannot be picked again. The cursor starts on the page's `start` (the next status; the first choice a typed query finds), else on `current`, else on the first choice, read while the reader has not moved it, so an application that keeps `start` steady across a change made elsewhere moves the tag, not the cursor. Back, or Backspace in an empty field, returns to the commands with their query and cursor as they were, also when the palette opened straight on a page (`CommandPalette.openAt`). A pick (`SelectedChoice`) holds the page until the parent answers: the row prints the working cursor, the list is `aria-busy`, and moving, picking, typing and Back wait. With `CommandPalette.pickSucceeded` the palette closes; with `pickFailed` the page stays with its query, focus and the cursor on that choice, and the app says why in `toDetailView`, which shows on the commands and on every page alike. Escape closes the whole palette, even while a pick is out, and reports `Closed`; an answer arriving after that, or after the page was left, changes nothing. A page whose command leaves the list gives way to the commands. One level: choices do not open pages.
1076
+ - **CommandPalette** (Submodel over Dialog and `@foldkit/ui`'s Input with a listbox of its own, since a combobox would not let the cursor land on held rows or Escape reach the Dialog) → a card at 18vh holding `div.palette-q` (a `span.palette-scope` and a combobox input), `div.palette-detail` when the app gives `toDetailView`, `div.palette-list[role="listbox"]` of `div.palette-group` headings and `div.palette-row[role="option"][aria-selected]` rows (control register; `isAuthored` titles, `.authored`, set in sans with a glyph and wait for a query), a `div.sr-only[role="status"]`, and `div.palette-more`. Once a query is typed the status says how many rows it finds ("3 results.", "Nothing matches."); it is empty as the card opens and on a fresh page, so it is never mounted holding its words (rule 12). Labels and hints wrap within their row, including long names and destination paths. One cursor: the inverted row, moved by the keys only. ↑ ↓ move it and stop at the ends, each key one row from where the last left it however fast they come (the field's own handler, in the view, reports `PressedArrowKey` with the rows its render showed, and update steps from the Model's cursor); Home and End stay the field's, moving the caret; Enter does what a click on its row does, decided in update from what the render showed each row to be (held, opening a page, a choice on a page): a command closes the palette first so what it opens takes focus, and reports `SelectedCommand`. An arrow or an Enter pressed before the list has caught up with the keys before it (straight after a letter, or before the arrows already waiting have moved the cursor) cannot name a row yet, so it waits for the next render: the row that render shows the cursor on becomes the cursor, and a waiting Enter acts on it as on any row. So arrows typed straight after a letter step through the new query's rows, and an Enter acts on the keys before it, never on letters typed after it. While the palette is open, ↑ ↓ and Enter pressed before its field has focus (straight after the key that opened it) are its own too: `CommandPalette.subscriptions(keyPrefix)` takes them on the window before the page's bindings see them, and they wait as above. A key an IME is composing stays the IME's. The pointer shades the row under it (Header Grey, as a list's row) and a click runs that row; it never moves the cursor and never scrolls. The cursor comes into view on every key that moves it, when the view or the query changes, and when its row leaves the list (it falls back to the first): the app, which changes the list, returns `CommandPalette.ScrollToCursor` with the palette's id; a row that only reflows is not followed. The cursor holds to its row by id while the app updates the list; `CommandPalette.maybeCursorId` reads the row it shows for a list of commands, so the app can compare it before and after its change. A row with `isDisabled` prints muted (`[aria-disabled="true"]`), its `reason` in the hint, and the cursor still lands on it but Enter and a click do nothing. **Pages:** a command with a `page` (a status, a date, a destination) ends in a caret and opens that page in place instead of closing: `div.palette-q` holds a Back IconButton, the scope chip (what the commands act on, kept on every page), the page's title (`span.palette-title`, pixel caps), then the page's own field (placeholder `find`, else "type to narrow"), focused. A long scope wraps inside its chip and the field takes its own line when too little is left beside it, so nothing leaves the card at 390px. The choices are found in like the commands. The choice in force now (`current`) carries a `current` tag and cannot be picked again. A page can be a tree: a choice's `level` (1 at the roots) puts it under the nearest choice before it one level up and indents it a step a level (`--palette-level`, at most a third of the row); a query then keeps each choice it finds with the choices it sits under, muted (`.palette-row.context`, "(context)" for screen readers) and still pickable, so a found choice shows where it is; the status counts only the choices found. The indent is not read out, so a choice whose place matters names its path in its hint. The cursor starts on the page's `start` (the next status; the first choice a typed query finds), else on `current`, else on the first choice, each only among the choices the query finds, read while the reader has not moved it, so an application that keeps `start` steady across a change made elsewhere moves the tag, not the cursor. Back, or Backspace in an empty field, returns to the commands with their query and cursor as they were, also when the palette opened straight on a page (`CommandPalette.openAt`). A pick (`SelectedChoice`) holds the page until the parent answers: the row prints the working cursor, the list is `aria-busy`, and moving, picking, typing and Back wait. With `CommandPalette.pickSucceeded` the palette closes; with `pickFailed` the page stays with its query, focus and the cursor on that choice, and the app says why in `toDetailView`, which shows on the commands and on every page alike. Escape closes the whole palette, even while a pick is out, and reports `Closed`; an answer arriving after that, or after the page was left, changes nothing. A page whose command leaves the list gives way to the commands. One level: choices do not open pages.
1077
1077
  - **Keymap** → `div.keymap > div > h3 + div.keymap-row > kbd + span`: keys in the 11px pixel face under 10px muted group heads, for a narrow Card.
1078
1078
 
1079
1079
  ### Keys
@@ -424,10 +424,11 @@ export type ViewInputs = Readonly<{
424
424
  toLiveRegionView?: (() => Html) | undefined;
425
425
  }>;
426
426
  /** The row the cursor is on as the view shows it with `commands`: the row the reader moved it
427
- * to while that row is listed, else the page's `start` or `current`, else the first row; none
428
- * when nothing is listed. Compare it before and after the caller's commands change to know
429
- * when the cursor fell to another row (its row left) and should be scrolled into view with
430
- * `ScrollToCursor`; a cursor whose row is still listed keeps it, and the list its scroll. */
427
+ * to while that row is listed, else the page's `start` or `current` while the query finds it,
428
+ * else the first row the query finds; none when nothing is listed. Compare it before and after
429
+ * the caller's commands change to know when the cursor fell to another row (its row left) and
430
+ * should be scrolled into view with `ScrollToCursor`; a cursor whose row is still listed keeps
431
+ * it, and the list its scroll. */
431
432
  export declare const maybeCursorId: (model: Model, commands: ReadonlyArray<Command>) => Option.Option<string>;
432
433
  /**
433
434
  * Type to find a command or a title; one inverted cursor row, moved by ↑ and ↓ only
@@ -1 +1 @@
1
- {"version":3,"file":"commandPalette.d.ts","sourceRoot":"","sources":["../../../src/commandPalette/commandPalette.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"commandPalette.d.ts","sourceRoot":"","sources":["../../../src/commandPalette/commandPalette.ts"],"names":[],"mappings":"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"}
@@ -1,6 +1,6 @@
1
1
  import { brandViewResult as __foldkitBrandViewResult } from 'foldkit/brand'
2
2
  import { Input as UiInput } from "@foldkit/ui";
3
- import { Array, Effect, Match, Option, Schema, Stream, String, pipe } from "effect";
3
+ import { Array, Effect, HashSet, Match, Option, Schema, Stream, String, pipe } from "effect";
4
4
  import * as FoldkitCommand from "foldkit/command";
5
5
  import * as Dom from "foldkit/dom";
6
6
  import { defineMessageUnion } from "foldkit/message";
@@ -19,7 +19,7 @@ import { LOCALE } from "../locale/locale.js";
19
19
  import { slotView } from "../slot/slot.js";
20
20
  import { prefixKeys } from "../subscription/prefixKeys.js";
21
21
  import { BindPaletteKeys, FIELD_ATTRIBUTE, OPTION_SELECTOR, PAGE_ATTRIBUTE, earlyKeys, shownPageOf, } from "./keys.js";
22
- import { findChoices, findCommands } from "./match.js";
22
+ import { findCommands, pageChoices } from "./match.js";
23
23
  import { Message } from "./message.js";
24
24
  // MODEL
25
25
  /** An open page: its command, its own query, and its cursor once the reader moved it. */
@@ -211,7 +211,7 @@ const maybeCurrentCursor = (model) => __foldkitBrandViewResult((Option.match(mod
211
211
  onSome: (page) => __foldkitBrandViewResult((page.maybeCursor), "@gjermundgaraba-designs/pixel-paper/src/commandPalette/commandPalette.js#onSome~5"),
212
212
  })), "@gjermundgaraba-designs/pixel-paper/src/commandPalette/commandPalette.js#maybeCurrentCursor");
213
213
  const ARROW_STEP = { ArrowUp: -1, ArrowDown: 1 };
214
- const isListedIn = (rows, rowId) => __foldkitBrandViewResult((Array.some(rows, (row) => __foldkitBrandViewResult((row.id === rowId), "@gjermundgaraba-designs/pixel-paper/src/commandPalette/commandPalette.js#anonymous~8"))), "@gjermundgaraba-designs/pixel-paper/src/commandPalette/commandPalette.js#isListedIn");
214
+ const isListed = (rows, rowId) => __foldkitBrandViewResult((Array.some(rows, (row) => __foldkitBrandViewResult((row.id === rowId), "@gjermundgaraba-designs/pixel-paper/src/commandPalette/commandPalette.js#anonymous~8"))), "@gjermundgaraba-designs/pixel-paper/src/commandPalette/commandPalette.js#isListed");
215
215
  // NOTE: where the cursor is among the rows a key carries (see `Model`). With no arrows waiting
216
216
  // it is the Model's cursor while that row is listed, else the shown one, so each key acts from
217
217
  // where the last left it, also when several come before the next render; with arrows waiting,
@@ -219,11 +219,11 @@ const isListedIn = (rows, rowId) => __foldkitBrandViewResult((Array.some(rows, (
219
219
  // key waits for the next render.
220
220
  const cursorAsShown = (model, shown) => {
221
221
  const { rows, maybeShownCursor, shownQuery, shownShift } = shown;
222
- const isListed = shownQuery === query(model);
223
- if (isListed && model.cursorShift === 0) {
224
- return __foldkitBrandViewResult((Option.some(Option.orElse(Option.filter(maybeCurrentCursor(model), (rowId) => __foldkitBrandViewResult((isListedIn(rows, rowId)), "@gjermundgaraba-designs/pixel-paper/src/commandPalette/commandPalette.js#anonymous~9")), () => __foldkitBrandViewResult((maybeShownCursor), "@gjermundgaraba-designs/pixel-paper/src/commandPalette/commandPalette.js#anonymous~10")))), "@gjermundgaraba-designs/pixel-paper/src/commandPalette/commandPalette.js#cursorAsShown");
222
+ const isTheList = shownQuery === query(model);
223
+ if (isTheList && model.cursorShift === 0) {
224
+ return __foldkitBrandViewResult((Option.some(Option.orElse(Option.filter(maybeCurrentCursor(model), (rowId) => __foldkitBrandViewResult((isListed(rows, rowId)), "@gjermundgaraba-designs/pixel-paper/src/commandPalette/commandPalette.js#anonymous~9")), () => __foldkitBrandViewResult((maybeShownCursor), "@gjermundgaraba-designs/pixel-paper/src/commandPalette/commandPalette.js#anonymous~10")))), "@gjermundgaraba-designs/pixel-paper/src/commandPalette/commandPalette.js#cursorAsShown");
225
225
  }
226
- else if (isListed && shownShift === model.cursorShift) {
226
+ else if (isTheList && shownShift === model.cursorShift) {
227
227
  return __foldkitBrandViewResult((Option.some(maybeShownCursor)), "@gjermundgaraba-designs/pixel-paper/src/commandPalette/commandPalette.js#cursorAsShown");
228
228
  }
229
229
  else {
@@ -347,10 +347,11 @@ const earlyKeySubscriptions = Subscription.make()((entry) => (__foldkitBrandView
347
347
  * other record on the page uses: `subscriptions("palette")` is keyed `paletteEarlyKeys`. */
348
348
  export const subscriptions = (keyPrefix) => __foldkitBrandViewResult((prefixKeys(keyPrefix, earlyKeySubscriptions)), "@gjermundgaraba-designs/pixel-paper/src/commandPalette/commandPalette.js#subscriptions");
349
349
  const pageOf = (commands, state) => __foldkitBrandViewResult((pipe(Array.findFirst(commands, (command) => __foldkitBrandViewResult((command.id === state.commandId), "@gjermundgaraba-designs/pixel-paper/src/commandPalette/commandPalette.js#anonymous~23")), Option.flatMap((command) => __foldkitBrandViewResult((Option.fromNullishOr(command.page)), "@gjermundgaraba-designs/pixel-paper/src/commandPalette/commandPalette.js#anonymous~24")), Option.map((page) => (__foldkitBrandViewResult(({ commandId: state.commandId, page, state }), "@gjermundgaraba-designs/pixel-paper/src/commandPalette/commandPalette.js#anonymous~25"))))), "@gjermundgaraba-designs/pixel-paper/src/commandPalette/commandPalette.js#pageOf");
350
- const cursorOn = (rows, candidates) => __foldkitBrandViewResult((Option.orElse(Array.findFirst(Array.getSomes(candidates), (cursorId) => __foldkitBrandViewResult((Array.some(rows, (row) => __foldkitBrandViewResult((row.id === cursorId), "@gjermundgaraba-designs/pixel-paper/src/commandPalette/commandPalette.js#anonymous~27"))), "@gjermundgaraba-designs/pixel-paper/src/commandPalette/commandPalette.js#anonymous~26")), () => __foldkitBrandViewResult((Option.map(Array.head(rows), (row) => __foldkitBrandViewResult((row.id), "@gjermundgaraba-designs/pixel-paper/src/commandPalette/commandPalette.js#anonymous~29"))), "@gjermundgaraba-designs/pixel-paper/src/commandPalette/commandPalette.js#anonymous~28"))), "@gjermundgaraba-designs/pixel-paper/src/commandPalette/commandPalette.js#cursorOn");
350
+ const firstListed = (rows, candidates) => __foldkitBrandViewResult((Array.findFirst(Array.getSomes(candidates), (cursorId) => __foldkitBrandViewResult((isListed(rows, cursorId)), "@gjermundgaraba-designs/pixel-paper/src/commandPalette/commandPalette.js#anonymous~26"))), "@gjermundgaraba-designs/pixel-paper/src/commandPalette/commandPalette.js#firstListed");
351
+ const firstId = (rows) => __foldkitBrandViewResult((Option.map(Array.head(rows), (row) => __foldkitBrandViewResult((row.id), "@gjermundgaraba-designs/pixel-paper/src/commandPalette/commandPalette.js#anonymous~27"))), "@gjermundgaraba-designs/pixel-paper/src/commandPalette/commandPalette.js#firstId");
351
352
  const shifted = (shown, shift) => (__foldkitBrandViewResult(({
352
353
  ...shown,
353
- maybeCursorId: Option.map(rowAfter(shown.rows, shown.maybeCursorId, shift), (row) => __foldkitBrandViewResult((row.id), "@gjermundgaraba-designs/pixel-paper/src/commandPalette/commandPalette.js#anonymous~30")),
354
+ maybeCursorId: Option.map(rowAfter(shown.rows, shown.maybeCursorId, shift), (row) => __foldkitBrandViewResult((row.id), "@gjermundgaraba-designs/pixel-paper/src/commandPalette/commandPalette.js#anonymous~28")),
354
355
  }), "@gjermundgaraba-designs/pixel-paper/src/commandPalette/commandPalette.js#shifted"));
355
356
  // NOTE: arrows waiting for the view count from the cursor's row. When that row has left the
356
357
  // list (the application changed it in between), they are dropped rather than counted from
@@ -364,38 +365,47 @@ const shownOf = (model, commands) => {
364
365
  return __foldkitBrandViewResult((isAnchored ? shifted(unshifted, model.cursorShift) : unshifted), "@gjermundgaraba-designs/pixel-paper/src/commandPalette/commandPalette.js#shownOf");
365
366
  };
366
367
  // What the view shows of the Model with `commands`: the open page while its command is
367
- // listed, the rows its query finds, and the cursor's row: the row the reader moved it to
368
- // while that row is listed, else the page's `start`, else its `current`, else the first row;
369
- // none when nothing is listed.
368
+ // listed, the rows its query finds (on a tree page, with the rows they sit under), and the
369
+ // cursor's row: the row the reader moved it to while that row is listed, else the page's
370
+ // `start`, else its `current`, else the first row, these three only among the rows the query
371
+ // finds; none when nothing is listed.
370
372
  const unshiftedOf = (model, commands) => {
371
- const maybePage = Option.flatMap(model.maybePage, (state) => __foldkitBrandViewResult((pageOf(commands, state)), "@gjermundgaraba-designs/pixel-paper/src/commandPalette/commandPalette.js#anonymous~31"));
373
+ const maybePage = Option.flatMap(model.maybePage, (state) => __foldkitBrandViewResult((pageOf(commands, state)), "@gjermundgaraba-designs/pixel-paper/src/commandPalette/commandPalette.js#anonymous~29"));
372
374
  return __foldkitBrandViewResult((Option.match(maybePage, {
373
375
  onNone: () => {
374
376
  const rows = findCommands(commands, model.query);
375
- return __foldkitBrandViewResult(({ maybePage, rows, maybeCursorId: cursorOn(rows, [model.maybeCursor]) }), "@gjermundgaraba-designs/pixel-paper/src/commandPalette/commandPalette.js#onNone~14");
377
+ return __foldkitBrandViewResult(({
378
+ maybePage,
379
+ rows,
380
+ contextIds: HashSet.empty(),
381
+ maybeCursorId: pipe(Option.filter(model.maybeCursor, (rowId) => __foldkitBrandViewResult((isListed(rows, rowId)), "@gjermundgaraba-designs/pixel-paper/src/commandPalette/commandPalette.js#anonymous~30")), Option.orElse(() => __foldkitBrandViewResult((firstId(rows)), "@gjermundgaraba-designs/pixel-paper/src/commandPalette/commandPalette.js#anonymous~31"))),
382
+ }), "@gjermundgaraba-designs/pixel-paper/src/commandPalette/commandPalette.js#onNone~14");
376
383
  },
377
384
  onSome: ({ page, state }) => {
378
- const rows = findChoices(page, state.query);
385
+ const { rows, found, contextIds } = pageChoices(page, state.query);
379
386
  return __foldkitBrandViewResult(({
380
387
  maybePage,
381
388
  rows,
382
- maybeCursorId: cursorOn(rows, [
383
- state.maybeCursor,
389
+ contextIds,
390
+ maybeCursorId: pipe(Option.filter(state.maybeCursor, (rowId) => __foldkitBrandViewResult((isListed(rows, rowId)), "@gjermundgaraba-designs/pixel-paper/src/commandPalette/commandPalette.js#anonymous~32")), Option.orElse(() => __foldkitBrandViewResult((firstListed(found, [
384
391
  Option.fromNullishOr(page.start),
385
392
  Option.fromNullishOr(page.current),
386
- ]),
393
+ ])), "@gjermundgaraba-designs/pixel-paper/src/commandPalette/commandPalette.js#anonymous~33")), Option.orElse(() => __foldkitBrandViewResult((firstId(found)), "@gjermundgaraba-designs/pixel-paper/src/commandPalette/commandPalette.js#anonymous~34"))),
387
394
  }), "@gjermundgaraba-designs/pixel-paper/src/commandPalette/commandPalette.js#onSome~13");
388
395
  },
389
396
  })), "@gjermundgaraba-designs/pixel-paper/src/commandPalette/commandPalette.js#unshiftedOf");
390
397
  };
391
398
  /** The row the cursor is on as the view shows it with `commands`: the row the reader moved it
392
- * to while that row is listed, else the page's `start` or `current`, else the first row; none
393
- * when nothing is listed. Compare it before and after the caller's commands change to know
394
- * when the cursor fell to another row (its row left) and should be scrolled into view with
395
- * `ScrollToCursor`; a cursor whose row is still listed keeps it, and the list its scroll. */
399
+ * to while that row is listed, else the page's `start` or `current` while the query finds it,
400
+ * else the first row the query finds; none when nothing is listed. Compare it before and after
401
+ * the caller's commands change to know when the cursor fell to another row (its row left) and
402
+ * should be scrolled into view with `ScrollToCursor`; a cursor whose row is still listed keeps
403
+ * it, and the list its scroll. */
396
404
  export const maybeCursorId = (model, commands) => __foldkitBrandViewResult((shownOf(model, commands).maybeCursorId), "@gjermundgaraba-designs/pixel-paper/src/commandPalette/commandPalette.js#maybeCursorId");
397
405
  const isCommand = (row) => __foldkitBrandViewResult(("group" in row), "@gjermundgaraba-designs/pixel-paper/src/commandPalette/commandPalette.js#isCommand");
398
- const isCurrentRow = (shown, row) => __foldkitBrandViewResult((Option.exists(shown.maybePage, ({ page }) => __foldkitBrandViewResult((page.current === row.id), "@gjermundgaraba-designs/pixel-paper/src/commandPalette/commandPalette.js#anonymous~32"))), "@gjermundgaraba-designs/pixel-paper/src/commandPalette/commandPalette.js#isCurrentRow");
406
+ const isCurrentRow = (shown, row) => __foldkitBrandViewResult((Option.exists(shown.maybePage, ({ page }) => __foldkitBrandViewResult((page.current === row.id), "@gjermundgaraba-designs/pixel-paper/src/commandPalette/commandPalette.js#anonymous~35"))), "@gjermundgaraba-designs/pixel-paper/src/commandPalette/commandPalette.js#isCurrentRow");
407
+ const isContextRow = (shown, row) => __foldkitBrandViewResult((HashSet.has(shown.contextIds, row.id)), "@gjermundgaraba-designs/pixel-paper/src/commandPalette/commandPalette.js#isContextRow");
408
+ const levelOf = (row) => __foldkitBrandViewResult(("level" in row ? Option.fromNullishOr(row.level) : Option.none()), "@gjermundgaraba-designs/pixel-paper/src/commandPalette/commandPalette.js#levelOf");
399
409
  // What a row is as this render shows it, which Enter acts on as a click would.
400
410
  const shownRowFor = (shown, row) => (__foldkitBrandViewResult(({
401
411
  id: row.id,
@@ -406,14 +416,14 @@ const shownRowFor = (shown, row) => (__foldkitBrandViewResult(({
406
416
  // shows them (also for a page whose command left).
407
417
  const shownQueryOf = (model, shown) => __foldkitBrandViewResult((Option.match(shown.maybePage, { onNone: () => __foldkitBrandViewResult((model.query), "@gjermundgaraba-designs/pixel-paper/src/commandPalette/commandPalette.js#onNone~15"), onSome: ({ state }) => __foldkitBrandViewResult((state.query), "@gjermundgaraba-designs/pixel-paper/src/commandPalette/commandPalette.js#onSome~14") })), "@gjermundgaraba-designs/pixel-paper/src/commandPalette/commandPalette.js#shownQueryOf");
408
418
  // The command whose page the view shows.
409
- const shownPageIdOf = (shown) => __foldkitBrandViewResult((Option.map(shown.maybePage, ({ commandId }) => __foldkitBrandViewResult((commandId), "@gjermundgaraba-designs/pixel-paper/src/commandPalette/commandPalette.js#anonymous~33"))), "@gjermundgaraba-designs/pixel-paper/src/commandPalette/commandPalette.js#shownPageIdOf");
419
+ const shownPageIdOf = (shown) => __foldkitBrandViewResult((Option.map(shown.maybePage, ({ commandId }) => __foldkitBrandViewResult((commandId), "@gjermundgaraba-designs/pixel-paper/src/commandPalette/commandPalette.js#anonymous~36"))), "@gjermundgaraba-designs/pixel-paper/src/commandPalette/commandPalette.js#shownPageIdOf");
410
420
  // NOTE: the field's keys that act on the rows are taken here, where the rows are known, as a
411
421
  // Mount on the document cannot know them: ↑, ↓ and Enter carry what this render shows (see
412
422
  // `Model`), and each is taken from the field (its default prevented). A modified key and every
413
423
  // key while a pick is out are left to the field.
414
424
  const fieldKeyDown = (model, shown) => __foldkitBrandViewResult(((key, modifiers) => {
415
425
  const list = {
416
- rows: Array.map(shown.rows, (row) => __foldkitBrandViewResult((shownRowFor(shown, row)), "@gjermundgaraba-designs/pixel-paper/src/commandPalette/commandPalette.js#anonymous~35")),
426
+ rows: Array.map(shown.rows, (row) => __foldkitBrandViewResult((shownRowFor(shown, row)), "@gjermundgaraba-designs/pixel-paper/src/commandPalette/commandPalette.js#anonymous~38")),
417
427
  maybeShownCursor: shown.maybeCursorId,
418
428
  maybeShownPage: shownPageIdOf(shown),
419
429
  shownQuery: shownQueryOf(model, shown),
@@ -421,7 +431,7 @@ const fieldKeyDown = (model, shown) => __foldkitBrandViewResult(((key, modifiers
421
431
  };
422
432
  return __foldkitBrandViewResult((isPicking(model) || !isPlain(modifiers)
423
433
  ? Option.none()
424
- : Match.value(key).pipe(Match.withReturnType(), Match.whenOr("ArrowDown", "ArrowUp", (arrow) => __foldkitBrandViewResult((Option.some(Message.PressedArrowKey({ key: arrow, ...list }))), "@gjermundgaraba-designs/pixel-paper/src/commandPalette/commandPalette.js#anonymous~36")), Match.when("Enter", () => __foldkitBrandViewResult((Option.some(Message.PressedEnter(list))), "@gjermundgaraba-designs/pixel-paper/src/commandPalette/commandPalette.js#anonymous~37")), Match.orElse(() => __foldkitBrandViewResult((Option.none()), "@gjermundgaraba-designs/pixel-paper/src/commandPalette/commandPalette.js#anonymous~38")))), "@gjermundgaraba-designs/pixel-paper/src/commandPalette/commandPalette.js#anonymous~34");
434
+ : Match.value(key).pipe(Match.withReturnType(), Match.whenOr("ArrowDown", "ArrowUp", (arrow) => __foldkitBrandViewResult((Option.some(Message.PressedArrowKey({ key: arrow, ...list }))), "@gjermundgaraba-designs/pixel-paper/src/commandPalette/commandPalette.js#anonymous~39")), Match.when("Enter", () => __foldkitBrandViewResult((Option.some(Message.PressedEnter(list))), "@gjermundgaraba-designs/pixel-paper/src/commandPalette/commandPalette.js#anonymous~40")), Match.orElse(() => __foldkitBrandViewResult((Option.none()), "@gjermundgaraba-designs/pixel-paper/src/commandPalette/commandPalette.js#anonymous~41")))), "@gjermundgaraba-designs/pixel-paper/src/commandPalette/commandPalette.js#anonymous~37");
425
435
  }), "@gjermundgaraba-designs/pixel-paper/src/commandPalette/commandPalette.js#fieldKeyDown");
426
436
  const optionId = (paletteId, index) => __foldkitBrandViewResult((`${paletteId}-option-${index}`), "@gjermundgaraba-designs/pixel-paper/src/commandPalette/commandPalette.js#optionId");
427
437
  const NOTHING_FOUND = "nothing matches · try fewer letters";
@@ -436,7 +446,10 @@ const rowPartsView = (model, shown, row, h) => {
436
446
  ...(row.glyph === undefined
437
447
  ? []
438
448
  : [h.span([h.Class("glyph")], [Glyph.view({ name: row.glyph }, h)])]),
439
- h.span([h.Class("palette-label")], [row.label]),
449
+ h.span([h.Class("palette-label")], [
450
+ row.label,
451
+ ...(isContextRow(shown, row) ? [h.span([h.Class("sr-only")], [" (context)"])] : []),
452
+ ]),
440
453
  ...(Option.contains(model.maybePicking, row.id)
441
454
  ? [h.span([h.Class("cursor"), h.AriaHidden(true)])]
442
455
  : []),
@@ -454,41 +467,49 @@ const rowView = (model, shown, row, index, h) => {
454
467
  ...(shownRow.isHeld ? [h.AriaDisabled(true)] : []),
455
468
  h.DataAttribute(ROW_ATTRIBUTE, row.id),
456
469
  ...(shownRow.hasPage ? [h.DataAttribute(OPENS_PAGE_ATTRIBUTE, "")] : []),
457
- h.Class(row.isAuthored === true ? "palette-row authored" : "palette-row"),
470
+ h.Class(Array.join([
471
+ "palette-row",
472
+ ...(row.isAuthored === true ? ["authored"] : []),
473
+ ...(isContextRow(shown, row) ? ["context"] : []),
474
+ ], " ")),
475
+ ...Option.match(levelOf(row), {
476
+ onNone: () => __foldkitBrandViewResult(([]), "@gjermundgaraba-designs/pixel-paper/src/commandPalette/commandPalette.js#onNone~16"),
477
+ onSome: (level) => __foldkitBrandViewResult(([h.Style({ "--palette-level": `${level}` })]), "@gjermundgaraba-designs/pixel-paper/src/commandPalette/commandPalette.js#onSome~15"),
478
+ }),
458
479
  ...(shownRow.isHeld || isPicking(model)
459
480
  ? []
460
481
  : [h.OnClick(Message.ClickedRow({ row: shownRow, maybeShownPage: shownPageIdOf(shown) }))]),
461
482
  ], rowPartsView(model, shown, row, h))), "@gjermundgaraba-designs/pixel-paper/src/commandPalette/commandPalette.js#rowView");
462
483
  };
463
484
  const groupedRows = (model, shown, h) => {
464
- const indexed = Array.map(shown.rows, (row, index) => (__foldkitBrandViewResult(({ row, index }), "@gjermundgaraba-designs/pixel-paper/src/commandPalette/commandPalette.js#anonymous~39")));
485
+ const indexed = Array.map(shown.rows, (row, index) => (__foldkitBrandViewResult(({ row, index }), "@gjermundgaraba-designs/pixel-paper/src/commandPalette/commandPalette.js#anonymous~42")));
465
486
  return __foldkitBrandViewResult((Option.match(shown.maybePage, {
466
- onSome: () => __foldkitBrandViewResult((Array.map(indexed, ({ row, index }) => __foldkitBrandViewResult((rowView(model, shown, row, index, h)), "@gjermundgaraba-designs/pixel-paper/src/commandPalette/commandPalette.js#anonymous~40"))), "@gjermundgaraba-designs/pixel-paper/src/commandPalette/commandPalette.js#onSome~15"),
487
+ onSome: () => __foldkitBrandViewResult((Array.map(indexed, ({ row, index }) => __foldkitBrandViewResult((rowView(model, shown, row, index, h)), "@gjermundgaraba-designs/pixel-paper/src/commandPalette/commandPalette.js#anonymous~43"))), "@gjermundgaraba-designs/pixel-paper/src/commandPalette/commandPalette.js#onSome~16"),
467
488
  onNone: () => {
468
- const groups = Array.dedupe(Array.map(shown.rows, (row) => (__foldkitBrandViewResult((isCommand(row) ? row.group : ""), "@gjermundgaraba-designs/pixel-paper/src/commandPalette/commandPalette.js#anonymous~41"))));
489
+ const groups = Array.dedupe(Array.map(shown.rows, (row) => (__foldkitBrandViewResult((isCommand(row) ? row.group : ""), "@gjermundgaraba-designs/pixel-paper/src/commandPalette/commandPalette.js#anonymous~44"))));
469
490
  return __foldkitBrandViewResult((Array.map(groups, (group, groupIndex) => {
470
491
  const headingId = `${id(model)}-group-${groupIndex}`;
471
492
  return __foldkitBrandViewResult((h.keyed("div")(`group-${group}`, [h.Role("group"), h.AriaLabelledBy(headingId)], [
472
493
  h.div([h.Id(headingId), h.Role("presentation"), h.Class("palette-group")], [group]),
473
- ...Array.map(Array.filter(indexed, ({ row }) => __foldkitBrandViewResult((isCommand(row) && row.group === group), "@gjermundgaraba-designs/pixel-paper/src/commandPalette/commandPalette.js#anonymous~43")), ({ row, index }) => __foldkitBrandViewResult((rowView(model, shown, row, index, h)), "@gjermundgaraba-designs/pixel-paper/src/commandPalette/commandPalette.js#anonymous~44")),
474
- ])), "@gjermundgaraba-designs/pixel-paper/src/commandPalette/commandPalette.js#anonymous~42");
475
- })), "@gjermundgaraba-designs/pixel-paper/src/commandPalette/commandPalette.js#onNone~16");
494
+ ...Array.map(Array.filter(indexed, ({ row }) => __foldkitBrandViewResult((isCommand(row) && row.group === group), "@gjermundgaraba-designs/pixel-paper/src/commandPalette/commandPalette.js#anonymous~46")), ({ row, index }) => __foldkitBrandViewResult((rowView(model, shown, row, index, h)), "@gjermundgaraba-designs/pixel-paper/src/commandPalette/commandPalette.js#anonymous~47")),
495
+ ])), "@gjermundgaraba-designs/pixel-paper/src/commandPalette/commandPalette.js#anonymous~45");
496
+ })), "@gjermundgaraba-designs/pixel-paper/src/commandPalette/commandPalette.js#onNone~17");
476
497
  },
477
498
  })), "@gjermundgaraba-designs/pixel-paper/src/commandPalette/commandPalette.js#groupedRows");
478
499
  };
479
500
  const comboboxAttributes = (model, shown, noun, h) => {
480
501
  const paletteId = id(model);
481
- const maybeCursorIndex = Option.flatMap(shown.maybeCursorId, (cursorId) => __foldkitBrandViewResult((Array.findFirstIndex(shown.rows, (row) => __foldkitBrandViewResult((row.id === cursorId), "@gjermundgaraba-designs/pixel-paper/src/commandPalette/commandPalette.js#anonymous~46"))), "@gjermundgaraba-designs/pixel-paper/src/commandPalette/commandPalette.js#anonymous~45"));
502
+ const maybeCursorIndex = Option.flatMap(shown.maybeCursorId, (cursorId) => __foldkitBrandViewResult((Array.findFirstIndex(shown.rows, (row) => __foldkitBrandViewResult((row.id === cursorId), "@gjermundgaraba-designs/pixel-paper/src/commandPalette/commandPalette.js#anonymous~49"))), "@gjermundgaraba-designs/pixel-paper/src/commandPalette/commandPalette.js#anonymous~48"));
482
503
  return __foldkitBrandViewResult(([
483
504
  h.OnKeyDownPreventDefault(fieldKeyDown(model, shown)),
484
505
  h.Role("combobox"),
485
506
  h.AriaExpanded(true),
486
507
  h.AriaControls(listIdOf(paletteId)),
487
508
  h.AriaAutocomplete("list"),
488
- ...Option.toArray(Option.map(maybeCursorIndex, (index) => __foldkitBrandViewResult((h.AriaActiveDescendant(optionId(paletteId, index))), "@gjermundgaraba-designs/pixel-paper/src/commandPalette/commandPalette.js#anonymous~47"))),
509
+ ...Option.toArray(Option.map(maybeCursorIndex, (index) => __foldkitBrandViewResult((h.AriaActiveDescendant(optionId(paletteId, index))), "@gjermundgaraba-designs/pixel-paper/src/commandPalette/commandPalette.js#anonymous~50"))),
489
510
  h.AriaLabel(Option.match(shown.maybePage, {
490
- onNone: () => __foldkitBrandViewResult((`Command or ${noun}`), "@gjermundgaraba-designs/pixel-paper/src/commandPalette/commandPalette.js#onNone~17"),
491
- onSome: ({ page }) => __foldkitBrandViewResult((page.title), "@gjermundgaraba-designs/pixel-paper/src/commandPalette/commandPalette.js#onSome~16"),
511
+ onNone: () => __foldkitBrandViewResult((`Command or ${noun}`), "@gjermundgaraba-designs/pixel-paper/src/commandPalette/commandPalette.js#onNone~18"),
512
+ onSome: ({ page }) => __foldkitBrandViewResult((page.title), "@gjermundgaraba-designs/pixel-paper/src/commandPalette/commandPalette.js#onSome~17"),
492
513
  })),
493
514
  h.Autocomplete("off"),
494
515
  h.DataAttribute(FIELD_ATTRIBUTE, ""),
@@ -502,8 +523,8 @@ const fieldView = (model, shown, noun, render, h) => {
502
523
  onInput: (value) => __foldkitBrandViewResult((Message.UpdatedQuery({ value, maybeShownPage })), "@gjermundgaraba-designs/pixel-paper/src/commandPalette/commandPalette.js#onInput"),
503
524
  isReadOnly: isPicking(model),
504
525
  placeholder: Option.match(shown.maybePage, {
505
- onNone: () => __foldkitBrandViewResult((`type a command or a ${noun}`), "@gjermundgaraba-designs/pixel-paper/src/commandPalette/commandPalette.js#onNone~18"),
506
- onSome: ({ page }) => __foldkitBrandViewResult((page.find ?? "type to narrow"), "@gjermundgaraba-designs/pixel-paper/src/commandPalette/commandPalette.js#onSome~17"),
526
+ onNone: () => __foldkitBrandViewResult((`type a command or a ${noun}`), "@gjermundgaraba-designs/pixel-paper/src/commandPalette/commandPalette.js#onNone~19"),
527
+ onSome: ({ page }) => __foldkitBrandViewResult((page.find ?? "type to narrow"), "@gjermundgaraba-designs/pixel-paper/src/commandPalette/commandPalette.js#onSome~18"),
507
528
  }),
508
529
  toView: (groups) => __foldkitBrandViewResult((h.input([
509
530
  ...render.initialFocus,
@@ -519,17 +540,17 @@ const headView = (model, shown, scope, field, h) => __foldkitBrandViewResult((h.
519
540
  isQuiet: true,
520
541
  isDisabled: isPicking(model),
521
542
  onClick: Message.ClickedBack(),
522
- }, h)), "@gjermundgaraba-designs/pixel-paper/src/commandPalette/commandPalette.js#anonymous~48"))),
543
+ }, h)), "@gjermundgaraba-designs/pixel-paper/src/commandPalette/commandPalette.js#anonymous~51"))),
523
544
  h.span([h.Class("palette-scope")], [scope]),
524
- ...Option.toArray(Option.map(shown.maybePage, ({ page }) => __foldkitBrandViewResult((h.span([h.Class("palette-title")], [page.title])), "@gjermundgaraba-designs/pixel-paper/src/commandPalette/commandPalette.js#anonymous~49"))),
545
+ ...Option.toArray(Option.map(shown.maybePage, ({ page }) => __foldkitBrandViewResult((h.span([h.Class("palette-title")], [page.title])), "@gjermundgaraba-designs/pixel-paper/src/commandPalette/commandPalette.js#anonymous~52"))),
525
546
  field,
526
547
  ])), "@gjermundgaraba-designs/pixel-paper/src/commandPalette/commandPalette.js#headView");
527
548
  const listView = (model, shown, h) => __foldkitBrandViewResult((h.div([
528
549
  h.Id(listIdOf(id(model))),
529
550
  h.Role("listbox"),
530
551
  h.AriaLabel(Option.match(shown.maybePage, {
531
- onNone: () => __foldkitBrandViewResult(("Results"), "@gjermundgaraba-designs/pixel-paper/src/commandPalette/commandPalette.js#onNone~19"),
532
- onSome: ({ page }) => __foldkitBrandViewResult((page.title), "@gjermundgaraba-designs/pixel-paper/src/commandPalette/commandPalette.js#onSome~18"),
552
+ onNone: () => __foldkitBrandViewResult(("Results"), "@gjermundgaraba-designs/pixel-paper/src/commandPalette/commandPalette.js#onNone~20"),
553
+ onSome: ({ page }) => __foldkitBrandViewResult((page.title), "@gjermundgaraba-designs/pixel-paper/src/commandPalette/commandPalette.js#onSome~19"),
533
554
  })),
534
555
  ...(isPicking(model) ? [h.AriaBusy(true)] : []),
535
556
  h.Class("palette-list"),
@@ -542,13 +563,15 @@ const listView = (model, shown, h) => __foldkitBrandViewResult((h.div([
542
563
  // count.
543
564
  const countView = (model, shown, h) => {
544
565
  const shownQuery = shownQueryOf(model, shown);
545
- return __foldkitBrandViewResult((h.div([h.Role("status"), h.Class("sr-only")], String.isEmpty(shownQuery) ? [] : [countWords(shown.rows.length)])), "@gjermundgaraba-designs/pixel-paper/src/commandPalette/commandPalette.js#countView");
566
+ return __foldkitBrandViewResult((h.div([h.Role("status"), h.Class("sr-only")], String.isEmpty(shownQuery)
567
+ ? []
568
+ : [countWords(shown.rows.length - HashSet.size(shown.contextIds))])), "@gjermundgaraba-designs/pixel-paper/src/commandPalette/commandPalette.js#countView");
546
569
  };
547
570
  const cardView = (model, viewInputs, render, h) => {
548
- const { commands, scope = "commands", noun = "title", toDetailView = () => __foldkitBrandViewResult(([]), "@gjermundgaraba-designs/pixel-paper/src/commandPalette/commandPalette.js#anonymous~50") } = viewInputs;
571
+ const { commands, scope = "commands", noun = "title", toDetailView = () => __foldkitBrandViewResult(([]), "@gjermundgaraba-designs/pixel-paper/src/commandPalette/commandPalette.js#anonymous~53") } = viewInputs;
549
572
  const shown = shownOf(model, commands);
550
573
  return __foldkitBrandViewResult((h.div([
551
- ...Option.toArray(Option.map(shownPageIdOf(shown), (commandId) => __foldkitBrandViewResult((h.DataAttribute(PAGE_ATTRIBUTE, commandId)), "@gjermundgaraba-designs/pixel-paper/src/commandPalette/commandPalette.js#anonymous~51"))),
574
+ ...Option.toArray(Option.map(shownPageIdOf(shown), (commandId) => __foldkitBrandViewResult((h.DataAttribute(PAGE_ATTRIBUTE, commandId)), "@gjermundgaraba-designs/pixel-paper/src/commandPalette/commandPalette.js#anonymous~54"))),
552
575
  h.OnMount(BindPaletteKeys()),
553
576
  h.Class("card palette"),
554
577
  ], [
@@ -556,7 +579,7 @@ const cardView = (model, viewInputs, render, h) => {
556
579
  ...slotView("div", "palette-detail", toDetailView(), h),
557
580
  listView(model, shown, h),
558
581
  countView(model, shown, h),
559
- h.div([h.Class("palette-more")], [Option.match(shown.maybePage, { onNone: () => __foldkitBrandViewResult((COMMANDS_FOOT), "@gjermundgaraba-designs/pixel-paper/src/commandPalette/commandPalette.js#onNone~20"), onSome: () => __foldkitBrandViewResult((PAGE_FOOT), "@gjermundgaraba-designs/pixel-paper/src/commandPalette/commandPalette.js#onSome~19") })]),
582
+ h.div([h.Class("palette-more")], [Option.match(shown.maybePage, { onNone: () => __foldkitBrandViewResult((COMMANDS_FOOT), "@gjermundgaraba-designs/pixel-paper/src/commandPalette/commandPalette.js#onNone~21"), onSome: () => __foldkitBrandViewResult((PAGE_FOOT), "@gjermundgaraba-designs/pixel-paper/src/commandPalette/commandPalette.js#onSome~20") })]),
560
583
  ])), "@gjermundgaraba-designs/pixel-paper/src/commandPalette/commandPalette.js#cardView");
561
584
  };
562
585
  /**
@@ -590,4 +613,4 @@ export const view = defineView((model, viewInputs, h) => __foldkitBrandViewResul
590
613
  toView: (render) => __foldkitBrandViewResult((cardView(model, viewInputs, render, h)), "@gjermundgaraba-designs/pixel-paper/src/commandPalette/commandPalette.js#toView~2"),
591
614
  },
592
615
  toParentMessage: (message) => __foldkitBrandViewResult((Message.GotDialogMessage({ message })), "@gjermundgaraba-designs/pixel-paper/src/commandPalette/commandPalette.js#toParentMessage~2"),
593
- })), "@gjermundgaraba-designs/pixel-paper/src/commandPalette/commandPalette.js#anonymous~52"));
616
+ })), "@gjermundgaraba-designs/pixel-paper/src/commandPalette/commandPalette.js#anonymous~55"));
@@ -1,3 +1,4 @@
1
+ import { HashSet } from "effect";
1
2
  import type { GlyphName } from "../glyph/glyphs.ts";
2
3
  /** One row of the palette: a command, or a choice on a page. */
3
4
  export type Choice = Readonly<{
@@ -14,6 +15,9 @@ export type Choice = Readonly<{
14
15
  isDisabled?: boolean;
15
16
  /** Why it cannot run now (`"No item open"`), printed in place of the hint while held. */
16
17
  reason?: string;
18
+ /** On a page, its depth in the page's tree, from 1 at the roots: it sits under the nearest
19
+ * choice before it one level up, and indents by its depth. A page without levels is flat. */
20
+ level?: number;
17
21
  }>;
18
22
  /** A page of choices a command opens in place (a status, a date, a destination). */
19
23
  export type Page = Readonly<{
@@ -24,14 +28,14 @@ export type Page = Readonly<{
24
28
  find?: string;
25
29
  /** The choice in force now: tagged current, and not picked again. */
26
30
  current?: string;
27
- /** Where the cursor starts while the reader has not moved it: this choice while it is
28
- * listed, else `current`, else the first choice (the next status after the current one; the
29
- * first a typed query finds). */
31
+ /** Where the cursor starts while the reader has not moved it: this choice while the query
32
+ * finds it, else `current` while the query finds it, else the first choice the query finds
33
+ * (the next status after the current one; the first a typed query finds). */
30
34
  start?: string;
31
35
  }>;
32
36
  /** One command, under its group's heading. With a `page` it opens that page in place instead
33
37
  * of being selected. */
34
- export type Command = Choice & Readonly<{
38
+ export type Command = Omit<Choice, "level"> & Readonly<{
35
39
  group: string;
36
40
  page?: Page;
37
41
  }>;
@@ -40,6 +44,15 @@ export declare const matches: (query: string, label: string) => boolean;
40
44
  /** The commands the query finds, under their groups in the order the groups first appear,
41
45
  * authored titles after the commands. With no query, authored titles wait. */
42
46
  export declare const findCommands: (commands: ReadonlyArray<Command>, query: string) => ReadonlyArray<Command>;
43
- /** The choices a page shows for its query. */
47
+ /** The choices a page's query finds. */
44
48
  export declare const findChoices: (page: Page, query: string) => ReadonlyArray<Choice>;
49
+ /** What a page shows for its query: the choices it finds; the rows, which are those choices
50
+ * each with the choices it sits under in the page's tree, in the page's order; and the rows
51
+ * kept only for the choices under them. */
52
+ export type PageChoices = Readonly<{
53
+ rows: ReadonlyArray<Choice>;
54
+ found: ReadonlyArray<Choice>;
55
+ contextIds: HashSet.HashSet<string>;
56
+ }>;
57
+ export declare const pageChoices: (page: Page, query: string) => PageChoices;
45
58
  //# sourceMappingURL=match.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"match.d.ts","sourceRoot":"","sources":["../../../src/commandPalette/match.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAIpD,gEAAgE;AAChE,MAAM,MAAM,MAAM,GAAG,QAAQ,CAAC;IAC5B,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,0EAA0E;IAC1E,IAAI,CAAC,EAAE,MAAM,CAAC;IACd;uBACmB;IACnB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,KAAK,CAAC,EAAE,SAAS,CAAC;IAClB;2CACuC;IACvC,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,yFAAyF;IACzF,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB,CAAC,CAAC;AAEH,oFAAoF;AACpF,MAAM,MAAM,IAAI,GAAG,QAAQ,CAAC;IAC1B,yEAAyE;IACzE,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;IAC/B,kEAAkE;IAClE,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,qEAAqE;IACrE,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB;;sCAEkC;IAClC,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC,CAAC;AAEH;yBACyB;AACzB,MAAM,MAAM,OAAO,GAAG,MAAM,GAAG,QAAQ,CAAC;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,IAAI,CAAA;CAAE,CAAC,CAAC;AASxE,sFAAsF;AACtF,eAAO,MAAM,OAAO,UAAW,MAAM,SAAS,MAAM,KAAG,OAGtD,CAAC;AAEF;+EAC+E;AAC/E,eAAO,MAAM,YAAY,aACb,aAAa,CAAC,OAAO,CAAC,SACzB,MAAM,KACZ,aAAa,CAAC,OAAO,CAevB,CAAC;AAEF,8CAA8C;AAC9C,eAAO,MAAM,WAAW,SAAU,IAAI,SAAS,MAAM,KAAG,aAAa,CAAC,MAAM,CAGF,CAAC"}
1
+ {"version":3,"file":"match.d.ts","sourceRoot":"","sources":["../../../src/commandPalette/match.ts"],"names":[],"mappings":"AAAA,OAAO,EAAS,OAAO,EAA+B,MAAM,QAAQ,CAAC;AAErE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAIpD,gEAAgE;AAChE,MAAM,MAAM,MAAM,GAAG,QAAQ,CAAC;IAC5B,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,0EAA0E;IAC1E,IAAI,CAAC,EAAE,MAAM,CAAC;IACd;uBACmB;IACnB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,KAAK,CAAC,EAAE,SAAS,CAAC;IAClB;2CACuC;IACvC,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,yFAAyF;IACzF,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;kGAC8F;IAC9F,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC,CAAC;AAEH,oFAAoF;AACpF,MAAM,MAAM,IAAI,GAAG,QAAQ,CAAC;IAC1B,yEAAyE;IACzE,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;IAC/B,kEAAkE;IAClE,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,qEAAqE;IACrE,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB;;kFAE8E;IAC9E,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC,CAAC;AAEH;yBACyB;AACzB,MAAM,MAAM,OAAO,GAAG,IAAI,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,QAAQ,CAAC;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,IAAI,CAAA;CAAE,CAAC,CAAC;AASvF,sFAAsF;AACtF,eAAO,MAAM,OAAO,UAAW,MAAM,SAAS,MAAM,KAAG,OAGtD,CAAC;AAEF;+EAC+E;AAC/E,eAAO,MAAM,YAAY,aACb,aAAa,CAAC,OAAO,CAAC,SACzB,MAAM,KACZ,aAAa,CAAC,OAAO,CAevB,CAAC;AAIF,wCAAwC;AACxC,eAAO,MAAM,WAAW,SAAU,IAAI,SAAS,MAAM,KAAG,aAAa,CAAC,MAAM,CAGF,CAAC;AAM3E;;4CAE4C;AAC5C,MAAM,MAAM,WAAW,GAAG,QAAQ,CAAC;IACjC,IAAI,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;IAC5B,KAAK,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;IAC7B,UAAU,EAAE,OAAO,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;CACrC,CAAC,CAAC;AA2BH,eAAO,MAAM,WAAW,SAAU,IAAI,SAAS,MAAM,KAAG,WACM,CAAC"}
@@ -1,5 +1,5 @@
1
1
  import { brandViewResult as __foldkitBrandViewResult } from 'foldkit/brand'
2
- import { Array, Option, Order, String, pipe } from "effect";
2
+ import { Array, HashSet, Option, Order, String, pipe } from "effect";
3
3
  // MATCHING
4
4
  const isSubsequence = (needle, haystack) => __foldkitBrandViewResult((Array.reduce(Array.fromIterable(haystack), 0, (found, character) => __foldkitBrandViewResult((needle.charAt(found) === character ? found + 1 : found), "@gjermundgaraba-designs/pixel-paper/src/commandPalette/match.js#anonymous")) === needle.length), "@gjermundgaraba-designs/pixel-paper/src/commandPalette/match.js#isSubsequence");
5
5
  /** Whether the query finds a label: its letters in order, anywhere, ignoring case. */
@@ -21,7 +21,33 @@ export const findCommands = (commands, query) => {
21
21
  const groupIndex = (command) => __foldkitBrandViewResult((Option.getOrElse(Array.findFirstIndex(groups, (group) => __foldkitBrandViewResult((group === command.group), "@gjermundgaraba-designs/pixel-paper/src/commandPalette/match.js#anonymous~7")), () => __foldkitBrandViewResult((groups.length), "@gjermundgaraba-designs/pixel-paper/src/commandPalette/match.js#anonymous~8"))), "@gjermundgaraba-designs/pixel-paper/src/commandPalette/match.js#groupIndex");
22
22
  return __foldkitBrandViewResult((Array.sort(ordered, Order.mapInput(Order.Number, groupIndex))), "@gjermundgaraba-designs/pixel-paper/src/commandPalette/match.js#findCommands");
23
23
  };
24
- /** The choices a page shows for its query. */
25
- export const findChoices = (page, query) => __foldkitBrandViewResult((String.isEmpty(String.trim(query))
24
+ const isBlank = (query) => __foldkitBrandViewResult((String.isEmpty(String.trim(query))), "@gjermundgaraba-designs/pixel-paper/src/commandPalette/match.js#isBlank");
25
+ /** The choices a page's query finds. */
26
+ export const findChoices = (page, query) => __foldkitBrandViewResult((isBlank(query)
26
27
  ? page.choices
27
28
  : Array.filter(page.choices, (choice) => __foldkitBrandViewResult((matches(query, choice.label)), "@gjermundgaraba-designs/pixel-paper/src/commandPalette/match.js#anonymous~9"))), "@gjermundgaraba-designs/pixel-paper/src/commandPalette/match.js#findChoices");
29
+ const WALK_START = { chain: [], keptIds: HashSet.empty() };
30
+ const unfiltered = (page) => (__foldkitBrandViewResult(({
31
+ rows: page.choices,
32
+ found: page.choices,
33
+ contextIds: HashSet.empty(),
34
+ }), "@gjermundgaraba-designs/pixel-paper/src/commandPalette/match.js#unfiltered"));
35
+ const treeChoices = (page, query) => {
36
+ const found = findChoices(page, query);
37
+ const foundIds = HashSet.fromIterable(Array.map(found, ({ id }) => __foldkitBrandViewResult((id), "@gjermundgaraba-designs/pixel-paper/src/commandPalette/match.js#anonymous~10")));
38
+ const { keptIds } = Array.reduce(page.choices, WALK_START, ({ chain, keptIds }, choice) => {
39
+ const nextChain = Array.append(Array.take(chain, (choice.level ?? 1) - 1), choice);
40
+ return __foldkitBrandViewResult(({
41
+ chain: nextChain,
42
+ keptIds: HashSet.has(foundIds, choice.id)
43
+ ? HashSet.union(keptIds, HashSet.fromIterable(Array.map(nextChain, ({ id }) => __foldkitBrandViewResult((id), "@gjermundgaraba-designs/pixel-paper/src/commandPalette/match.js#anonymous~12"))))
44
+ : keptIds,
45
+ }), "@gjermundgaraba-designs/pixel-paper/src/commandPalette/match.js#anonymous~11");
46
+ });
47
+ return __foldkitBrandViewResult(({
48
+ rows: Array.filter(page.choices, ({ id }) => __foldkitBrandViewResult((HashSet.has(keptIds, id)), "@gjermundgaraba-designs/pixel-paper/src/commandPalette/match.js#anonymous~13")),
49
+ found,
50
+ contextIds: HashSet.difference(keptIds, foundIds),
51
+ }), "@gjermundgaraba-designs/pixel-paper/src/commandPalette/match.js#treeChoices");
52
+ };
53
+ export const pageChoices = (page, query) => __foldkitBrandViewResult((isBlank(query) ? unfiltered(page) : treeChoices(page, query)), "@gjermundgaraba-designs/pixel-paper/src/commandPalette/match.js#pageChoices");
@@ -173,7 +173,8 @@ export declare const targetAtPoint: (subject: Subject, columns: ReadonlyArray<Co
173
173
  /** What moving `splitter` by `share` asks of the layout, from the sizes the layout holds (not
174
174
  * the sizes printed, which the stylesheet may hold at a minimum): a width splitter adds
175
175
  * `share` to its column's width; a pane splitter moves `share` of its column's height from
176
- * the pane below it to the pane above. None when what it sizes is gone. */
176
+ * the pane below it to the pane above, leaving each a weight above nothing however small the
177
+ * column. None when what it sizes is gone. */
177
178
  export declare const resizeOf: (layout: Layout, splitter: Splitter, share: number) => Option.Option<Resize>;
178
179
  /** Whether what `splitter` sizes is still in `layout`. */
179
180
  export declare const isSplitterPresent: (layout: Layout, splitter: Splitter) => boolean;
@@ -1 +1 @@
1
- {"version":3,"file":"layout.d.ts","sourceRoot":"","sources":["../../../src/reel/layout.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"layout.d.ts","sourceRoot":"","sources":["../../../src/reel/layout.ts"],"names":[],"mappings":"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"}
@@ -69,9 +69,10 @@ export const columnIndexOf = (layout, columnId) => __foldkitBrandViewResult((Arr
69
69
  export const columnOf = (layout, columnId) => __foldkitBrandViewResult((Array.findFirst(layout, (column) => __foldkitBrandViewResult((column.id === columnId), "@gjermundgaraba-designs/pixel-paper/src/reel/layout.js#anonymous~7"))), "@gjermundgaraba-designs/pixel-paper/src/reel/layout.js#columnOf");
70
70
  /** The id of the column after the one at `columnIndex`: none after the last. */
71
71
  const nextColumnId = (layout, columnIndex) => __foldkitBrandViewResult((Option.map(Array.get(layout, columnIndex + 1), (column) => __foldkitBrandViewResult((column.id), "@gjermundgaraba-designs/pixel-paper/src/reel/layout.js#anonymous~8"))), "@gjermundgaraba-designs/pixel-paper/src/reel/layout.js#nextColumnId");
72
+ // a running mean, which never passes the largest weight, where the weights' sum may overflow
72
73
  const meanWeight = (panes) => __foldkitBrandViewResult((Array.match(panes, {
73
74
  onEmpty: () => __foldkitBrandViewResult((1), "@gjermundgaraba-designs/pixel-paper/src/reel/layout.js#onEmpty"),
74
- onNonEmpty: (some) => __foldkitBrandViewResult((Number.sumAll(Array.map(some, (pane) => __foldkitBrandViewResult((pane.weight), "@gjermundgaraba-designs/pixel-paper/src/reel/layout.js#anonymous~9"))) / some.length), "@gjermundgaraba-designs/pixel-paper/src/reel/layout.js#onNonEmpty"),
75
+ onNonEmpty: (some) => __foldkitBrandViewResult((Array.reduce(some, 0, (mean, pane, index) => __foldkitBrandViewResult((mean + (pane.weight - mean) / (index + 1)), "@gjermundgaraba-designs/pixel-paper/src/reel/layout.js#anonymous~9"))), "@gjermundgaraba-designs/pixel-paper/src/reel/layout.js#onNonEmpty"),
75
76
  })), "@gjermundgaraba-designs/pixel-paper/src/reel/layout.js#meanWeight");
76
77
  const othersIn = (column, paneId) => __foldkitBrandViewResult((Array.filter(column.panes, (pane) => __foldkitBrandViewResult((pane.id !== paneId), "@gjermundgaraba-designs/pixel-paper/src/reel/layout.js#anonymous~10"))), "@gjermundgaraba-designs/pixel-paper/src/reel/layout.js#othersIn");
77
78
  const insertAt = (items, index, item) => {
@@ -343,10 +344,20 @@ const WIDTH_FLOOR = 0.1;
343
344
  const SHARE_FLOOR = 0.05;
344
345
  const PRECISION = 10_000;
345
346
  const rounded = (value) => __foldkitBrandViewResult((Math.round(value * PRECISION) / PRECISION), "@gjermundgaraba-designs/pixel-paper/src/reel/layout.js#rounded");
347
+ /** `weight`, at least `floor`, to four decimals, or, where a step of four decimals is more than
348
+ * `floor` (a column whose weights total under a five-hundredth, or two panes that small), to
349
+ * the first power of ten no more than it, so that it never rounds to nothing. A weight too
350
+ * large or a floor too small to scale by that power is left as it is. */
351
+ const roundedOver = (weight, floor) => {
352
+ const precision = Math.max(PRECISION, 10 ** Math.ceil(-Math.log10(floor)));
353
+ const scaled = weight * precision;
354
+ return __foldkitBrandViewResult((globalThis.Number.isFinite(scaled) ? Math.round(scaled) / precision : weight), "@gjermundgaraba-designs/pixel-paper/src/reel/layout.js#roundedOver");
355
+ };
346
356
  /** What moving `splitter` by `share` asks of the layout, from the sizes the layout holds (not
347
357
  * the sizes printed, which the stylesheet may hold at a minimum): a width splitter adds
348
358
  * `share` to its column's width; a pane splitter moves `share` of its column's height from
349
- * the pane below it to the pane above. None when what it sizes is gone. */
359
+ * the pane below it to the pane above, leaving each a weight above nothing however small the
360
+ * column. None when what it sizes is gone. */
350
361
  export const resizeOf = (layout, splitter, share) => __foldkitBrandViewResult((Splitter.match(splitter, {
351
362
  Width: ({ columnId }) => __foldkitBrandViewResult((Option.map(columnOf(layout, columnId), (column) => __foldkitBrandViewResult((OutMessage.ResizedColumn({
352
363
  columnId,
@@ -355,14 +366,21 @@ export const resizeOf = (layout, splitter, share) => __foldkitBrandViewResult((S
355
366
  Split: ({ abovePaneId }) => __foldkitBrandViewResult((Option.flatMap(placeOf(layout, abovePaneId), ({ column, row, pane: above }) => __foldkitBrandViewResult((Option.map(Array.get(column.panes, row + 1), (below) => {
356
367
  const total = Number.sumAll(Array.map(column.panes, ({ weight }) => __foldkitBrandViewResult((weight), "@gjermundgaraba-designs/pixel-paper/src/reel/layout.js#anonymous~51")));
357
368
  const pair = above.weight + below.weight;
358
- const floor = Math.min(total * SHARE_FLOOR, pair / 2);
369
+ // a column so small that a twentieth of it is no number keeps the least there is
370
+ const floor = Math.max(Math.min(total * SHARE_FLOOR, pair / 2), globalThis.Number.MIN_VALUE);
359
371
  const nextAbove = Number.clamp(above.weight + share * total, {
360
372
  minimum: floor,
361
373
  maximum: pair - floor,
362
374
  });
363
375
  return __foldkitBrandViewResult((OutMessage.ResizedPanes({
364
376
  columnId: column.id,
365
- weights: { [above.id]: rounded(nextAbove), [below.id]: rounded(pair - nextAbove) },
377
+ weights: globalThis.Number.isFinite(total)
378
+ ? {
379
+ [above.id]: roundedOver(nextAbove, floor),
380
+ [below.id]: roundedOver(pair - nextAbove, floor),
381
+ }
382
+ : // weights that total past the largest number have no share to move: they stay
383
+ { [above.id]: above.weight, [below.id]: below.weight },
366
384
  })), "@gjermundgaraba-designs/pixel-paper/src/reel/layout.js#anonymous~50");
367
385
  })), "@gjermundgaraba-designs/pixel-paper/src/reel/layout.js#anonymous~49"))), "@gjermundgaraba-designs/pixel-paper/src/reel/layout.js#Split"),
368
386
  })), "@gjermundgaraba-designs/pixel-paper/src/reel/layout.js#resizeOf");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gjermundgaraba-designs/pixel-paper",
3
- "version": "0.19.0",
3
+ "version": "0.20.1",
4
4
  "license": "MIT",
5
5
  "repository": {
6
6
  "type": "git",
@@ -1,5 +1,5 @@
1
1
  import { Input as UiInput } from "@foldkit/ui";
2
- import { Array, Effect, Match, Option, Schema, Stream, String, pipe } from "effect";
2
+ import { Array, Effect, HashSet, Match, Option, Schema, Stream, String, pipe } from "effect";
3
3
  import * as FoldkitCommand from "foldkit/command";
4
4
  import * as Dom from "foldkit/dom";
5
5
  import type { Attribute, Html, HtmlBuilder, KeyboardModifiers } from "foldkit/html";
@@ -27,7 +27,7 @@ import {
27
27
  earlyKeys,
28
28
  shownPageOf,
29
29
  } from "./keys.ts";
30
- import { findChoices, findCommands } from "./match.ts";
30
+ import { findCommands, pageChoices } from "./match.ts";
31
31
  import type { Choice, Command, Page } from "./match.ts";
32
32
  import { Message } from "./message.ts";
33
33
  import type { ArrowKey, ShownRow } from "./message.ts";
@@ -329,7 +329,7 @@ type ShownList = Readonly<{
329
329
  shownShift: number;
330
330
  }>;
331
331
 
332
- const isListedIn = (rows: ReadonlyArray<ShownRow>, rowId: string): boolean =>
332
+ const isListed = (rows: ReadonlyArray<Readonly<{ id: string }>>, rowId: string): boolean =>
333
333
  Array.some(rows, (row) => row.id === rowId);
334
334
 
335
335
  // NOTE: where the cursor is among the rows a key carries (see `Model`). With no arrows waiting
@@ -339,16 +339,16 @@ const isListedIn = (rows: ReadonlyArray<ShownRow>, rowId: string): boolean =>
339
339
  // key waits for the next render.
340
340
  const cursorAsShown = (model: Model, shown: ShownList): Option.Option<Option.Option<string>> => {
341
341
  const { rows, maybeShownCursor, shownQuery, shownShift } = shown;
342
- const isListed = shownQuery === query(model);
342
+ const isTheList = shownQuery === query(model);
343
343
 
344
- if (isListed && model.cursorShift === 0) {
344
+ if (isTheList && model.cursorShift === 0) {
345
345
  return Option.some(
346
346
  Option.orElse(
347
- Option.filter(maybeCurrentCursor(model), (rowId) => isListedIn(rows, rowId)),
347
+ Option.filter(maybeCurrentCursor(model), (rowId) => isListed(rows, rowId)),
348
348
  () => maybeShownCursor,
349
349
  ),
350
350
  );
351
- } else if (isListed && shownShift === model.cursorShift) {
351
+ } else if (isTheList && shownShift === model.cursorShift) {
352
352
  return Option.some(maybeShownCursor);
353
353
  } else {
354
354
  return Option.none();
@@ -571,6 +571,8 @@ type ShownPageState = Readonly<{ commandId: string; page: Page; state: OpenPage
571
571
  type Shown = Readonly<{
572
572
  maybePage: Option.Option<ShownPageState>;
573
573
  rows: ReadonlyArray<Choice | Command>;
574
+ /** The rows a page's query keeps only to show where a row it finds sits in the tree. */
575
+ contextIds: HashSet.HashSet<string>;
574
576
  maybeCursorId: Option.Option<string>;
575
577
  }>;
576
578
 
@@ -581,16 +583,14 @@ const pageOf = (commands: ReadonlyArray<Command>, state: OpenPage): Option.Optio
581
583
  Option.map((page) => ({ commandId: state.commandId, page, state })),
582
584
  );
583
585
 
584
- const cursorOn = (
586
+ const firstListed = (
585
587
  rows: ReadonlyArray<Choice>,
586
588
  candidates: ReadonlyArray<Option.Option<string>>,
587
589
  ): Option.Option<string> =>
588
- Option.orElse(
589
- Array.findFirst(Array.getSomes(candidates), (cursorId) =>
590
- Array.some(rows, (row) => row.id === cursorId),
591
- ),
592
- () => Option.map(Array.head(rows), (row) => row.id),
593
- );
590
+ Array.findFirst(Array.getSomes(candidates), (cursorId) => isListed(rows, cursorId));
591
+
592
+ const firstId = (rows: ReadonlyArray<Choice | Command>): Option.Option<string> =>
593
+ Option.map(Array.head(rows), (row) => row.id);
594
594
 
595
595
  const shifted = (shown: Shown, shift: number): Shown => ({
596
596
  ...shown,
@@ -610,36 +610,52 @@ const shownOf = (model: Model, commands: ReadonlyArray<Command>): Shown => {
610
610
  };
611
611
 
612
612
  // What the view shows of the Model with `commands`: the open page while its command is
613
- // listed, the rows its query finds, and the cursor's row: the row the reader moved it to
614
- // while that row is listed, else the page's `start`, else its `current`, else the first row;
615
- // none when nothing is listed.
613
+ // listed, the rows its query finds (on a tree page, with the rows they sit under), and the
614
+ // cursor's row: the row the reader moved it to while that row is listed, else the page's
615
+ // `start`, else its `current`, else the first row, these three only among the rows the query
616
+ // finds; none when nothing is listed.
616
617
  const unshiftedOf = (model: Model, commands: ReadonlyArray<Command>): Shown => {
617
618
  const maybePage = Option.flatMap(model.maybePage, (state) => pageOf(commands, state));
618
619
  return Option.match(maybePage, {
619
620
  onNone: () => {
620
621
  const rows = findCommands(commands, model.query);
621
- return { maybePage, rows, maybeCursorId: cursorOn(rows, [model.maybeCursor]) };
622
+ return {
623
+ maybePage,
624
+ rows,
625
+ contextIds: HashSet.empty(),
626
+ maybeCursorId: pipe(
627
+ Option.filter(model.maybeCursor, (rowId) => isListed(rows, rowId)),
628
+ Option.orElse(() => firstId(rows)),
629
+ ),
630
+ };
622
631
  },
623
632
  onSome: ({ page, state }) => {
624
- const rows = findChoices(page, state.query);
633
+ const { rows, found, contextIds } = pageChoices(page, state.query);
625
634
  return {
626
635
  maybePage,
627
636
  rows,
628
- maybeCursorId: cursorOn(rows, [
629
- state.maybeCursor,
630
- Option.fromNullishOr(page.start),
631
- Option.fromNullishOr(page.current),
632
- ]),
637
+ contextIds,
638
+ maybeCursorId: pipe(
639
+ Option.filter(state.maybeCursor, (rowId) => isListed(rows, rowId)),
640
+ Option.orElse(() =>
641
+ firstListed(found, [
642
+ Option.fromNullishOr(page.start),
643
+ Option.fromNullishOr(page.current),
644
+ ]),
645
+ ),
646
+ Option.orElse(() => firstId(found)),
647
+ ),
633
648
  };
634
649
  },
635
650
  });
636
651
  };
637
652
 
638
653
  /** The row the cursor is on as the view shows it with `commands`: the row the reader moved it
639
- * to while that row is listed, else the page's `start` or `current`, else the first row; none
640
- * when nothing is listed. Compare it before and after the caller's commands change to know
641
- * when the cursor fell to another row (its row left) and should be scrolled into view with
642
- * `ScrollToCursor`; a cursor whose row is still listed keeps it, and the list its scroll. */
654
+ * to while that row is listed, else the page's `start` or `current` while the query finds it,
655
+ * else the first row the query finds; none when nothing is listed. Compare it before and after
656
+ * the caller's commands change to know when the cursor fell to another row (its row left) and
657
+ * should be scrolled into view with `ScrollToCursor`; a cursor whose row is still listed keeps
658
+ * it, and the list its scroll. */
643
659
  export const maybeCursorId = (
644
660
  model: Model,
645
661
  commands: ReadonlyArray<Command>,
@@ -650,6 +666,12 @@ const isCommand = (row: Choice | Command): row is Command => "group" in row;
650
666
  const isCurrentRow = (shown: Shown, row: Choice | Command): boolean =>
651
667
  Option.exists(shown.maybePage, ({ page }) => page.current === row.id);
652
668
 
669
+ const isContextRow = (shown: Shown, row: Choice | Command): boolean =>
670
+ HashSet.has(shown.contextIds, row.id);
671
+
672
+ const levelOf = (row: Choice | Command): Option.Option<number> =>
673
+ "level" in row ? Option.fromNullishOr(row.level) : Option.none();
674
+
653
675
  // What a row is as this render shows it, which Enter acts on as a click would.
654
676
  const shownRowFor = (shown: Shown, row: Choice | Command): ShownRow => ({
655
677
  id: row.id,
@@ -718,7 +740,13 @@ const rowPartsView = (
718
740
  ...(row.glyph === undefined
719
741
  ? []
720
742
  : [h.span([h.Class("glyph")], [Glyph.view({ name: row.glyph }, h)])]),
721
- h.span([h.Class("palette-label")], [row.label]),
743
+ h.span(
744
+ [h.Class("palette-label")],
745
+ [
746
+ row.label,
747
+ ...(isContextRow(shown, row) ? [h.span([h.Class("sr-only")], [" (context)"])] : []),
748
+ ],
749
+ ),
722
750
  ...(Option.contains(model.maybePicking, row.id)
723
751
  ? [h.span([h.Class("cursor"), h.AriaHidden(true)])]
724
752
  : []),
@@ -745,7 +773,20 @@ const rowView = (
745
773
  ...(shownRow.isHeld ? [h.AriaDisabled(true)] : []),
746
774
  h.DataAttribute(ROW_ATTRIBUTE, row.id),
747
775
  ...(shownRow.hasPage ? [h.DataAttribute(OPENS_PAGE_ATTRIBUTE, "")] : []),
748
- h.Class(row.isAuthored === true ? "palette-row authored" : "palette-row"),
776
+ h.Class(
777
+ Array.join(
778
+ [
779
+ "palette-row",
780
+ ...(row.isAuthored === true ? ["authored"] : []),
781
+ ...(isContextRow(shown, row) ? ["context"] : []),
782
+ ],
783
+ " ",
784
+ ),
785
+ ),
786
+ ...Option.match(levelOf(row), {
787
+ onNone: () => [],
788
+ onSome: (level) => [h.Style({ "--palette-level": `${level}` })],
789
+ }),
749
790
  ...(shownRow.isHeld || isPicking(model)
750
791
  ? []
751
792
  : [h.OnClick(Message.ClickedRow({ row: shownRow, maybeShownPage: shownPageIdOf(shown) }))]),
@@ -898,7 +939,9 @@ const countView = (model: Model, shown: Shown, h: HtmlBuilder<Message>): Html =>
898
939
  const shownQuery = shownQueryOf(model, shown);
899
940
  return h.div(
900
941
  [h.Role("status"), h.Class("sr-only")],
901
- String.isEmpty(shownQuery) ? [] : [countWords(shown.rows.length)],
942
+ String.isEmpty(shownQuery)
943
+ ? []
944
+ : [countWords(shown.rows.length - HashSet.size(shown.contextIds))],
902
945
  );
903
946
  };
904
947
 
@@ -1,4 +1,4 @@
1
- import { Array, Option, Order, String, pipe } from "effect";
1
+ import { Array, HashSet, Option, Order, String, pipe } from "effect";
2
2
 
3
3
  import type { GlyphName } from "../glyph/glyphs.ts";
4
4
 
@@ -19,6 +19,9 @@ export type Choice = Readonly<{
19
19
  isDisabled?: boolean;
20
20
  /** Why it cannot run now (`"No item open"`), printed in place of the hint while held. */
21
21
  reason?: string;
22
+ /** On a page, its depth in the page's tree, from 1 at the roots: it sits under the nearest
23
+ * choice before it one level up, and indents by its depth. A page without levels is flat. */
24
+ level?: number;
22
25
  }>;
23
26
 
24
27
  /** A page of choices a command opens in place (a status, a date, a destination). */
@@ -30,15 +33,15 @@ export type Page = Readonly<{
30
33
  find?: string;
31
34
  /** The choice in force now: tagged current, and not picked again. */
32
35
  current?: string;
33
- /** Where the cursor starts while the reader has not moved it: this choice while it is
34
- * listed, else `current`, else the first choice (the next status after the current one; the
35
- * first a typed query finds). */
36
+ /** Where the cursor starts while the reader has not moved it: this choice while the query
37
+ * finds it, else `current` while the query finds it, else the first choice the query finds
38
+ * (the next status after the current one; the first a typed query finds). */
36
39
  start?: string;
37
40
  }>;
38
41
 
39
42
  /** One command, under its group's heading. With a `page` it opens that page in place instead
40
43
  * of being selected. */
41
- export type Command = Choice & Readonly<{ group: string; page?: Page }>;
44
+ export type Command = Omit<Choice, "level"> & Readonly<{ group: string; page?: Page }>;
42
45
 
43
46
  // MATCHING
44
47
 
@@ -75,8 +78,51 @@ export const findCommands = (
75
78
  return Array.sort(ordered, Order.mapInput(Order.Number, groupIndex));
76
79
  };
77
80
 
78
- /** The choices a page shows for its query. */
81
+ const isBlank = (query: string): boolean => String.isEmpty(String.trim(query));
82
+
83
+ /** The choices a page's query finds. */
79
84
  export const findChoices = (page: Page, query: string): ReadonlyArray<Choice> =>
80
- String.isEmpty(String.trim(query))
85
+ isBlank(query)
81
86
  ? page.choices
82
87
  : Array.filter(page.choices, (choice) => matches(query, choice.label));
88
+
89
+ type Walk = Readonly<{ chain: ReadonlyArray<Choice>; keptIds: HashSet.HashSet<string> }>;
90
+
91
+ const WALK_START: Walk = { chain: [], keptIds: HashSet.empty() };
92
+
93
+ /** What a page shows for its query: the choices it finds; the rows, which are those choices
94
+ * each with the choices it sits under in the page's tree, in the page's order; and the rows
95
+ * kept only for the choices under them. */
96
+ export type PageChoices = Readonly<{
97
+ rows: ReadonlyArray<Choice>;
98
+ found: ReadonlyArray<Choice>;
99
+ contextIds: HashSet.HashSet<string>;
100
+ }>;
101
+
102
+ const unfiltered = (page: Page): PageChoices => ({
103
+ rows: page.choices,
104
+ found: page.choices,
105
+ contextIds: HashSet.empty(),
106
+ });
107
+
108
+ const treeChoices = (page: Page, query: string): PageChoices => {
109
+ const found = findChoices(page, query);
110
+ const foundIds = HashSet.fromIterable(Array.map(found, ({ id }) => id));
111
+ const { keptIds } = Array.reduce(page.choices, WALK_START, ({ chain, keptIds }, choice): Walk => {
112
+ const nextChain = Array.append(Array.take(chain, (choice.level ?? 1) - 1), choice);
113
+ return {
114
+ chain: nextChain,
115
+ keptIds: HashSet.has(foundIds, choice.id)
116
+ ? HashSet.union(keptIds, HashSet.fromIterable(Array.map(nextChain, ({ id }) => id)))
117
+ : keptIds,
118
+ };
119
+ });
120
+ return {
121
+ rows: Array.filter(page.choices, ({ id }) => HashSet.has(keptIds, id)),
122
+ found,
123
+ contextIds: HashSet.difference(keptIds, foundIds),
124
+ };
125
+ };
126
+
127
+ export const pageChoices = (page: Page, query: string): PageChoices =>
128
+ isBlank(query) ? unfiltered(page) : treeChoices(page, query);
@@ -2893,7 +2893,9 @@ dialog.veil:focus {
2893
2893
  align-items: center;
2894
2894
  gap: 10px;
2895
2895
  width: 100%;
2896
- padding: 8px 16px;
2896
+ /* on a page's tree a choice indents a step a level under the roots (--palette-level), never
2897
+ past a third of the row */
2898
+ padding: 8px 16px 8px calc(16px + min((var(--palette-level, 1) - 1) * 20px, 33%));
2897
2899
  font-family: var(--font-px);
2898
2900
  font-size: var(--type-control);
2899
2901
  text-transform: uppercase;
@@ -2923,6 +2925,11 @@ dialog.veil:focus {
2923
2925
  background-color: var(--head);
2924
2926
  }
2925
2927
 
2928
+ /* a choice a query keeps only to show where a found one sits: muted, still pickable */
2929
+ .palette-row.context:not([aria-selected="true"]) {
2930
+ color: var(--muted);
2931
+ }
2932
+
2926
2933
  .palette-row .hint {
2927
2934
  margin-left: auto;
2928
2935
  font-size: var(--type-state);
@@ -111,10 +111,12 @@ export const columnOf = (layout: Layout, columnId: string): Option.Option<Column
111
111
  const nextColumnId = (layout: Layout, columnIndex: number): Option.Option<string> =>
112
112
  Option.map(Array.get(layout, columnIndex + 1), (column) => column.id);
113
113
 
114
+ // a running mean, which never passes the largest weight, where the weights' sum may overflow
114
115
  const meanWeight = (panes: ReadonlyArray<Pane>): number =>
115
116
  Array.match(panes, {
116
117
  onEmpty: () => 1,
117
- onNonEmpty: (some) => Number.sumAll(Array.map(some, (pane) => pane.weight)) / some.length,
118
+ onNonEmpty: (some) =>
119
+ Array.reduce(some, 0, (mean, pane, index) => mean + (pane.weight - mean) / (index + 1)),
118
120
  });
119
121
 
120
122
  const othersIn = (column: Column, paneId: string): ReadonlyArray<Pane> =>
@@ -562,10 +564,21 @@ const PRECISION = 10_000;
562
564
 
563
565
  const rounded = (value: number): number => Math.round(value * PRECISION) / PRECISION;
564
566
 
567
+ /** `weight`, at least `floor`, to four decimals, or, where a step of four decimals is more than
568
+ * `floor` (a column whose weights total under a five-hundredth, or two panes that small), to
569
+ * the first power of ten no more than it, so that it never rounds to nothing. A weight too
570
+ * large or a floor too small to scale by that power is left as it is. */
571
+ const roundedOver = (weight: number, floor: number): number => {
572
+ const precision = Math.max(PRECISION, 10 ** Math.ceil(-Math.log10(floor)));
573
+ const scaled = weight * precision;
574
+ return globalThis.Number.isFinite(scaled) ? Math.round(scaled) / precision : weight;
575
+ };
576
+
565
577
  /** What moving `splitter` by `share` asks of the layout, from the sizes the layout holds (not
566
578
  * the sizes printed, which the stylesheet may hold at a minimum): a width splitter adds
567
579
  * `share` to its column's width; a pane splitter moves `share` of its column's height from
568
- * the pane below it to the pane above. None when what it sizes is gone. */
580
+ * the pane below it to the pane above, leaving each a weight above nothing however small the
581
+ * column. None when what it sizes is gone. */
569
582
  export const resizeOf = (
570
583
  layout: Layout,
571
584
  splitter: Splitter,
@@ -584,14 +597,24 @@ export const resizeOf = (
584
597
  Option.map(Array.get(column.panes, row + 1), (below) => {
585
598
  const total = Number.sumAll(Array.map(column.panes, ({ weight }) => weight));
586
599
  const pair = above.weight + below.weight;
587
- const floor = Math.min(total * SHARE_FLOOR, pair / 2);
600
+ // a column so small that a twentieth of it is no number keeps the least there is
601
+ const floor = Math.max(
602
+ Math.min(total * SHARE_FLOOR, pair / 2),
603
+ globalThis.Number.MIN_VALUE,
604
+ );
588
605
  const nextAbove = Number.clamp(above.weight + share * total, {
589
606
  minimum: floor,
590
607
  maximum: pair - floor,
591
608
  });
592
609
  return OutMessage.ResizedPanes({
593
610
  columnId: column.id,
594
- weights: { [above.id]: rounded(nextAbove), [below.id]: rounded(pair - nextAbove) },
611
+ weights: globalThis.Number.isFinite(total)
612
+ ? {
613
+ [above.id]: roundedOver(nextAbove, floor),
614
+ [below.id]: roundedOver(pair - nextAbove, floor),
615
+ }
616
+ : // weights that total past the largest number have no share to move: they stay
617
+ { [above.id]: above.weight, [below.id]: below.weight },
595
618
  });
596
619
  }),
597
620
  ),