@3sln/nook 0.1.0 → 0.2.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/README.md CHANGED
@@ -12,7 +12,7 @@ Import `@3sln/nook` once in your browser entry point, then add one `<nook-dock>`
12
12
 
13
13
  ```html
14
14
  <nook-dock corner="bottom-right">
15
- <nook-action id="share" label="Share page"></nook-action>
15
+ <nook-item id="share" label="Share page"></nook-item>
16
16
  </nook-dock>
17
17
  ```
18
18
 
@@ -25,7 +25,7 @@ dock.addEventListener("nook-action", ({ detail }) => {
25
25
  });
26
26
  ```
27
27
 
28
- Feedback is disabled until its submit hook is wired. Commands and Settings appear only when their definitions are present; More appears only when entries overflow. Configuration belongs to each dock, through attributes, properties, and inherited CSS custom properties; there is no global configuration registry.
28
+ Feedback is disabled until its submit hook is wired. Commands and Settings appear only when their capabilities are present. Tap and held-swipe picking share one root dial. Configuration belongs to each dock, through attributes, properties, and inherited CSS custom properties; there is no global configuration registry.
29
29
 
30
30
  ## Try the demo
31
31
 
@@ -47,13 +47,19 @@ import "@3sln/nook";
47
47
 
48
48
  ```html
49
49
  <nook-dock corner="bottom-right">
50
- <nook-action id="share" label="Share page" keybind="Alt+S">
50
+ <nook-command
51
+ id="share-page"
52
+ name="Share page"
53
+ description="Share the current page"
54
+ keybind="Alt+S"
55
+ ></nook-command>
56
+ <nook-item id="share" command="share-page">
51
57
  <template slot="icon">
52
58
  <svg viewBox="0 0 24 24" fill="none" stroke="currentColor">
53
59
  <use href="#nook-icon-link"></use>
54
60
  </svg>
55
61
  </template>
56
- </nook-action>
62
+ </nook-item>
57
63
 
58
64
  <nook-menu label="Resources">
59
65
  <nook-panel label="Page guide">
@@ -81,38 +87,41 @@ import "@3sln/nook";
81
87
  </nook-dock>
82
88
  ```
83
89
 
84
- Each dock owns only its descendants; elements from nested docks are excluded. Use one dock per app for predictable document-level shortcuts. Declarative `nook-action`, `nook-menu`, `nook-panel`, and `nook-setting` elements are hidden by their own shadow styles. Optionally include `nook-action,nook-menu,nook-panel,nook-setting,nook-command,nook-arg { display:none }` in the host stylesheet to hide definitions before JavaScript loads.
90
+ Each dock owns only its descendants; elements from nested docks are excluded. Use one dock per app for predictable document-level shortcuts. Declarative `nook-item` (`nook-action` is a legacy alias), `nook-menu`, `nook-panel`, and `nook-setting` elements are hidden by their own shadow styles. Optionally include `nook-item,nook-action,nook-menu,nook-panel,nook-setting,nook-command,nook-arg { display:none }` in the host stylesheet to hide definitions before JavaScript loads.
85
91
 
86
92
  - `nook-dock`: the corner handle and composition root.
87
- - `nook-action`: emits intent for the embedding app to handle.
93
+ - `nook-item`: a leaf choice that invokes a referenced command or emits a custom selection event.
94
+ - `nook-action`: the compatibility alias; existing `nook-action` events and standalone keybindings continue to work.
88
95
  - `nook-menu`: a nested group of actions, panels, or menus.
89
96
  - `nook-panel`: an overlay surface for arbitrary application UI.
90
97
  - `nook-command`: a searchable command with optional declarative arguments.
91
98
  - `nook-arg`: a command argument definition.
92
99
  - `nook-setting`: an inline control. `type="toggle"`, `range`, `enum` (`select` is a compatibility alias), or native input types such as `color`, `number`, and `text`. `min`, `max`, `step`, and `value` reflect to the control. Toggle values use the strings `true` and `false`; the JS `value` property returns a boolean for toggles and a string for other controls.
93
100
 
94
- Top-level actions, panels, and menus fill the root automatically. When they exceed the available row width, Nook reserves the last slot for **More** and moves lower-priority entries into an overflow menu. The default root limit is five items, including More; `--nook-root-max-items` can change that limit. Viewport width and the configured item size/gap can reduce it further. When everything fits, More disappears.
101
+ Tap/click opens the root dial; a held swipe previews the same persistent buttons and chooses one on release. Up to six icon choices follow a curved arc. **Full menu** owns the horizontal endpoint and **Settings** owns the vertical endpoint when settings are available. The four inner positions are filled by prioritized contextual actions, panels, menus, Feedback, and Commands. Full menu always contains the complete grouped list, including choices shown in the dial. Smaller visible areas show fewer extras without moving or replacing the directional choices.
102
+
103
+ The first-use Quick tour temporarily occupies an extra slot and has a small attention badge. It is also reachable from Full menu if the dial cannot fit it. It never intercepts normal menu use. Completing it removes the choice and releases its slot for another contextual item. `--nook-root-max-items` can reduce the dial limit, up to a hard maximum of six.
95
104
 
96
105
  ```html
97
- <nook-action label="Save" priority="100" group="Workspace"></nook-action>
98
- <nook-action label="Share" priority="50" group="Sharing"></nook-action>
106
+ <nook-item label="Save" priority="100" group="Workspace"></nook-item>
107
+ <nook-item label="Share" priority="50" group="Sharing"></nook-item>
99
108
  <nook-menu label="Export" priority="10" group="Workspace">
