@gjermundgaraba-designs/pixel-paper 0.19.0 → 0.20.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/DESIGN.md +1 -1
- package/dist/src/commandPalette/commandPalette.d.ts +2 -2
- package/dist/src/commandPalette/commandPalette.d.ts.map +1 -1
- package/dist/src/commandPalette/commandPalette.js +66 -42
- package/dist/src/commandPalette/match.d.ts +15 -5
- package/dist/src/commandPalette/match.d.ts.map +1 -1
- package/dist/src/commandPalette/match.js +17 -2
- package/package.json +1 -1
- package/src/commandPalette/commandPalette.ts +71 -24
- package/src/commandPalette/match.ts +35 -6
- package/src/pixel-paper.css +8 -1
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,8 +424,8 @@ 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
|
|
428
|
-
* when nothing is listed. Compare it before and after the caller's commands change to know
|
|
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 the caller's commands change to know
|
|
429
429
|
* when the cursor fell to another row (its row left) and should be scrolled into view with
|
|
430
430
|
* `ScrollToCursor`; a cursor whose row is still listed keeps it, and the list its scroll. */
|
|
431
431
|
export declare const maybeCursorId: (model: Model, commands: ReadonlyArray<Command>) => Option.Option<string>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"commandPalette.d.ts","sourceRoot":"","sources":["../../../src/commandPalette/commandPalette.ts"],"names":[],"mappings":"AACA,OAAO,EAAS,MAAM,
|
|
1
|
+
{"version":3,"file":"commandPalette.d.ts","sourceRoot":"","sources":["../../../src/commandPalette/commandPalette.ts"],"names":[],"mappings":"AACA,OAAO,EAAS,MAAM,EAAkB,MAAM,EAAE,MAAM,EAAwB,MAAM,QAAQ,CAAC;AAC7F,OAAO,KAAK,cAAc,MAAM,iBAAiB,CAAC;AAElD,OAAO,KAAK,EAAa,IAAI,EAAkC,MAAM,cAAc,CAAC;AAKpF,OAAO,KAAK,YAAY,MAAM,sBAAsB,CAAC;AACrD,OAAO,KAAK,MAAM,MAAM,gBAAgB,CAAC;AAoBzC,OAAO,KAAK,EAAU,OAAO,EAAQ,MAAM,YAAY,CAAC;AACxD,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAavC;;;;;;;;;;;kDAWkD;AAClD,eAAO,MAAM,KAAK;;;;;;;;;;;;;;;;;;;;;;;;;EAQhB,CAAC;AACH,MAAM,MAAM,KAAK,GAAG,OAAO,KAAK,CAAC,IAAI,CAAC;AAItC;;;;;2EAK2E;AAC3E,eAAO,MAAM,UAAU;;QACF,EAAE;;;QACH,SAAS;QAAiB,QAAQ;;;EAEpD,CAAC;AACH,MAAM,MAAM,UAAU,GAAG,OAAO,UAAU,CAAC,IAAI,CAAC;AAIhD;gBACgB;AAChB,MAAM,MAAM,UAAU,GAAG,QAAQ,CAAC;IAAE,EAAE,EAAE,MAAM,CAAA;CAAE,CAAC,CAAC;AAKlD,wCAAwC;AACxC,eAAO,MAAM,IAAI,WAAY,UAAU,KAAG,KAQxC,CAAC;AAIH,gDAAgD;AAChD,eAAO,MAAM,EAAE,UAAW,KAAK,KAAG,MAAiC,CAAC;AAEpE,mCAAmC;AACnC,eAAO,MAAM,MAAM,UAAW,KAAK,KAAG,OAAsC,CAAC;AAE7E;+CAC+C;AAC/C,eAAO,MAAM,KAAK,UAAW,KAAK,KAAG,MACuD,CAAC;AAE7F,sCAAsC;AACtC,eAAO,MAAM,WAAW,UAAW,KAAK,KAAG,MAAM,CAAC,MAAM,CAAC,MAAM,CACR,CAAC;AAExD,kEAAkE;AAClE,eAAO,MAAM,SAAS,UAAW,KAAK,KAAG,OAA4C,CAAC;AAStF;;;;;;GAMG;AACH,eAAO,MAAM,cAAc;;;;iBAOzB,CAAC;AAyBH;;;mFAGmF;AACnF,eAAO,MAAM,iBAAiB;;;;;;;;;;;;;;iBAS5B,CAAC;AA4RH,+FAA+F;AAC/F,eAAO,MAAM,MAAM,UAAW,KAAK,WAAW,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAuBjD,CAAC;AAEL;;2FAE2F;AAC3F,eAAO,MAAM,IAAI,UAAW,KAAK,KAAG,MAAM,CAAC,MAAM,CAAC,KAAK,EAAE,OAAO,CAClB,CAAC;AAE/C,kCAAkC;AAClC,MAAM,MAAM,YAAY,GAAG,QAAQ,CAAC;IAClC;8BAC0B;IAC1B,SAAS,EAAE,MAAM,CAAC;CACnB,CAAC,CAAC;AAEH,wFAAwF;AACxF,eAAO,MAAM,MAAM,UAAW,KAAK,UAAU,YAAY,KAAG,MAAM,CAAC,MAAM,CAAC,KAAK,EAAE,OAAO,CACf,CAAC;AAE1E;;mDAEmD;AACnD,eAAO,MAAM,aAAa,UAAW,KAAK,YAAY,MAAM,KAAG,MAAM,CAAC,MAAM,CAAC,KAAK,EAAE,OAAO,CAG5E,CAAC;AAEhB;;uBAEuB;AACvB,eAAO,MAAM,UAAU,UAAW,KAAK,YAAY,MAAM,KAAG,MAAM,CAAC,MAAM,CAAC,KAAK,EAAE,OAAO,CAGzE,CAAC;AAiBhB;;;;6FAI6F;AAC7F,eAAO,MAAM,aAAa,GAAI,MAAM,SAAS,MAAM,aAAa,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;mGACxB,CAAC;AAI/C,8EAA8E;AAC9E,MAAM,MAAM,UAAU,GAAG,QAAQ,CAAC;IAChC;oFACgF;IAChF,QAAQ,EAAE,aAAa,CAAC,OAAO,CAAC,CAAC;IACjC;wBACoB;IACpB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,mFAAmF;IACnF,IAAI,CAAC,EAAE,MAAM,CAAC;IACd;;kCAE8B;IAC9B,YAAY,CAAC,EAAE,MAAM,aAAa,CAAC,IAAI,GAAG,MAAM,CAAC,CAAC;IAClD;0CACsC;IACtC,gBAAgB,CAAC,EAAE,CAAC,MAAM,IAAI,CAAC,GAAG,SAAS,CAAC;CAC7C,CAAC,CAAC;AA2FH;;;;8FAI8F;AAC9F,eAAO,MAAM,aAAa,UACjB,KAAK,YACF,aAAa,CAAC,OAAO,CAAC,KAC/B,MAAM,CAAC,MAAM,CAAC,MAAM,CAA2C,CAAC;AA4TnE;;;;;;;;;;;;;GAaG;AAOH,eAAO,MAAM,IAAI;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;IAlcf;oFACgF;cACtE,aAAa,CAAC,OAAO,CAAC;IAChC;wBACoB;YACZ,MAAM;IACd,mFAAmF;WAC5E,MAAM;IACb;;kCAE8B;mBACf,MAAM,aAAa,CAAC,IAAI,GAAG,MAAM,CAAC;IACjD;0CACsC;uBACnB,CAAC,MAAM,IAAI,CAAC,GAAG,SAAS;GAic5C,CAAC"}
|
|
@@ -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 {
|
|
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. */
|
|
@@ -347,10 +347,12 @@ 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
|
|
350
|
+
const isListed = (rows, rowId) => __foldkitBrandViewResult((Array.some(rows, (row) => __foldkitBrandViewResult((row.id === rowId), "@gjermundgaraba-designs/pixel-paper/src/commandPalette/commandPalette.js#anonymous~26"))), "@gjermundgaraba-designs/pixel-paper/src/commandPalette/commandPalette.js#isListed");
|
|
351
|
+
const firstListed = (rows, candidates) => __foldkitBrandViewResult((Array.findFirst(Array.getSomes(candidates), (cursorId) => __foldkitBrandViewResult((isListed(rows, cursorId)), "@gjermundgaraba-designs/pixel-paper/src/commandPalette/commandPalette.js#anonymous~27"))), "@gjermundgaraba-designs/pixel-paper/src/commandPalette/commandPalette.js#firstListed");
|
|
352
|
+
const firstId = (rows) => __foldkitBrandViewResult((Option.map(Array.head(rows), (row) => __foldkitBrandViewResult((row.id), "@gjermundgaraba-designs/pixel-paper/src/commandPalette/commandPalette.js#anonymous~28"))), "@gjermundgaraba-designs/pixel-paper/src/commandPalette/commandPalette.js#firstId");
|
|
351
353
|
const shifted = (shown, shift) => (__foldkitBrandViewResult(({
|
|
352
354
|
...shown,
|
|
353
|
-
maybeCursorId: Option.map(rowAfter(shown.rows, shown.maybeCursorId, shift), (row) => __foldkitBrandViewResult((row.id), "@gjermundgaraba-designs/pixel-paper/src/commandPalette/commandPalette.js#anonymous~
|
|
355
|
+
maybeCursorId: Option.map(rowAfter(shown.rows, shown.maybeCursorId, shift), (row) => __foldkitBrandViewResult((row.id), "@gjermundgaraba-designs/pixel-paper/src/commandPalette/commandPalette.js#anonymous~29")),
|
|
354
356
|
}), "@gjermundgaraba-designs/pixel-paper/src/commandPalette/commandPalette.js#shifted"));
|
|
355
357
|
// NOTE: arrows waiting for the view count from the cursor's row. When that row has left the
|
|
356
358
|
// list (the application changed it in between), they are dropped rather than counted from
|
|
@@ -364,38 +366,47 @@ const shownOf = (model, commands) => {
|
|
|
364
366
|
return __foldkitBrandViewResult((isAnchored ? shifted(unshifted, model.cursorShift) : unshifted), "@gjermundgaraba-designs/pixel-paper/src/commandPalette/commandPalette.js#shownOf");
|
|
365
367
|
};
|
|
366
368
|
// What the view shows of the Model with `commands`: the open page while its command is
|
|
367
|
-
// listed, the rows its query finds
|
|
368
|
-
// while that row is listed, else the page's
|
|
369
|
-
//
|
|
369
|
+
// listed, the rows its query finds (on a tree page, with the rows they sit under), and the
|
|
370
|
+
// cursor's row: the row the reader moved it to while that row is listed, else the page's
|
|
371
|
+
// `start`, else its `current`, else the first row, these three only among the rows the query
|
|
372
|
+
// finds; none when nothing is listed.
|
|
370
373
|
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~
|
|
374
|
+
const maybePage = Option.flatMap(model.maybePage, (state) => __foldkitBrandViewResult((pageOf(commands, state)), "@gjermundgaraba-designs/pixel-paper/src/commandPalette/commandPalette.js#anonymous~30"));
|
|
372
375
|
return __foldkitBrandViewResult((Option.match(maybePage, {
|
|
373
376
|
onNone: () => {
|
|
374
377
|
const rows = findCommands(commands, model.query);
|
|
375
|
-
return __foldkitBrandViewResult(({
|
|
378
|
+
return __foldkitBrandViewResult(({
|
|
379
|
+
maybePage,
|
|
380
|
+
rows,
|
|
381
|
+
contextIds: HashSet.empty(),
|
|
382
|
+
maybeCursorId: pipe(Option.filter(model.maybeCursor, (rowId) => __foldkitBrandViewResult((isListed(rows, rowId)), "@gjermundgaraba-designs/pixel-paper/src/commandPalette/commandPalette.js#anonymous~31")), Option.orElse(() => __foldkitBrandViewResult((firstId(rows)), "@gjermundgaraba-designs/pixel-paper/src/commandPalette/commandPalette.js#anonymous~32"))),
|
|
383
|
+
}), "@gjermundgaraba-designs/pixel-paper/src/commandPalette/commandPalette.js#onNone~14");
|
|
376
384
|
},
|
|
377
385
|
onSome: ({ page, state }) => {
|
|
378
|
-
const rows =
|
|
386
|
+
const { rows, found } = pageChoices(page, state.query);
|
|
387
|
+
const idsOf = (choices) => __foldkitBrandViewResult((HashSet.fromIterable(Array.map(choices, ({ id }) => __foldkitBrandViewResult((id), "@gjermundgaraba-designs/pixel-paper/src/commandPalette/commandPalette.js#anonymous~33")))), "@gjermundgaraba-designs/pixel-paper/src/commandPalette/commandPalette.js#idsOf");
|
|
379
388
|
return __foldkitBrandViewResult(({
|
|
380
389
|
maybePage,
|
|
381
390
|
rows,
|
|
382
|
-
|
|
383
|
-
|
|
391
|
+
contextIds: HashSet.difference(idsOf(rows), idsOf(found)),
|
|
392
|
+
maybeCursorId: pipe(Option.filter(state.maybeCursor, (rowId) => __foldkitBrandViewResult((isListed(rows, rowId)), "@gjermundgaraba-designs/pixel-paper/src/commandPalette/commandPalette.js#anonymous~34")), Option.orElse(() => __foldkitBrandViewResult((firstListed(found, [
|
|
384
393
|
Option.fromNullishOr(page.start),
|
|
385
394
|
Option.fromNullishOr(page.current),
|
|
386
|
-
]),
|
|
395
|
+
])), "@gjermundgaraba-designs/pixel-paper/src/commandPalette/commandPalette.js#anonymous~35")), Option.orElse(() => __foldkitBrandViewResult((firstId(found)), "@gjermundgaraba-designs/pixel-paper/src/commandPalette/commandPalette.js#anonymous~36"))),
|
|
387
396
|
}), "@gjermundgaraba-designs/pixel-paper/src/commandPalette/commandPalette.js#onSome~13");
|
|
388
397
|
},
|
|
389
398
|
})), "@gjermundgaraba-designs/pixel-paper/src/commandPalette/commandPalette.js#unshiftedOf");
|
|
390
399
|
};
|
|
391
400
|
/** 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
|
|
393
|
-
* when nothing is listed. Compare it before and after the caller's commands change to know
|
|
401
|
+
* to while that row is listed, else the page's `start` or `current` while the query finds it,
|
|
402
|
+
* else the first row the query finds; none when nothing is listed. Compare it before and after the caller's commands change to know
|
|
394
403
|
* when the cursor fell to another row (its row left) and should be scrolled into view with
|
|
395
404
|
* `ScrollToCursor`; a cursor whose row is still listed keeps it, and the list its scroll. */
|
|
396
405
|
export const maybeCursorId = (model, commands) => __foldkitBrandViewResult((shownOf(model, commands).maybeCursorId), "@gjermundgaraba-designs/pixel-paper/src/commandPalette/commandPalette.js#maybeCursorId");
|
|
397
406
|
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~
|
|
407
|
+
const isCurrentRow = (shown, row) => __foldkitBrandViewResult((Option.exists(shown.maybePage, ({ page }) => __foldkitBrandViewResult((page.current === row.id), "@gjermundgaraba-designs/pixel-paper/src/commandPalette/commandPalette.js#anonymous~37"))), "@gjermundgaraba-designs/pixel-paper/src/commandPalette/commandPalette.js#isCurrentRow");
|
|
408
|
+
const isContextRow = (shown, row) => __foldkitBrandViewResult((HashSet.has(shown.contextIds, row.id)), "@gjermundgaraba-designs/pixel-paper/src/commandPalette/commandPalette.js#isContextRow");
|
|
409
|
+
const levelOf = (row) => __foldkitBrandViewResult(("level" in row ? Option.fromNullishOr(row.level) : Option.none()), "@gjermundgaraba-designs/pixel-paper/src/commandPalette/commandPalette.js#levelOf");
|
|
399
410
|
// What a row is as this render shows it, which Enter acts on as a click would.
|
|
400
411
|
const shownRowFor = (shown, row) => (__foldkitBrandViewResult(({
|
|
401
412
|
id: row.id,
|
|
@@ -406,14 +417,14 @@ const shownRowFor = (shown, row) => (__foldkitBrandViewResult(({
|
|
|
406
417
|
// shows them (also for a page whose command left).
|
|
407
418
|
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
419
|
// 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~
|
|
420
|
+
const shownPageIdOf = (shown) => __foldkitBrandViewResult((Option.map(shown.maybePage, ({ commandId }) => __foldkitBrandViewResult((commandId), "@gjermundgaraba-designs/pixel-paper/src/commandPalette/commandPalette.js#anonymous~38"))), "@gjermundgaraba-designs/pixel-paper/src/commandPalette/commandPalette.js#shownPageIdOf");
|
|
410
421
|
// NOTE: the field's keys that act on the rows are taken here, where the rows are known, as a
|
|
411
422
|
// Mount on the document cannot know them: ↑, ↓ and Enter carry what this render shows (see
|
|
412
423
|
// `Model`), and each is taken from the field (its default prevented). A modified key and every
|
|
413
424
|
// key while a pick is out are left to the field.
|
|
414
425
|
const fieldKeyDown = (model, shown) => __foldkitBrandViewResult(((key, modifiers) => {
|
|
415
426
|
const list = {
|
|
416
|
-
rows: Array.map(shown.rows, (row) => __foldkitBrandViewResult((shownRowFor(shown, row)), "@gjermundgaraba-designs/pixel-paper/src/commandPalette/commandPalette.js#anonymous~
|
|
427
|
+
rows: Array.map(shown.rows, (row) => __foldkitBrandViewResult((shownRowFor(shown, row)), "@gjermundgaraba-designs/pixel-paper/src/commandPalette/commandPalette.js#anonymous~40")),
|
|
417
428
|
maybeShownCursor: shown.maybeCursorId,
|
|
418
429
|
maybeShownPage: shownPageIdOf(shown),
|
|
419
430
|
shownQuery: shownQueryOf(model, shown),
|
|
@@ -421,7 +432,7 @@ const fieldKeyDown = (model, shown) => __foldkitBrandViewResult(((key, modifiers
|
|
|
421
432
|
};
|
|
422
433
|
return __foldkitBrandViewResult((isPicking(model) || !isPlain(modifiers)
|
|
423
434
|
? 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~
|
|
435
|
+
: 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~41")), Match.when("Enter", () => __foldkitBrandViewResult((Option.some(Message.PressedEnter(list))), "@gjermundgaraba-designs/pixel-paper/src/commandPalette/commandPalette.js#anonymous~42")), Match.orElse(() => __foldkitBrandViewResult((Option.none()), "@gjermundgaraba-designs/pixel-paper/src/commandPalette/commandPalette.js#anonymous~43")))), "@gjermundgaraba-designs/pixel-paper/src/commandPalette/commandPalette.js#anonymous~39");
|
|
425
436
|
}), "@gjermundgaraba-designs/pixel-paper/src/commandPalette/commandPalette.js#fieldKeyDown");
|
|
426
437
|
const optionId = (paletteId, index) => __foldkitBrandViewResult((`${paletteId}-option-${index}`), "@gjermundgaraba-designs/pixel-paper/src/commandPalette/commandPalette.js#optionId");
|
|
427
438
|
const NOTHING_FOUND = "nothing matches · try fewer letters";
|
|
@@ -436,7 +447,10 @@ const rowPartsView = (model, shown, row, h) => {
|
|
|
436
447
|
...(row.glyph === undefined
|
|
437
448
|
? []
|
|
438
449
|
: [h.span([h.Class("glyph")], [Glyph.view({ name: row.glyph }, h)])]),
|
|
439
|
-
h.span([h.Class("palette-label")], [
|
|
450
|
+
h.span([h.Class("palette-label")], [
|
|
451
|
+
row.label,
|
|
452
|
+
...(isContextRow(shown, row) ? [h.span([h.Class("sr-only")], [" (context)"])] : []),
|
|
453
|
+
]),
|
|
440
454
|
...(Option.contains(model.maybePicking, row.id)
|
|
441
455
|
? [h.span([h.Class("cursor"), h.AriaHidden(true)])]
|
|
442
456
|
: []),
|
|
@@ -454,41 +468,49 @@ const rowView = (model, shown, row, index, h) => {
|
|
|
454
468
|
...(shownRow.isHeld ? [h.AriaDisabled(true)] : []),
|
|
455
469
|
h.DataAttribute(ROW_ATTRIBUTE, row.id),
|
|
456
470
|
...(shownRow.hasPage ? [h.DataAttribute(OPENS_PAGE_ATTRIBUTE, "")] : []),
|
|
457
|
-
h.Class(
|
|
471
|
+
h.Class(Array.join([
|
|
472
|
+
"palette-row",
|
|
473
|
+
...(row.isAuthored === true ? ["authored"] : []),
|
|
474
|
+
...(isContextRow(shown, row) ? ["context"] : []),
|
|
475
|
+
], " ")),
|
|
476
|
+
...Option.match(levelOf(row), {
|
|
477
|
+
onNone: () => __foldkitBrandViewResult(([]), "@gjermundgaraba-designs/pixel-paper/src/commandPalette/commandPalette.js#onNone~16"),
|
|
478
|
+
onSome: (level) => __foldkitBrandViewResult(([h.Style({ "--palette-level": `${level}` })]), "@gjermundgaraba-designs/pixel-paper/src/commandPalette/commandPalette.js#onSome~15"),
|
|
479
|
+
}),
|
|
458
480
|
...(shownRow.isHeld || isPicking(model)
|
|
459
481
|
? []
|
|
460
482
|
: [h.OnClick(Message.ClickedRow({ row: shownRow, maybeShownPage: shownPageIdOf(shown) }))]),
|
|
461
483
|
], rowPartsView(model, shown, row, h))), "@gjermundgaraba-designs/pixel-paper/src/commandPalette/commandPalette.js#rowView");
|
|
462
484
|
};
|
|
463
485
|
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~
|
|
486
|
+
const indexed = Array.map(shown.rows, (row, index) => (__foldkitBrandViewResult(({ row, index }), "@gjermundgaraba-designs/pixel-paper/src/commandPalette/commandPalette.js#anonymous~44")));
|
|
465
487
|
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~
|
|
488
|
+
onSome: () => __foldkitBrandViewResult((Array.map(indexed, ({ row, index }) => __foldkitBrandViewResult((rowView(model, shown, row, index, h)), "@gjermundgaraba-designs/pixel-paper/src/commandPalette/commandPalette.js#anonymous~45"))), "@gjermundgaraba-designs/pixel-paper/src/commandPalette/commandPalette.js#onSome~16"),
|
|
467
489
|
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~
|
|
490
|
+
const groups = Array.dedupe(Array.map(shown.rows, (row) => (__foldkitBrandViewResult((isCommand(row) ? row.group : ""), "@gjermundgaraba-designs/pixel-paper/src/commandPalette/commandPalette.js#anonymous~46"))));
|
|
469
491
|
return __foldkitBrandViewResult((Array.map(groups, (group, groupIndex) => {
|
|
470
492
|
const headingId = `${id(model)}-group-${groupIndex}`;
|
|
471
493
|
return __foldkitBrandViewResult((h.keyed("div")(`group-${group}`, [h.Role("group"), h.AriaLabelledBy(headingId)], [
|
|
472
494
|
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~
|
|
474
|
-
])), "@gjermundgaraba-designs/pixel-paper/src/commandPalette/commandPalette.js#anonymous~
|
|
475
|
-
})), "@gjermundgaraba-designs/pixel-paper/src/commandPalette/commandPalette.js#onNone~
|
|
495
|
+
...Array.map(Array.filter(indexed, ({ row }) => __foldkitBrandViewResult((isCommand(row) && row.group === group), "@gjermundgaraba-designs/pixel-paper/src/commandPalette/commandPalette.js#anonymous~48")), ({ row, index }) => __foldkitBrandViewResult((rowView(model, shown, row, index, h)), "@gjermundgaraba-designs/pixel-paper/src/commandPalette/commandPalette.js#anonymous~49")),
|
|
496
|
+
])), "@gjermundgaraba-designs/pixel-paper/src/commandPalette/commandPalette.js#anonymous~47");
|
|
497
|
+
})), "@gjermundgaraba-designs/pixel-paper/src/commandPalette/commandPalette.js#onNone~17");
|
|
476
498
|
},
|
|
477
499
|
})), "@gjermundgaraba-designs/pixel-paper/src/commandPalette/commandPalette.js#groupedRows");
|
|
478
500
|
};
|
|
479
501
|
const comboboxAttributes = (model, shown, noun, h) => {
|
|
480
502
|
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~
|
|
503
|
+
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~51"))), "@gjermundgaraba-designs/pixel-paper/src/commandPalette/commandPalette.js#anonymous~50"));
|
|
482
504
|
return __foldkitBrandViewResult(([
|
|
483
505
|
h.OnKeyDownPreventDefault(fieldKeyDown(model, shown)),
|
|
484
506
|
h.Role("combobox"),
|
|
485
507
|
h.AriaExpanded(true),
|
|
486
508
|
h.AriaControls(listIdOf(paletteId)),
|
|
487
509
|
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~
|
|
510
|
+
...Option.toArray(Option.map(maybeCursorIndex, (index) => __foldkitBrandViewResult((h.AriaActiveDescendant(optionId(paletteId, index))), "@gjermundgaraba-designs/pixel-paper/src/commandPalette/commandPalette.js#anonymous~52"))),
|
|
489
511
|
h.AriaLabel(Option.match(shown.maybePage, {
|
|
490
|
-
onNone: () => __foldkitBrandViewResult((`Command or ${noun}`), "@gjermundgaraba-designs/pixel-paper/src/commandPalette/commandPalette.js#onNone~
|
|
491
|
-
onSome: ({ page }) => __foldkitBrandViewResult((page.title), "@gjermundgaraba-designs/pixel-paper/src/commandPalette/commandPalette.js#onSome~
|
|
512
|
+
onNone: () => __foldkitBrandViewResult((`Command or ${noun}`), "@gjermundgaraba-designs/pixel-paper/src/commandPalette/commandPalette.js#onNone~18"),
|
|
513
|
+
onSome: ({ page }) => __foldkitBrandViewResult((page.title), "@gjermundgaraba-designs/pixel-paper/src/commandPalette/commandPalette.js#onSome~17"),
|
|
492
514
|
})),
|
|
493
515
|
h.Autocomplete("off"),
|
|
494
516
|
h.DataAttribute(FIELD_ATTRIBUTE, ""),
|
|
@@ -502,8 +524,8 @@ const fieldView = (model, shown, noun, render, h) => {
|
|
|
502
524
|
onInput: (value) => __foldkitBrandViewResult((Message.UpdatedQuery({ value, maybeShownPage })), "@gjermundgaraba-designs/pixel-paper/src/commandPalette/commandPalette.js#onInput"),
|
|
503
525
|
isReadOnly: isPicking(model),
|
|
504
526
|
placeholder: Option.match(shown.maybePage, {
|
|
505
|
-
onNone: () => __foldkitBrandViewResult((`type a command or a ${noun}`), "@gjermundgaraba-designs/pixel-paper/src/commandPalette/commandPalette.js#onNone~
|
|
506
|
-
onSome: ({ page }) => __foldkitBrandViewResult((page.find ?? "type to narrow"), "@gjermundgaraba-designs/pixel-paper/src/commandPalette/commandPalette.js#onSome~
|
|
527
|
+
onNone: () => __foldkitBrandViewResult((`type a command or a ${noun}`), "@gjermundgaraba-designs/pixel-paper/src/commandPalette/commandPalette.js#onNone~19"),
|
|
528
|
+
onSome: ({ page }) => __foldkitBrandViewResult((page.find ?? "type to narrow"), "@gjermundgaraba-designs/pixel-paper/src/commandPalette/commandPalette.js#onSome~18"),
|
|
507
529
|
}),
|
|
508
530
|
toView: (groups) => __foldkitBrandViewResult((h.input([
|
|
509
531
|
...render.initialFocus,
|
|
@@ -519,17 +541,17 @@ const headView = (model, shown, scope, field, h) => __foldkitBrandViewResult((h.
|
|
|
519
541
|
isQuiet: true,
|
|
520
542
|
isDisabled: isPicking(model),
|
|
521
543
|
onClick: Message.ClickedBack(),
|
|
522
|
-
}, h)), "@gjermundgaraba-designs/pixel-paper/src/commandPalette/commandPalette.js#anonymous~
|
|
544
|
+
}, h)), "@gjermundgaraba-designs/pixel-paper/src/commandPalette/commandPalette.js#anonymous~53"))),
|
|
523
545
|
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~
|
|
546
|
+
...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~54"))),
|
|
525
547
|
field,
|
|
526
548
|
])), "@gjermundgaraba-designs/pixel-paper/src/commandPalette/commandPalette.js#headView");
|
|
527
549
|
const listView = (model, shown, h) => __foldkitBrandViewResult((h.div([
|
|
528
550
|
h.Id(listIdOf(id(model))),
|
|
529
551
|
h.Role("listbox"),
|
|
530
552
|
h.AriaLabel(Option.match(shown.maybePage, {
|
|
531
|
-
onNone: () => __foldkitBrandViewResult(("Results"), "@gjermundgaraba-designs/pixel-paper/src/commandPalette/commandPalette.js#onNone~
|
|
532
|
-
onSome: ({ page }) => __foldkitBrandViewResult((page.title), "@gjermundgaraba-designs/pixel-paper/src/commandPalette/commandPalette.js#onSome~
|
|
553
|
+
onNone: () => __foldkitBrandViewResult(("Results"), "@gjermundgaraba-designs/pixel-paper/src/commandPalette/commandPalette.js#onNone~20"),
|
|
554
|
+
onSome: ({ page }) => __foldkitBrandViewResult((page.title), "@gjermundgaraba-designs/pixel-paper/src/commandPalette/commandPalette.js#onSome~19"),
|
|
533
555
|
})),
|
|
534
556
|
...(isPicking(model) ? [h.AriaBusy(true)] : []),
|
|
535
557
|
h.Class("palette-list"),
|
|
@@ -542,13 +564,15 @@ const listView = (model, shown, h) => __foldkitBrandViewResult((h.div([
|
|
|
542
564
|
// count.
|
|
543
565
|
const countView = (model, shown, h) => {
|
|
544
566
|
const shownQuery = shownQueryOf(model, shown);
|
|
545
|
-
return __foldkitBrandViewResult((h.div([h.Role("status"), h.Class("sr-only")], String.isEmpty(shownQuery)
|
|
567
|
+
return __foldkitBrandViewResult((h.div([h.Role("status"), h.Class("sr-only")], String.isEmpty(shownQuery)
|
|
568
|
+
? []
|
|
569
|
+
: [countWords(shown.rows.length - HashSet.size(shown.contextIds))])), "@gjermundgaraba-designs/pixel-paper/src/commandPalette/commandPalette.js#countView");
|
|
546
570
|
};
|
|
547
571
|
const cardView = (model, viewInputs, render, h) => {
|
|
548
|
-
const { commands, scope = "commands", noun = "title", toDetailView = () => __foldkitBrandViewResult(([]), "@gjermundgaraba-designs/pixel-paper/src/commandPalette/commandPalette.js#anonymous~
|
|
572
|
+
const { commands, scope = "commands", noun = "title", toDetailView = () => __foldkitBrandViewResult(([]), "@gjermundgaraba-designs/pixel-paper/src/commandPalette/commandPalette.js#anonymous~55") } = viewInputs;
|
|
549
573
|
const shown = shownOf(model, commands);
|
|
550
574
|
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~
|
|
575
|
+
...Option.toArray(Option.map(shownPageIdOf(shown), (commandId) => __foldkitBrandViewResult((h.DataAttribute(PAGE_ATTRIBUTE, commandId)), "@gjermundgaraba-designs/pixel-paper/src/commandPalette/commandPalette.js#anonymous~56"))),
|
|
552
576
|
h.OnMount(BindPaletteKeys()),
|
|
553
577
|
h.Class("card palette"),
|
|
554
578
|
], [
|
|
@@ -556,7 +580,7 @@ const cardView = (model, viewInputs, render, h) => {
|
|
|
556
580
|
...slotView("div", "palette-detail", toDetailView(), h),
|
|
557
581
|
listView(model, shown, h),
|
|
558
582
|
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~
|
|
583
|
+
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
584
|
])), "@gjermundgaraba-designs/pixel-paper/src/commandPalette/commandPalette.js#cardView");
|
|
561
585
|
};
|
|
562
586
|
/**
|
|
@@ -590,4 +614,4 @@ export const view = defineView((model, viewInputs, h) => __foldkitBrandViewResul
|
|
|
590
614
|
toView: (render) => __foldkitBrandViewResult((cardView(model, viewInputs, render, h)), "@gjermundgaraba-designs/pixel-paper/src/commandPalette/commandPalette.js#toView~2"),
|
|
591
615
|
},
|
|
592
616
|
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~
|
|
617
|
+
})), "@gjermundgaraba-designs/pixel-paper/src/commandPalette/commandPalette.js#anonymous~57"));
|
|
@@ -14,6 +14,9 @@ export type Choice = Readonly<{
|
|
|
14
14
|
isDisabled?: boolean;
|
|
15
15
|
/** Why it cannot run now (`"No item open"`), printed in place of the hint while held. */
|
|
16
16
|
reason?: string;
|
|
17
|
+
/** On a page, its depth in the page's tree, from 1 at the roots: it sits under the nearest
|
|
18
|
+
* choice before it one level up, and indents by its depth. A page without levels is flat. */
|
|
19
|
+
level?: number;
|
|
17
20
|
}>;
|
|
18
21
|
/** A page of choices a command opens in place (a status, a date, a destination). */
|
|
19
22
|
export type Page = Readonly<{
|
|
@@ -24,14 +27,14 @@ export type Page = Readonly<{
|
|
|
24
27
|
find?: string;
|
|
25
28
|
/** The choice in force now: tagged current, and not picked again. */
|
|
26
29
|
current?: string;
|
|
27
|
-
/** Where the cursor starts while the reader has not moved it: this choice while
|
|
28
|
-
*
|
|
29
|
-
* first a typed query finds). */
|
|
30
|
+
/** Where the cursor starts while the reader has not moved it: this choice while the query
|
|
31
|
+
* finds it, else `current` while the query finds it, else the first choice the query finds
|
|
32
|
+
* (the next status after the current one; the first a typed query finds). */
|
|
30
33
|
start?: string;
|
|
31
34
|
}>;
|
|
32
35
|
/** One command, under its group's heading. With a `page` it opens that page in place instead
|
|
33
36
|
* of being selected. */
|
|
34
|
-
export type Command = Choice & Readonly<{
|
|
37
|
+
export type Command = Omit<Choice, "level"> & Readonly<{
|
|
35
38
|
group: string;
|
|
36
39
|
page?: Page;
|
|
37
40
|
}>;
|
|
@@ -40,6 +43,13 @@ export declare const matches: (query: string, label: string) => boolean;
|
|
|
40
43
|
/** The commands the query finds, under their groups in the order the groups first appear,
|
|
41
44
|
* authored titles after the commands. With no query, authored titles wait. */
|
|
42
45
|
export declare const findCommands: (commands: ReadonlyArray<Command>, query: string) => ReadonlyArray<Command>;
|
|
43
|
-
/** The choices a page
|
|
46
|
+
/** The choices a page's query finds. */
|
|
44
47
|
export declare const findChoices: (page: Page, query: string) => ReadonlyArray<Choice>;
|
|
48
|
+
/** What a page shows for its query: the choices it finds, and the rows, which are those
|
|
49
|
+
* choices each with the choices it sits under in the page's tree, in the page's order. */
|
|
50
|
+
export type PageChoices = Readonly<{
|
|
51
|
+
rows: ReadonlyArray<Choice>;
|
|
52
|
+
found: ReadonlyArray<Choice>;
|
|
53
|
+
}>;
|
|
54
|
+
export declare const pageChoices: (page: Page, query: string) => PageChoices;
|
|
45
55
|
//# 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;
|
|
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;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;AAEF,wCAAwC;AACxC,eAAO,MAAM,WAAW,SAAU,IAAI,SAAS,MAAM,KAAG,aAAa,CAAC,MAAM,CAGF,CAAC;AAM3E;2FAC2F;AAC3F,MAAM,MAAM,WAAW,GAAG,QAAQ,CAAC;IACjC,IAAI,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;IAC5B,KAAK,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;CAC9B,CAAC,CAAC;AAEH,eAAO,MAAM,WAAW,SAAU,IAAI,SAAS,MAAM,KAAG,WAavD,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,22 @@ 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
|
|
24
|
+
/** The choices a page's query finds. */
|
|
25
25
|
export const findChoices = (page, query) => __foldkitBrandViewResult((String.isEmpty(String.trim(query))
|
|
26
26
|
? page.choices
|
|
27
27
|
: 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");
|
|
28
|
+
const WALK_START = { chain: [], keptIds: HashSet.empty() };
|
|
29
|
+
export const pageChoices = (page, query) => {
|
|
30
|
+
const found = findChoices(page, query);
|
|
31
|
+
const foundIds = HashSet.fromIterable(Array.map(found, ({ id }) => __foldkitBrandViewResult((id), "@gjermundgaraba-designs/pixel-paper/src/commandPalette/match.js#anonymous~10")));
|
|
32
|
+
const { keptIds } = Array.reduce(page.choices, WALK_START, ({ chain, keptIds }, choice) => {
|
|
33
|
+
const nextChain = Array.append(Array.take(chain, (choice.level ?? 1) - 1), choice);
|
|
34
|
+
return __foldkitBrandViewResult(({
|
|
35
|
+
chain: nextChain,
|
|
36
|
+
keptIds: HashSet.has(foundIds, choice.id)
|
|
37
|
+
? HashSet.union(keptIds, HashSet.fromIterable(Array.map(nextChain, ({ id }) => __foldkitBrandViewResult((id), "@gjermundgaraba-designs/pixel-paper/src/commandPalette/match.js#anonymous~12"))))
|
|
38
|
+
: keptIds,
|
|
39
|
+
}), "@gjermundgaraba-designs/pixel-paper/src/commandPalette/match.js#anonymous~11");
|
|
40
|
+
});
|
|
41
|
+
return __foldkitBrandViewResult(({ rows: Array.filter(page.choices, ({ id }) => __foldkitBrandViewResult((HashSet.has(keptIds, id)), "@gjermundgaraba-designs/pixel-paper/src/commandPalette/match.js#anonymous~13")), found }), "@gjermundgaraba-designs/pixel-paper/src/commandPalette/match.js#pageChoices");
|
|
42
|
+
};
|
package/package.json
CHANGED
|
@@ -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 {
|
|
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";
|
|
@@ -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,17 @@ 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
|
|
586
|
+
const isListed = (rows: ReadonlyArray<Choice | Command>, rowId: string): boolean =>
|
|
587
|
+
Array.some(rows, (row) => row.id === rowId);
|
|
588
|
+
|
|
589
|
+
const firstListed = (
|
|
585
590
|
rows: ReadonlyArray<Choice>,
|
|
586
591
|
candidates: ReadonlyArray<Option.Option<string>>,
|
|
587
592
|
): Option.Option<string> =>
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
() => Option.map(Array.head(rows), (row) => row.id),
|
|
593
|
-
);
|
|
593
|
+
Array.findFirst(Array.getSomes(candidates), (cursorId) => isListed(rows, cursorId));
|
|
594
|
+
|
|
595
|
+
const firstId = (rows: ReadonlyArray<Choice | Command>): Option.Option<string> =>
|
|
596
|
+
Option.map(Array.head(rows), (row) => row.id);
|
|
594
597
|
|
|
595
598
|
const shifted = (shown: Shown, shift: number): Shown => ({
|
|
596
599
|
...shown,
|
|
@@ -610,34 +613,51 @@ const shownOf = (model: Model, commands: ReadonlyArray<Command>): Shown => {
|
|
|
610
613
|
};
|
|
611
614
|
|
|
612
615
|
// What the view shows of the Model with `commands`: the open page while its command is
|
|
613
|
-
// listed, the rows its query finds
|
|
614
|
-
// while that row is listed, else the page's
|
|
615
|
-
//
|
|
616
|
+
// listed, the rows its query finds (on a tree page, with the rows they sit under), and the
|
|
617
|
+
// cursor's row: the row the reader moved it to while that row is listed, else the page's
|
|
618
|
+
// `start`, else its `current`, else the first row, these three only among the rows the query
|
|
619
|
+
// finds; none when nothing is listed.
|
|
616
620
|
const unshiftedOf = (model: Model, commands: ReadonlyArray<Command>): Shown => {
|
|
617
621
|
const maybePage = Option.flatMap(model.maybePage, (state) => pageOf(commands, state));
|
|
618
622
|
return Option.match(maybePage, {
|
|
619
623
|
onNone: () => {
|
|
620
624
|
const rows = findCommands(commands, model.query);
|
|
621
|
-
return {
|
|
625
|
+
return {
|
|
626
|
+
maybePage,
|
|
627
|
+
rows,
|
|
628
|
+
contextIds: HashSet.empty(),
|
|
629
|
+
maybeCursorId: pipe(
|
|
630
|
+
Option.filter(model.maybeCursor, (rowId) => isListed(rows, rowId)),
|
|
631
|
+
Option.orElse(() => firstId(rows)),
|
|
632
|
+
),
|
|
633
|
+
};
|
|
622
634
|
},
|
|
623
635
|
onSome: ({ page, state }) => {
|
|
624
|
-
const rows =
|
|
636
|
+
const { rows, found } = pageChoices(page, state.query);
|
|
637
|
+
const idsOf = (choices: ReadonlyArray<Choice>) =>
|
|
638
|
+
HashSet.fromIterable(Array.map(choices, ({ id }) => id));
|
|
625
639
|
return {
|
|
626
640
|
maybePage,
|
|
627
641
|
rows,
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
Option.
|
|
631
|
-
Option.
|
|
632
|
-
|
|
642
|
+
contextIds: HashSet.difference(idsOf(rows), idsOf(found)),
|
|
643
|
+
maybeCursorId: pipe(
|
|
644
|
+
Option.filter(state.maybeCursor, (rowId) => isListed(rows, rowId)),
|
|
645
|
+
Option.orElse(() =>
|
|
646
|
+
firstListed(found, [
|
|
647
|
+
Option.fromNullishOr(page.start),
|
|
648
|
+
Option.fromNullishOr(page.current),
|
|
649
|
+
]),
|
|
650
|
+
),
|
|
651
|
+
Option.orElse(() => firstId(found)),
|
|
652
|
+
),
|
|
633
653
|
};
|
|
634
654
|
},
|
|
635
655
|
});
|
|
636
656
|
};
|
|
637
657
|
|
|
638
658
|
/** 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
|
|
640
|
-
* when nothing is listed. Compare it before and after the caller's commands change to know
|
|
659
|
+
* to while that row is listed, else the page's `start` or `current` while the query finds it,
|
|
660
|
+
* else the first row the query finds; none when nothing is listed. Compare it before and after the caller's commands change to know
|
|
641
661
|
* when the cursor fell to another row (its row left) and should be scrolled into view with
|
|
642
662
|
* `ScrollToCursor`; a cursor whose row is still listed keeps it, and the list its scroll. */
|
|
643
663
|
export const maybeCursorId = (
|
|
@@ -650,6 +670,12 @@ const isCommand = (row: Choice | Command): row is Command => "group" in row;
|
|
|
650
670
|
const isCurrentRow = (shown: Shown, row: Choice | Command): boolean =>
|
|
651
671
|
Option.exists(shown.maybePage, ({ page }) => page.current === row.id);
|
|
652
672
|
|
|
673
|
+
const isContextRow = (shown: Shown, row: Choice | Command): boolean =>
|
|
674
|
+
HashSet.has(shown.contextIds, row.id);
|
|
675
|
+
|
|
676
|
+
const levelOf = (row: Choice | Command): Option.Option<number> =>
|
|
677
|
+
"level" in row ? Option.fromNullishOr(row.level) : Option.none();
|
|
678
|
+
|
|
653
679
|
// What a row is as this render shows it, which Enter acts on as a click would.
|
|
654
680
|
const shownRowFor = (shown: Shown, row: Choice | Command): ShownRow => ({
|
|
655
681
|
id: row.id,
|
|
@@ -718,7 +744,13 @@ const rowPartsView = (
|
|
|
718
744
|
...(row.glyph === undefined
|
|
719
745
|
? []
|
|
720
746
|
: [h.span([h.Class("glyph")], [Glyph.view({ name: row.glyph }, h)])]),
|
|
721
|
-
h.span(
|
|
747
|
+
h.span(
|
|
748
|
+
[h.Class("palette-label")],
|
|
749
|
+
[
|
|
750
|
+
row.label,
|
|
751
|
+
...(isContextRow(shown, row) ? [h.span([h.Class("sr-only")], [" (context)"])] : []),
|
|
752
|
+
],
|
|
753
|
+
),
|
|
722
754
|
...(Option.contains(model.maybePicking, row.id)
|
|
723
755
|
? [h.span([h.Class("cursor"), h.AriaHidden(true)])]
|
|
724
756
|
: []),
|
|
@@ -745,7 +777,20 @@ const rowView = (
|
|
|
745
777
|
...(shownRow.isHeld ? [h.AriaDisabled(true)] : []),
|
|
746
778
|
h.DataAttribute(ROW_ATTRIBUTE, row.id),
|
|
747
779
|
...(shownRow.hasPage ? [h.DataAttribute(OPENS_PAGE_ATTRIBUTE, "")] : []),
|
|
748
|
-
h.Class(
|
|
780
|
+
h.Class(
|
|
781
|
+
Array.join(
|
|
782
|
+
[
|
|
783
|
+
"palette-row",
|
|
784
|
+
...(row.isAuthored === true ? ["authored"] : []),
|
|
785
|
+
...(isContextRow(shown, row) ? ["context"] : []),
|
|
786
|
+
],
|
|
787
|
+
" ",
|
|
788
|
+
),
|
|
789
|
+
),
|
|
790
|
+
...Option.match(levelOf(row), {
|
|
791
|
+
onNone: () => [],
|
|
792
|
+
onSome: (level) => [h.Style({ "--palette-level": `${level}` })],
|
|
793
|
+
}),
|
|
749
794
|
...(shownRow.isHeld || isPicking(model)
|
|
750
795
|
? []
|
|
751
796
|
: [h.OnClick(Message.ClickedRow({ row: shownRow, maybeShownPage: shownPageIdOf(shown) }))]),
|
|
@@ -898,7 +943,9 @@ const countView = (model: Model, shown: Shown, h: HtmlBuilder<Message>): Html =>
|
|
|
898
943
|
const shownQuery = shownQueryOf(model, shown);
|
|
899
944
|
return h.div(
|
|
900
945
|
[h.Role("status"), h.Class("sr-only")],
|
|
901
|
-
String.isEmpty(shownQuery)
|
|
946
|
+
String.isEmpty(shownQuery)
|
|
947
|
+
? []
|
|
948
|
+
: [countWords(shown.rows.length - HashSet.size(shown.contextIds))],
|
|
902
949
|
);
|
|
903
950
|
};
|
|
904
951
|
|
|
@@ -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
|
|
34
|
-
*
|
|
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,34 @@ export const findCommands = (
|
|
|
75
78
|
return Array.sort(ordered, Order.mapInput(Order.Number, groupIndex));
|
|
76
79
|
};
|
|
77
80
|
|
|
78
|
-
/** The choices a page
|
|
81
|
+
/** The choices a page's query finds. */
|
|
79
82
|
export const findChoices = (page: Page, query: string): ReadonlyArray<Choice> =>
|
|
80
83
|
String.isEmpty(String.trim(query))
|
|
81
84
|
? page.choices
|
|
82
85
|
: Array.filter(page.choices, (choice) => matches(query, choice.label));
|
|
86
|
+
|
|
87
|
+
type Walk = Readonly<{ chain: ReadonlyArray<Choice>; keptIds: HashSet.HashSet<string> }>;
|
|
88
|
+
|
|
89
|
+
const WALK_START: Walk = { chain: [], keptIds: HashSet.empty() };
|
|
90
|
+
|
|
91
|
+
/** What a page shows for its query: the choices it finds, and the rows, which are those
|
|
92
|
+
* choices each with the choices it sits under in the page's tree, in the page's order. */
|
|
93
|
+
export type PageChoices = Readonly<{
|
|
94
|
+
rows: ReadonlyArray<Choice>;
|
|
95
|
+
found: ReadonlyArray<Choice>;
|
|
96
|
+
}>;
|
|
97
|
+
|
|
98
|
+
export const pageChoices = (page: Page, query: string): PageChoices => {
|
|
99
|
+
const found = findChoices(page, query);
|
|
100
|
+
const foundIds = HashSet.fromIterable(Array.map(found, ({ id }) => id));
|
|
101
|
+
const { keptIds } = Array.reduce(page.choices, WALK_START, ({ chain, keptIds }, choice): Walk => {
|
|
102
|
+
const nextChain = Array.append(Array.take(chain, (choice.level ?? 1) - 1), choice);
|
|
103
|
+
return {
|
|
104
|
+
chain: nextChain,
|
|
105
|
+
keptIds: HashSet.has(foundIds, choice.id)
|
|
106
|
+
? HashSet.union(keptIds, HashSet.fromIterable(Array.map(nextChain, ({ id }) => id)))
|
|
107
|
+
: keptIds,
|
|
108
|
+
};
|
|
109
|
+
});
|
|
110
|
+
return { rows: Array.filter(page.choices, ({ id }) => HashSet.has(keptIds, id)), found };
|
|
111
|
+
};
|
package/src/pixel-paper.css
CHANGED
|
@@ -2893,7 +2893,9 @@ dialog.veil:focus {
|
|
|
2893
2893
|
align-items: center;
|
|
2894
2894
|
gap: 10px;
|
|
2895
2895
|
width: 100%;
|
|
2896
|
-
|
|
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);
|