100
- <nook-action label="PDF"></nook-action>
101
- <nook-action label="Markdown"></nook-action>
109
+ <nook-item label="PDF"></nook-item>
110
+ <nook-item label="Markdown"></nook-item>
102
111
  </nook-menu>
103
112
  ```
104
113
 
105
- Higher numeric `priority` values win root slots. Equal priorities keep DOM order. `item.priority` and `item.group` reflect their attributes and update live. Groups organize overflow into labeled sections; they do not create extra navigation levels. Groups appear in order of their highest-priority member, and entries retain priority order within each group. Ungrouped entries appear under Other when named groups are present. There are no duplicate root entries in More.
114
+ Higher numeric `priority` values win the available extra dial slots. Equal priorities keep DOM order. `item.priority` and `item.group` reflect their attributes and update live. Groups organize the Full menu list into labeled sections; they do not create extra navigation levels. Groups appear in order of their highest-priority member, and entries retain priority order within each group. Ungrouped entries appear under Other when named groups are present. There is one projected choice per source in each view; opening or closing views does not recreate controls.
106
115
 
107
- For compatibility, `root`/`.root = true` makes a nested definition eligible for the root and supplies priority 100 when no explicit priority is set. It is a preference, not a guarantee against overflow. Top-level definitions need no `root` attribute. Root settings remain inline controls; other settings populate the automatic Settings launcher. Individual commands remain in the Commands palette.
116
+ For compatibility, `root`/`.root = true` makes a nested definition eligible for the root and supplies priority 100 when no explicit priority is set. It is a preference, not a guarantee against overflow. Top-level definitions need no `root` attribute. All `nook-setting` controls live in Settings, including legacy settings carrying `root`. Individual commands remain in the Commands palette.
108
117
 
109
- Automatic Feedback, Settings, and Commands launchers participate in allocation too. Their default priorities are 30, 20, and 10. Override them per dock:
118
+ Feedback and Commands compete for extra slots with default priorities 30 and 10. Settings is reserved and cannot be displaced by item priority. Override them per dock:
110
119
 
111
120
  ```js
112
121
  dock.priorities = { feedback: 100, settings: 50, commands: 25 };
113
122
  ```
114
123
 
115
- Reassign the object to apply changes. Their shortcuts remain available when their launcher moves into More. Priority reflects app-provided configuration; Nook does not yet provide an end-user reorder interface or persist personal ordering. Embedders can store preferences and apply them through these properties.
124
+ Reassign the object to apply changes. Their shortcuts remain available even when their launcher is outside the root dial. Priority reflects app-provided configuration; Nook does not yet provide an end-user reorder interface or persist personal ordering. Embedders can store preferences and apply them through these properties.
116
125
 
117
126
  `label` and `description` accept attributes or slotted templates. `slot="icon"` accepts a template or an element. Labels are accessible names; desktop tooltips include supported keybindings. Overflow entries show their labels inline. Set `--nook-label-display: block` to show text beneath root action icons. Set `hidden` or `disabled` to withdraw a definition and its descendants.
118
127
 
@@ -175,7 +184,7 @@ dock.addEventListener("nook-change", ({ detail: { source, value } }) => {
175
184
  });
176
185
  ```
177
186
 
178
- Native `color`, `number`, and `text` inputs and custom `<template slot="control">` remain available. Add `root` to put a setting directly in the root, or leave it off to include it in quick settings.
187
+ Native `color`, `number`, and `text` inputs and custom `<template slot="control">` remain available. Settings always render inside Quick settings; `root` does not move controls into the dial.
179
188
 
180
189
  Set `settings-href` on the dock to show an **All settings** link below the quick settings controls:
181
190
 
@@ -185,7 +194,7 @@ Set `settings-href` on the dock to show an **All settings** link below the quick
185
194
  </nook-dock>
186
195
  ```
187
196
 
188
- The equivalent property is `dock.settingsHref = "/settings"`. Set it to `null` or an empty string to remove the link. Changes project live without replacing controls or their drafts. A link alone makes Settings available, even without inline settings. It is a normal same-tab anchor with standard browser navigation and modified-click behavior. Its label uses Bab's `All settings` message. Style it with `::part(settings-footer)` and `::part(settings-link)`, `--nook-settings-footer-gap`, and `--nook-settings-link-color`.
197
+ The equivalent property is `dock.settingsHref = "/settings"`. Set it to `null` or an empty string to remove the link. Changes project live without replacing controls or their drafts. A link alone makes Settings available, even without inline settings. In Full menu, Settings follows this link when configured; otherwise it opens quick settings. The dial always opens quick settings. It is a normal same-tab anchor with standard browser navigation and modified-click behavior. Its label uses Bab's `All settings` message. Style it with `::part(settings-footer)` and `::part(settings-link)`, `--nook-settings-footer-gap`, and `--nook-settings-link-color`.
189
198
 
190
199
  Picker parts: `enum-control`, `enum-trigger`, `enum-current`, `enum-chevron`, `enum-value`, `enum-options`, `enum-option`, `enum-option-content`, `enum-option-icon`, `enum-option-text`, `enum-option-label`, `enum-option-description`, `enum-selected-label`, `enum-check`, `enum-error`, and `enum-empty`. See the [ARIA listbox interaction pattern](https://www.w3.org/WAI/ARIA/apg/patterns/listbox/).
191
200
 
@@ -224,21 +233,23 @@ The built-in form includes Bug report, UI / usability, Feature request, and Othe
224
233
 
225
234
  `attachFiles(files, { signal })` may prepare/upload files and return an array of files or app-owned descriptors; returning nothing stages the original files. Submission receives these as `{ kind: 'file' | 'screenshot', file }`. The defaults allow five attachments of up to 10 MiB each. Native file `accept` is a picker hint; your hooks/backend own delivery, validation, authorization, and any upload cleanup.
226
235
 
227
- `captureScreenshot({ root, exclude, signal })` is an optional app capability. “Capture app view” enters a session with a compact Capture / snapshot-count / Done toolbar, leaving the app navigable. Snapshot previews expand only when the count is selected; routine capture status is announced without occupying screen space. Users can take multiple snapshots, see their count, preview them, and remove individual snapshots before returning to their feedback draft. Done aborts the session signal. Nook hides only its own UI during each capture; wrapped app content stays visible. The hook receives the app root and Nook UI nodes to exclude. DOM renderers should honor both. Returned File/Blob values or app descriptors are staged for final submission, never automatically sent.
236
+ `captureScreenshot({ root, exclude, signal })` is an optional app capability. “Capture app view” enters a session with a compact Capture / snapshot-count / Done toolbar, leaving the app navigable. Snapshot previews expand only when the count is selected; “Hide previews” or Escape collapses them back to the toolbar. Routine capture status is announced without occupying screen space. Users can take multiple snapshots, see their count, preview them, and remove individual snapshots before returning to their feedback draft. Done aborts the session signal. Nook hides only its own UI during each capture; wrapped app content stays visible. The hook receives the app root and Nook UI nodes to exclude. DOM renderers should honor both. Returned File/Blob values or app descriptors are staged for final submission, never automatically sent.
228
237
 
229
238
  `createTabCaptureSession()` reuses one browser sharing stream across snapshots and stops its tracks when Done aborts the signal; call `stop()` for explicit cleanup. The compatibility `captureTabScreenshot(options)` helper takes one shot and immediately stops sharing. Browser permission and a user gesture are required to begin sharing. The browser owns the surface chooser, so the selected surface cannot be guaranteed. These helpers are not silent DOM screenshot services. Apps may supply a DOM-to-image hook instead, subject to that renderer's font, image, shadow-DOM, and cross-origin limitations.
230
239
 
231
- Capture-session styling parts include `capture-session`, `capture-snapshot`, `capture-done`, `capture-review`, `capture-review-icon`, `capture-count`, `capture-status`, `capture-gallery`, `capture-preview-image`, `capture-thumbnails`, `capture-thumbnail`, `capture-preview`, and `capture-remove`.
240
+ Capture-session styling parts include `capture-session`, `capture-snapshot`, `capture-done`, `capture-review`, `capture-review-icon`, `capture-count`, `capture-status`, `capture-gallery`, `capture-hide-previews`, `capture-preview-image`, `capture-thumbnails`, `capture-thumbnail`, `capture-preview`, and `capture-remove`.
232
241
 
233
- Feedback parts include `feedback-form`, `feedback-intro`, `feedback-types`, `feedback-type`, `feedback-label`, `feedback-message`, `feedback-tools`, `feedback-file-input`, `feedback-attach`, `feedback-capture`, `feedback-attachments`, `feedback-attachment`, `feedback-preview`, `feedback-attachment-name`, `feedback-remove`, `feedback-privacy`, `feedback-status`, `feedback-send`, `feedback-success`, `feedback-another`, and `feedback-done`.
242
+ Feedback parts include `feedback-form`, `feedback-intro`, `feedback-types`, `feedback-type`, `feedback-label`, `feedback-message-label`, `feedback-message`, `feedback-tools`, `feedback-file-input`, `feedback-attach`, `feedback-capture`, `feedback-attachments`, `feedback-attachment`, `feedback-preview`, `feedback-attachment-name`, `feedback-remove`, `feedback-privacy`, `feedback-status`, `feedback-send`, `feedback-success`, `feedback-another`, and `feedback-done`.
234
243
 
235
244
  ## First use
236
245
 
237
- Before its first opening, Nook gives a brief attention burst: about three soft pulses over six seconds. It then stops. If unopened, it retries after a random 45–75 minutes. The cooldown is stored under `@3sln/nook:attention:v1`, so reloads do not restart the burst. Hidden tabs defer attention until visible; opening Nook stops reminders. The scheduler releases its timer/listeners when the last dock disconnects. The first open shows a short device-specific introduction, then “Got it” continues to the requested menu or panel. Escape may dismiss it immediately. Reduced-motion preferences disable the pulse. The keyboard-specific number tip appears only when layout data is available.
246
+ Before its first opening, Nook gives a brief attention burst: about three soft pulses over six seconds, followed by a random 45–75 minute cooldown if unopened. The deadline is stored under `@3sln/nook:attention:v1`. Opening Nook stops the widget reminder and opens the usable dial immediately. A badged **Quick tour** choice provides optional, device-specific help. Escape or Back leaves it available; “Got it” acknowledges and removes it. Reduced-motion preferences disable the pulse and turn the gesture illustrations into static diagrams.
247
+
248
+ The tutorial includes small SVG loops for touch picking, keyboard navigation, and holding then dragging. They use the current theme and run only while the tutorial is visible. The desktop guide lists the available major shortcuts, respects configured or disabled bindings, and updates live. It uses Option/Command on macOS and Alt/Ctrl elsewhere; platform detection changes wording only, never keyboard matching. Its optional number-key tip appears only when browser-reported layout data resolves number keys. The dial shows concise labels where they fit without overlapping. Visible captions stay in place on hover or focus and suppress duplicate tooltips; icon-only choices retain tooltips above neighboring buttons. Long labels truncate; captions that cannot fit cleanly are omitted. Alt/Option reveals numbered choices on desktop. Icons never shift when the helper appears. Set `--nook-label-display: none` for an icon-only default.
238
249
 
239
- Showing the introduction records `seen` under `@3sln/nook:tutorial:v1` in localStorage. This is shared across Nook instances on the same origin. Future visits open directly into the requested view. If storage is denied or throws, a provider-owned, document-scoped in-memory fallback prevents repeated introductions during the current page session. No network requests or account identification are involved.
250
+ Acknowledging the tutorial records `seen` under `@3sln/nook:tutorial:v1` in localStorage. Merely opening Nook or navigating other views does not mark it seen. The preference is shared across docks on the same origin. If storage is unavailable, a document-scoped memory fallback remembers acknowledgement during the current page session.
240
251
 
241
- To replay the first-use experience and its initial attention burst while developing, remove both `@3sln/nook:tutorial:v1` and `@3sln/nook:attention:v1` from localStorage and reload. `dock.open("welcome")` can replay just the tutorial without resetting stored first-use state. The introduction is marked as seen when shown, rather than requiring completion.
252
+ To replay the first-use experience and its initial attention burst while developing, remove both `@3sln/nook:tutorial:v1` and `@3sln/nook:attention:v1` from localStorage and reload. `dock.open("welcome")` can replay just the tutorial without resetting stored first-use state. Only acknowledgement records the tutorial as seen; opening it and leaving with Escape keeps the badged choice available.
242
253
 
243
254
  ### Customizing the tutorial
244
255
 
@@ -247,6 +258,7 @@ Customize each dock's headings and button labels through its `tutorial` property
247
258
  ```js
248
259
  dock.tutorial = {
249
260
  title: "Meet your workspace shortcuts",
261
+ label: "Quick tour",
250
262
  continueLabel: "Let's go",
251
263
  };
252
264
  ```
@@ -263,7 +275,7 @@ Supply direct-child templates for custom content. Desktop and touch wrappers rem
263
275
  </template>
264
276
  <template slot="tutorial-touch">
265
277
  <p>Tap the corner for workspace extras.</p>
266
- <p>Swipe sideways for all actions or vertically for settings.</p>
278
+ <p>Swipe sideways for full menu or vertically for settings.</p>
267
279
  </template>
268
280
  <template slot="tutorial-drag">Hold, then drag to choose a corner.</template>
269
281
  </nook-dock>
@@ -271,11 +283,13 @@ Supply direct-child templates for custom content. Desktop and touch wrappers rem
271
283
 
272
284
  Slot `tutorial` replaces the entire welcome body and takes precedence over individual device regions. The Continue button remains Nook-owned. With a full replacement, the embedder owns any device-specific content inside it. The drag-region template remains hidden when Nook cannot move.
273
285
 
274
- The library does not include a persistent “How Nook works” entry, help surface, or corner-picker menu. The demo's guide is an ordinary app-authored `nook-panel`; embedders can supply their own help in the same way. Position can be changed through dragging or the dock's `position` property.
286
+ After acknowledgement the built-in tour entry is removed; there is no persistent help or corner-picker menu. The demo's guide is an ordinary app-authored `nook-panel`; embedders can supply their own help in the same way. Position can be changed through dragging or the dock's `position` property.
275
287
 
276
288
  Templates are cloned once on insertion/replacement. Unrelated mutations and navigation preserve mounted custom content and form state. Replace a template element to apply new content; removing it restores the built-in region. Listeners on inert template nodes are not cloned; use custom elements or bubbling events. Custom content renders in the dock's shadow root, so include a `<style>` in the template or use the exposed parts.
277
289
 
278
- Style `::part(tutorial)`, `::part(tutorial-desktop)`, `::part(tutorial-touch)`, `::part(tutorial-drag)`, and `::part(tutorial-continue)`. The welcome surface also exposes the shared `heading`, `header`, `body`, and `surface` parts. They inherit Nook's color, typography, spacing, and animation tokens.
290
+ Supply `tutorial-keyboard-graphic`, `tutorial-touch-graphic`, or `tutorial-drag-graphic` templates to replace individual illustrations without replacing the copy. `--nook-tutorial-graphic-width` sets their maximum width (360px by default). The SVGs expose `tutorial-graphic` and their respective graphic parts.
291
+
292
+ Style `::part(tutorial)`, `::part(tutorial-desktop)`, `::part(tutorial-shortcuts)`, `::part(tutorial-shortcut)`, `::part(tutorial-shortcut-label)`, `::part(tutorial-shortcut-keybind)`, `::part(tutorial-touch)`, `::part(tutorial-drag)`, and `::part(tutorial-continue)`. The welcome surface also exposes the shared `heading`, `header`, `body`, and `surface` parts. They inherit Nook's color, typography, spacing, and animation tokens.
279
293
 
280
294
  ## SVG sprite
281
295
 
@@ -341,15 +355,50 @@ dock.addEventListener("nook-command", (event) => {
341
355
  });
342
356
  ```
343
357
 
344
- Keyboard interpretation uses reported `KeyboardEvent.key` values and, for Alt/Option printable-key bindings and numbered selection, `navigator.keyboard.getLayoutMap()`. There are no US key-position tables, user-agent checks, inferred OS layouts, or embedder layout configuration. A layout-dependent binding is disabled and its hint hidden unless browser-provided mapping resolves its key. If layout data is absent, denied, incomplete, or later becomes unavailable, those bindings and their number badges are disabled; the UI remains clickable and logical-key shortcuts such as Ctrl/Cmd+Shift+P remain available. Available mappings are refreshed on focus, Alt/Option modifier presses, and browser layout-change events. Dependent bindings are suspended while a refresh is pending, and stale requests cannot overwrite newer results. Browser/OS-reserved combinations can still take precedence.
358
+ Keyboard interpretation uses reported `KeyboardEvent.key` values and, for Alt/Option printable-key bindings and numbered selection, `navigator.keyboard.getLayoutMap()`. There are no US key-position tables, inferred OS layouts, or embedder layout configuration. Platform information is used only to choose modifier names in help text. A layout-dependent binding is disabled and its hint hidden unless browser-provided mapping resolves its key. If layout data is absent, denied, incomplete, or later becomes unavailable, those bindings and their number badges are disabled; the UI remains clickable and logical-key shortcuts such as Ctrl/Cmd+Shift+P remain available. Available mappings are refreshed on focus, Alt/Option modifier presses, and browser layout-change events. Dependent bindings are suspended while a refresh is pending, and stale requests cannot overwrite newer results. Browser/OS-reserved combinations can still take precedence.
345
359
 
346
360
  Search results, bindings, command availability, and argument definitions update live. Existing result buttons and form fields are retained. Adding a command does not erase the current query or draft arguments; opening the palette anew starts a fresh search. Newly added argument fields are pre-rendered before navigation.
347
361
 
348
362
  Additional parts: `command-search`, `command-list`, `command-empty`, `command-result`, `command-name`, `command-description`, `command-keybind`, `command-status`, `command-form`, `command-fields`, `command-field`, `command-arg-label`, `command-arg-control`, `command-run`, `number`, `tutorial`, and `tutorial-continue`. Set `--nook-command-width` (default `480px`) to customize the desktop palette width.
349
363
 
364
+ ## Items and commands
365
+
366
+ Commands declare reusable operations. Items declare where a choice appears. Submenus remain `nook-menu` containers, and controls remain `nook-setting` elements.
367
+
368
+ ```html
369
+ <nook-command
370
+ id="save"
371
+ name="Save changes"
372
+ description="Save the current draft"
373
+ keybind="Alt+S"
374
+ ></nook-command>
375
+ <nook-menu label="File">
376
+ <nook-item command="save"></nook-item>
377
+ <nook-item id="custom-flow" label="Custom workflow"></nook-item>
378
+ </nook-menu>
379
+ ```
380
+
381
+ `command` is a command ID within the same dock; `item.command` reflects that attribute. A linked item inherits the command's name, icon, and shortcut unless it supplies its own label or icon. The command owns the keybinding: an item's own `keybind` is ignored when it references a command, and new standalone items do not register shortcuts. Legacy standalone `nook-action` definitions retain their old bindings.
382
+
383
+ Selecting a new item first emits cancelable `nook-select` with `{ source, id, command, context }`. `command` is the resolved element or `null` for a custom item. Call `preventDefault()` to handle selection yourself. Otherwise a linked item enters the same command argument form used by the palette, or executes a command without arguments. Multiple items share one form and one execution path. Shortcuts invoke the command directly without selecting a particular item.
384
+
385
+ ```js
386
+ dock.addEventListener("nook-select", (event) => {
387
+ if (event.detail.id === "custom-flow") {
388
+ event.preventDefault();
389
+ // Open an app-owned flow. The app decides when to close Nook.
390
+ }
391
+ });
392
+ dock.addEventListener("nook-command", (event) => {
393
+ if (event.detail.id === "save") saveDraft();
394
+ });
395
+ ```
396
+
397
+ Missing, invalid, hidden, or disabled referenced commands withdraw their items from the menu. Labels, icons, shortcuts, references, and command availability update live. Existing `nook-action` declarations can reference commands too; they keep their cancelable `nook-action` hook before invocation. No network requests or operation implementations are supplied by Nook.
398
+
350
399
  ## Compact and expanded views
351
400
 
352
- Small menus float without a surrounding card on desktop and mobile. Root views stay compact within their configured row capacity; submenu views through four; settings stay compact through three controls. The root shows only its items. Floating actions share one restrained frosted surface for contrast over app content.
401
+ The root is a compact icon arc on desktop and mobile, with individually surfaced choices. Other small menus float without a surrounding card; submenu views through four; settings stay compact through three controls. The root shows only its items. Floating nested menus share one restrained frosted surface for contrast over app content.
353
402
 
354
403
  Larger views use a contained card on desktop. On touch/coarse-pointer or hoverless devices they fill the viewport over a blurred, tinted backdrop, with safe-area padding, a close/back header, and scrollable content. Adding/removing definitions updates presentation live without remounting their UI. Arbitrary panels and the first-use tutorial default to expanded presentation because their content size is not implied by an item count.
355
404
 
@@ -359,7 +408,7 @@ The close button appears only in fullscreen mobile views. Compact and desktop vi
359
408
 
360
409
  Embedders can override the default with `presentation="compact"` or `presentation="expanded"` on a `nook-menu`/`nook-panel`, or `root-presentation`, `settings-presentation`, `overflow-presentation` (or legacy `full-presentation`), `welcome-presentation` on the dock. Removing the override restores automatic selection. Use explicit presentation for custom content with unusual dimensions.
361
410
 
362
- Customize floating control backgrounds with `--nook-floating-background`; customize the viewport layer with `--nook-backdrop` and `--nook-backdrop-blur` (defaults to the shared 24px material blur). Group spacing uses `--nook-group-gap` (default `16px`).
411
+ Customize floating control backgrounds with `--nook-floating-background`; customize the viewport layer with `--nook-backdrop` and `--nook-backdrop-blur` (defaults to the shared 7.5px material blur). Group spacing uses `--nook-group-gap` (default `16px`).
363
412
 
364
413
  ## Events and application behavior
365
414
 
@@ -367,7 +416,7 @@ Customize floating control backgrounds with `--nook-floating-background`; custom
367
416
  const dock = document.querySelector("nook-dock");
368
417
  dock.context = { route: location.pathname, documentId: "current-document" };
369
418
 
370
- dock.addEventListener("nook-action", (event) => {
419
+ dock.addEventListener("nook-select", (event) => {
371
420
  const { source, id, context } = event.detail;
372
421
  // Translate intent into your application's Actions here.
373
422
  // event.preventDefault() keeps Nook open for this action.
@@ -394,20 +443,22 @@ For a custom setting, supply `<template slot="control">…</template>`. Its nati
394
443
 
395
444
  Tap/click opens the root menu, with action icons. The device-specific introduction is shown on first use. Touch/coarse-pointer or hoverless devices hide keyboard hints and show swipe instructions instead. These capability queries update live when browser emulation or input capabilities change. Clicking outside closes it. Hold for 450ms, then drag. The live handle stays anchored and dims while a non-interactive preview shows the destination corner; release commits that corner. Cancellation restores the original placement. The mark rotates to match each corner. The handle has a 48px touch target and a 32px visible corner with the same recognizable quarter-circle shape.
396
445
 
397
- On touch devices (and pointer-based testing), swipe horizontally or diagonally for All actions, and vertically for settings. Swipes have a 32px distance threshold and broad directional bands. Empty surfaces do not activate from gestures or shortcuts. Gestures start only on the handle, leaving application scrolling alone. More is the overflow launcher in the root menu; swipes show the complete All actions list.
446
+ Fast swipes open Full menu horizontally or diagonally and Settings vertically. A slower held swipe reveals the same root dial used by tapping. Swipes have a 32px distance threshold and broad directional bands. Empty surfaces do not activate from gestures or shortcuts. Gestures start only on the handle, leaving application scrolling alone. Release directly on a dial choice to choose it; releasing elsewhere cancels. Extra choices never receive directional swipe bindings.
398
447
 
399
- | Desktop action | Default | Dock attribute |
400
- | --------------- | ------------- | ------------------ |
401
- | Main/root menu | `Shift+?` | `root-keybind` |
402
- | Settings | `Alt+Shift+S` | `settings-keybind` |
403
- | More / overflow | `Alt+Shift+Q` | `overflow-keybind` |
404
- | Feedback | `Alt+Shift+F` | `feedback-keybind` |
448
+ | Desktop action | Default | Dock attribute |
449
+ | --------------- | ---------------------------------- | ------------------ |
450
+ | Main/root menu | `Shift+?` | `root-keybind` |
451
+ | Settings | `Alt+Shift+S` | `settings-keybind` |
452
+ | Full menu | `Alt+Shift+M` | `all-keybind` |
453
+ | Commands | `Ctrl+Shift+P` / `Command+Shift+P` | `commands-keybind` |
454
+ | More / overflow | `Alt+Shift+Q` | `overflow-keybind` |
455
+ | Feedback | `Alt+Shift+F` | `feedback-keybind` |
405
456
 
406
457
  `quick-keybind` remains a compatibility alias when `overflow-keybind` is absent. Set an empty `overflow-keybind` to disable the default.
407
458
 
408
459
  Designate the feedback action/panel with `feedback`. The first available designated item wins. Any action, panel, or submenu, including a root item, may also declare `keybind` or set `.keybind`. Bindings use `Ctrl`, `Alt`, `Meta`, `Shift` plus `KeyboardEvent.key`, for example `Meta+Shift+K`. Modifiers must match exactly. Set an empty dock binding attribute to disable that default. Dock defaults take precedence over item bindings; otherwise the first available item in DOM order wins. Browser/OS-reserved bindings may not reach the page.
409
460
 
410
- Shortcuts ignore editable elements, composition, repeated presses, and already-prevented events. Inside a menu, arrows cycle focus, Home/End jump, Enter/Space activate buttons, and Alt/Option+1–9 chooses the corresponding displayed action. Hold Alt/Option to reveal assigned numbers alongside item labels in readable inline rows; release it or leave the window to hide them. Plain number keys never activate items. Tab cycles within the current surface. Escape closes the entire menu and clears navigation; reopening starts at the root. Backspace (the Mac Delete key) goes back one level. Backspace/Delete retains normal editing behavior in text fields. Native editing keys retain their behavior inside controls. Focus returns to the element that opened the menu. Reduced-motion preferences disable animation.
461
+ Shortcuts ignore editable elements, composition, repeated presses, and already-prevented events. Inside a menu, arrows cycle focus, Home/End jump, Enter/Space activate buttons, and Alt/Option+1–9 chooses the corresponding displayed action. Hold Alt/Option to reveal assigned numbers alongside item labels without moving the root arc; other icon menus use readable inline rows; release it or leave the window to hide them. Plain number keys never activate items. Tab cycles within the current surface. Escape closes the entire menu and clears navigation; reopening starts at the root. Backspace (the Mac Delete key) goes back one level. Backspace/Delete retains normal editing behavior in text fields. Native editing keys retain their behavior inside controls. Focus returns to the element that opened the menu. Reduced-motion preferences disable animation.
411
462
 
412
463
  ```js
413
464
  dock.open(); // root
@@ -451,11 +502,11 @@ During dragging, every allowed corner/center is outlined and the closest one is
451
502
 
452
503
  Defaults adapt to light and dark schemes through native `color-scheme` and `light-dark()` colors. Set `nook-dock { color-scheme: dark; }` or `light` to choose explicitly; leave the default `light dark` to follow the system. Native controls, accent text contrast, floating controls, and backdrops follow the same palette. Explicit custom properties remain authoritative; use `light-dark(lightValue, darkValue)` when an override should also adapt.
453
504
 
454
- Compact views use one acrylic-style frosted toolbar: a neutral translucent tint, 24px blur, mild saturation, a crisp rounded edge, a fine inset highlight, and a single elevation shadow. Buttons and setting rows sit directly on the shared material; there are no feathered masks or persistent button bubbles. The material becomes opaque when backdrop filtering is unsupported or reduced transparency is requested.
505
+ Compact views use one acrylic-style frosted toolbar: a neutral translucent tint, 7.5px blur, a 41% surface tint, a crisp rounded edge, a fine inset highlight, and a single elevation shadow. Buttons and setting rows sit directly on the shared material; there are no feathered masks or persistent button bubbles. The material becomes opaque when backdrop filtering is unsupported or reduced transparency is requested.
455
506
 
456
507
  Fullscreen views share the same tint, blur, saturation, and edge highlight, extending the material to the viewport. `--nook-material-background` and `--nook-material-blur` control both variants together; per-variant overrides remain available. Both variants use the same opaque fallback for reduced transparency or unsupported backdrop filtering.
457
508
 
458
- Customize it with `--nook-halo-background`, `--nook-halo-blur` (24px), `--nook-halo-inset` (-8px), `--nook-material-saturation` (125%), `--nook-material-edge`, `--nook-material-highlight`, `--nook-radius`, and `--nook-shadow`. Tap highlights are disabled; keyboard focus on the handle follows its quarter-circle outline.
509
+ Customize it with `--nook-halo-background`, `--nook-halo-blur` (7.5px), `--nook-halo-inset` (-8px), `--nook-material-saturation` (100%), `--nook-material-edge`, `--nook-material-highlight`, `--nook-radius`, and `--nook-shadow`. Tap highlights are disabled; keyboard focus on the handle follows its quarter-circle outline.
459
510
 
460
511
  References: [Microsoft Acrylic guidance for transient menus and flyouts](https://learn.microsoft.com/en-us/windows/apps/design/style/acrylic) and [Josh W. Comeau's frosted-glass examples](https://www.joshwcomeau.com/css/backdrop-filter/). This is a CSS interpretation of the material, not the native OS implementation.
461
512
 
@@ -472,7 +523,7 @@ nook-dock {
472
523
  --nook-font: system-ui, sans-serif;
473
524
  --nook-width: 304px;
474
525
  --nook-overflow-width: 360px;
475
- --nook-root-max-items: 5;
526
+ --nook-root-max-items: 6;
476
527
  --nook-z: 10000;
477
528
  --nook-label-display: none;
478
529
  --nook-padding: 16px;
@@ -492,11 +543,11 @@ nook-dock::part(control) {
492
543
  }
493
544
  ```
494
545
 
495
- Exposed parts: `handle-tooltip`, `handle-shortcut`, `drag-hint`, `landing-zone`, `landing-outline`, `drag-preview`, `preview-handle`, `preview-mark`, `anchor`, `handle`, `mark`, `announcement`, `surface`, `header`, `back`, `heading`, `close`, `body`, `items`, `item`, `icon`, `item-label`, `tooltip`, `tooltip-text`, `setting`, `setting-label`, `description`, `control`, `content`, `groups`, `group`, `group-heading`, and `group-body`. The handle's quarter-circle background is a pseudo-element; customize its color with `--nook-accent` or the handle itself with `::part(handle)`. Inner geometry can be overridden by styling parts. Nook's inline SVG mark remains constant.
546
+ Exposed parts: `handle-tooltip`, `handle-shortcut`, `drag-hint`, `landing-zone`, `landing-outline`, `drag-preview`, `preview-handle`, `preview-mark`, `anchor`, `handle`, `mark`, `announcement`, `surface`, `header`, `back`, `heading`, `close`, `body`, `items`, `item`, `icon`, `item-label`, `tooltip`, `tooltip-text`, `dial-caption`, `tutorial-badge`, `setting`, `setting-label`, `description`, `control`, `content`, `groups`, `group`, `group-heading`, and `group-body`. The handle's quarter-circle background is a pseudo-element; customize its color with `--nook-accent` or the handle itself with `::part(handle)`. Inner geometry can be overridden by styling parts. Nook's inline SVG mark remains constant.
496
547
 
497
548
  The surface is fixed-position and uses a configurable z-index. Mount the dock near the document root, outside transformed/clipping ancestors. This version does not use the browser top layer; a host modal in the top layer can cover it.
498
549
 
499
- Overflow exposes `overflow-groups`, `overflow-group`, `overflow-group-heading`, and `overflow-group-body` parts alongside the standard item parts. `--nook-overflow-width` controls its desktop width; `--nook-keyboard-width` controls the temporary labeled width of icon menus while Alt/Option is held. More and the command palette keep their own widths.
550
+ Overflow exposes `overflow-groups`, `overflow-group`, `overflow-group-heading`, and `overflow-group-body` parts alongside the standard item parts. `--nook-overflow-width` controls its desktop width; `--nook-keyboard-width` controls temporary labeled layouts in nested icon menus. The root arc keeps its position and size while Alt/Option labels appear.
500
551
 
501
552
  Typography uses a shared hierarchy: 16px semibold headings, 14px primary text and command names, 13px control labels, and 12px descriptions/captions. Headings have a tighter line height; supporting text uses more breathing room and starts at the same inline edge. Fine print stays at 12px, and touch text inputs keep their 16px minimum. Customize `--nook-font`, `--nook-text-xs`, `--nook-text-sm`, `--nook-text-md`, `--nook-text-lg`, `--nook-line-height`, `--nook-heading-weight`, and `--nook-label-weight` to change the hierarchy consistently. `--nook-invite-cycle` (2s) and `--nook-invite-delay` (200ms) style the attention burst; the six-second budget and randomized cooldown are owned by the onboarding provider.
502
553
 
@@ -582,7 +633,7 @@ A dock containing only Nook definitions anchors to the viewport. To anchor withi
582
633
  <header>App bar</header>
583
634
  <nook-dock style="--nook-handle-z: 20; --nook-overlay-z: 1000">
584
635
  <main><!-- Existing application, rendered through a slot --></main>
585
- <nook-action label="Share"></nook-action>
636
+ <nook-item label="Share"></nook-item>
586
637
  </nook-dock>
587
638
  <footer>Bottom bar</footer>
588
639
  ```
@@ -600,7 +651,7 @@ dock.localization = {
600
651
  nook: {
601
652
  Settings: "Ajustes",
602
653
  More: "Más",
603
- "All actions": "Todas las acciones",
654
+ "Full menu": "Todas las acciones",
604
655
  "Search commands": "Buscar comandos",
605
656
  "{#} snapshots": "{#} capturas",
606
657
  "{#} snapshot": "{#} captura",
@@ -613,4 +664,4 @@ Without an explicit locale, Nook follows its `lang` attribute, then the document
613
664
 
614
665
  ### Held swipe dial
615
666
 
616
- Fast horizontal and diagonal swipes open **All actions**, including items already visible in the root menu; vertical swipes open settings. Hold a swipe outside Nook briefly to reveal a dial with Settings and All actions, then release directly on a choice. Releasing anywhere else cancels. Its opacity follows distance from Nook. A clear inner gap separates options from the handle; moving back inside that area cancels selection. Long-pressing before moving continues to drag the handle. Dial parts are `swipe-dial`, `swipe-dial-option`, `swipe-dial-icon`, and `swipe-dial-label`. `dock.open("all")` opens the complete list programmatically.
667
+ Fast horizontal and diagonal swipes open **Full menu**, including items already visible in the root menu; vertical swipes open settings. Hold a swipe outside Nook briefly to reveal the same root dial, with Settings, Full menu, and up to four prioritized extras, then release directly on a choice. Releasing anywhere else cancels. Its opacity follows distance from Nook. A clear inner gap separates options from the handle; moving back inside that area cancels selection. Long-pressing before moving continues to drag the handle. Dial parts are `swipe-dial`, `swipe-dial-option`, `swipe-dial-icon`, and `swipe-dial-label`. `dock.open("all")` opens the complete list programmatically.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@3sln/nook",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "type": "module",
5
5
  "description": "A quiet, contextual corner for web app extras.",
6
6
  "exports": {
package/src/bl/menu.js CHANGED
@@ -148,6 +148,7 @@ export function transition(state, intent) {
148
148
  return {
149
149
  ...state,
150
150
  path: [intent.view || state.introTarget || "root"],
151
+ needsIntro: false,
151
152
  introTarget: null,
152
153
  };
153
154
  case "configure-positions": {
@@ -233,13 +234,15 @@ export class MenuState extends Query {
233
234
  });
234
235
  const receive = (event) => {
235
236
  let intent = event.detail;
236
- if (state.needsIntro && ["open", "enter"].includes(intent.type)) {
237
- // Another tab or Nook instance may have shown the guide since boot.
238
- if (onboarding.hasSeen()) state = { ...state, needsIntro: false };
239
- else {
240
- onboarding.markSeen();
241
- intent = { type: "introduce", view: intent.view };
242
- }
237
+ if (state.needsIntro && onboarding.hasSeen())
238
+ state = { ...state, needsIntro: false };
239
+ if (state.needsIntro && ["open", "enter"].includes(intent.type))
240
+ onboarding.acknowledgeAttention?.();
241
+ if (["finish-intro", "dismiss-intro"].includes(intent.type)) {
242
+ state = { ...state, needsIntro: false };
243
+ onboarding.markSeen();
244
+ if (intent.type === "dismiss-intro")
245
+ intent = { type: "finish-intro", view: "root" };
243
246
  }
244
247
  state = transition(state, intent);
245
248
  notify(state);