@joewinke/jatui 0.54.1 → 0.55.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.
@@ -5,16 +5,25 @@
5
5
  *
6
6
  * Interaction contract:
7
7
  * - Click (or Space/Enter) = play / stop the last scape. A gesture-gated
8
- * engine call, made synchronously inside the click handler.
9
- * - The scape menu (menu button pattern) opens with ArrowDown/ArrowUp,
10
- * a press-and-hold (~450 ms) or a right-click (Android/Chrome fire
8
+ * engine call, made synchronously inside the click handler. With no
9
+ * playable scape registered (links only) the click opens the menu.
10
+ * - The menu (menu button pattern) opens with ArrowDown/ArrowUp, a
11
+ * press-and-hold (~450 ms) or a right-click (Android/Chrome fire
11
12
  * contextmenu on touch holds; iOS may cancel the pointer instead —
12
- * the keyboard path always works). It lists every
13
- * registered scape grouped meditation / focus / noise / nature / music
14
- * (`menuitemradio` items, current scape checked), carries a volume
15
- * slider wired to `engine.setVolume`, and shows each scape's
16
- * `description` as its secondary line — playlist scapes put their
17
- * track attribution there.
13
+ * the keyboard path always works). Top level: a volume slider wired to
14
+ * `engine.setVolume`, then one row per section — Radio / Focus /
15
+ * Noise / Meditation … in the host's order (`AmbienceOptions.groups`,
16
+ * jatui-mdq2p) — each opening a submenu of its items. Right / Enter /
17
+ * Space open a submenu and move focus into it; Left / Escape close it
18
+ * and return focus to its row (the ContextMenu submenu pattern); a
19
+ * mouse hover opens a row's submenu without moving focus.
20
+ * - Scapes are `menuitemradio` (current one checked; its section row
21
+ * carries a dot and the scape's name). Link items (radio stations) are
22
+ * real `<a target="_blank">` menu items with an external-link glyph:
23
+ * the station's own site or player opens in a new tab. Nothing is
24
+ * relayed — we never proxy a third-party stream.
25
+ * - A playing playlist shows its current track's attribution (title,
26
+ * composer, license) under the playlist item and its section row.
18
27
  *
19
28
  * A11y: the trigger is `aria-pressed` while playing and carries the full
20
29
  * menu-button wiring (`aria-expanded` / `aria-haspopup` / `aria-controls`).
@@ -27,24 +36,24 @@
27
36
  * Token-only theming (DaisyUI + `--jatui-*` + `--motion-*`). SSR-safe: the
28
37
  * engine is lazy and nothing here touches `window` at import.
29
38
  */
30
- import type { Ambience, Scape, ScapeGroup } from '../ambience/types';
31
- /** Canonical menu group order (the Scape contract's groups). */
39
+ import type { AmbienceGroupDef, AmbienceItem, ScapeGroup } from '../ambience/types';
40
+ import { type AmbienceSection } from '../ambience/groups';
41
+ import { type PlaylistTrack } from '../ambience/scapes/playlist';
42
+ /** Default menu section order (when the host configures no `groups`). */
32
43
  export declare const AMBIENCE_GROUP_ORDER: readonly ScapeGroup[];
33
- /** Human labels for the canonical groups. */
34
- export declare const GROUP_LABELS: Readonly<Record<ScapeGroup, string>>;
35
- /** One grouped section of the scape menu. */
36
- export interface AmbienceScapeGroup {
37
- group: ScapeGroup;
38
- label: string;
39
- scapes: Scape[];
40
- }
44
+ /** Human labels for the library's own groups. */
45
+ export declare const GROUP_LABELS: Readonly<Record<string, string>>;
46
+ /** One section of the menu: a top-level row and its submenu. */
47
+ export type AmbienceScapeGroup = AmbienceSection;
41
48
  /**
42
- * Group scapes for the menu: canonical group order, registration order
43
- * within a group, empty groups omitted. A group outside the union (a
44
- * registry from a newer engine) still renders, after the known ones, in
45
- * first-seen order — a menu never silently drops a registered scape.
49
+ * Group items for the menu: the host's section order (or the default),
50
+ * registration order within a section, empty sections omitted. A group
51
+ * missing from the order still renders, after the ordered ones, in
52
+ * first-seen order — a menu never silently drops a registered item.
46
53
  */
47
- export declare function groupScapes(scapes: Scape[]): AmbienceScapeGroup[];
54
+ export declare function groupScapes(items: AmbienceItem[], groups?: ReadonlyArray<ScapeGroup | AmbienceGroupDef>): AmbienceScapeGroup[];
55
+ /** "Gymnopédie No. 1 — Erik Satie · Public domain": a playing track's line. */
56
+ export declare function nowPlayingLine(track: PlaylistTrack | null | undefined): string;
48
57
  /**
49
58
  * The live label — "Ambience: Hibiscus, playing" while sounding,
50
59
  * "Ambience: Hibiscus, stopped" once a scape has been selected but is not
@@ -61,8 +70,30 @@ export declare const PLAY_ERROR_LABEL = "Ambience: couldn't start \u2014 tap aga
61
70
  * and settles on the exhale — 6% total, clamped against out-of-range input.
62
71
  */
63
72
  export declare function breathScale(value: number): number;
73
+ /** Submenu width (px) — matches the `w-64` class on the submenu panel. */
74
+ export declare const SUBMENU_WIDTH = 256;
75
+ /**
76
+ * Where a submenu goes, in viewport px. Beside the menu panel on the
77
+ * preferred side (the side the menu already extends to), else the other
78
+ * side, else — a phone, no room either side — overlapping the panel just
79
+ * below its row, clamped inside the viewport.
80
+ */
81
+ export declare function placeSubmenu(row: {
82
+ top: number;
83
+ bottom: number;
84
+ }, panel: {
85
+ left: number;
86
+ right: number;
87
+ }, viewport: {
88
+ width: number;
89
+ }, prefer: 'left' | 'right', width?: number, gap?: number, margin?: number): {
90
+ x: number;
91
+ y: number;
92
+ side: 'left' | 'right' | 'over';
93
+ };
94
+ import type { Ambience } from '../ambience/types';
64
95
  type $$ComponentProps = {
65
- /** The engine instance (from `createAmbience()`). The component never creates one, and never kills it — the host owns the engine's lifecycle. */
96
+ /** The engine instance (from `createAmbience()`). The component never creates one, and never kills it — the host owns the engine's lifecycle. Section order/labels come from the engine's `groups` option. */
66
97
  engine: Ambience;
67
98
  /** Smaller app-shell footprint: 40px trigger with an 18px glyph (default 44px / 20px). Never below the 40px touch floor. */
68
99
  compact?: boolean;
package/dist/index.d.ts CHANGED
@@ -408,8 +408,9 @@ export { default as ImageReveal } from './components/ImageReveal.svelte';
408
408
  export { imageRevealPhase, imageRevealBusy, imageRevealInitials, imageRevealSize, imageRevealTone, IMAGE_REVEAL_SIZES, IMAGE_REVEAL_RADIUS } from './components/imageReveal';
409
409
  export type { ImageRevealPhase, ImageRevealShape, ImageRevealSizeName, ImageRevealToneName, ImageRevealState } from './components/imageReveal';
410
410
  export { default as AmbienceToggle, AMBIENCE_GROUP_ORDER } from './components/AmbienceToggle.svelte';
411
- export { createAmbience, createDefaultAmbience, DEFAULT_SCAPES, BreathClock, BREATHS_PER_MINUTE, MASTER, ScapeRegistry, hibiscusScape, focusScape, noiseScapes, brownNoiseScape, pinkNoiseScape, whiteNoiseScape, rain, waves } from './ambience';
412
- export type { DefaultAmbienceOptions } from './ambience';
413
- export type { Ambience, AmbienceOptions, AmbienceState, BreathState, Scape, ScapeContext, ScapeGroup, ScapeInstance } from './ambience';
411
+ export { createAmbience, createDefaultAmbience, DEFAULT_SCAPES, BreathClock, BREATHS_PER_MINUTE, MASTER, ScapeRegistry, hibiscusScape, focusScape, noiseScapes, brownNoiseScape, pinkNoiseScape, whiteNoiseScape, rain, waves, AMBIENCE_DEFAULT_GROUP_ORDER, AMBIENCE_DEFAULT_GROUP_LABELS, groupAmbienceItems, ambienceGroupLabel, isAmbienceLink, normalizeAmbienceGroups, radioStation, safeAmbienceHref } from './ambience';
412
+ export type { DefaultAmbienceOptions, AmbienceSection } from './ambience';
413
+ export type { Ambience, AmbienceGroupDef, AmbienceItem, AmbienceLink, AmbienceOptions, AmbienceState, BreathState, Scape, ScapeContext, ScapeGroup, ScapeInstance } from './ambience';
414
414
  export { createPlaylistScape } from './ambience';
415
+ export { isPlaylistScape, trackAttribution } from './ambience';
415
416
  export type { PlaylistScape, PlaylistScapeOptions, PlaylistManifest, PlaylistState, PlaylistStatus, PlaylistTrack } from './ambience';
package/dist/index.js CHANGED
@@ -457,7 +457,8 @@ export { imageRevealPhase, imageRevealBusy, imageRevealInitials, imageRevealSize
457
457
  // navigation; createDefaultAmbience() pre-registers every shipped scape
458
458
  // (hibiscus, focus, brown/pink/white noise, rain, waves).
459
459
  export { default as AmbienceToggle, AMBIENCE_GROUP_ORDER } from './components/AmbienceToggle.svelte';
460
- export { createAmbience, createDefaultAmbience, DEFAULT_SCAPES, BreathClock, BREATHS_PER_MINUTE, MASTER, ScapeRegistry, hibiscusScape, focusScape, noiseScapes, brownNoiseScape, pinkNoiseScape, whiteNoiseScape, rain, waves } from './ambience';
460
+ export { createAmbience, createDefaultAmbience, DEFAULT_SCAPES, BreathClock, BREATHS_PER_MINUTE, MASTER, ScapeRegistry, hibiscusScape, focusScape, noiseScapes, brownNoiseScape, pinkNoiseScape, whiteNoiseScape, rain, waves, AMBIENCE_DEFAULT_GROUP_ORDER, AMBIENCE_DEFAULT_GROUP_LABELS, groupAmbienceItems, ambienceGroupLabel, isAmbienceLink, normalizeAmbienceGroups, radioStation, safeAmbienceHref } from './ambience';
461
461
  // Playlist scape (phase 2) — per-host, never in DEFAULT_SCAPES: build with
462
462
  // createPlaylistScape() and register on the engine yourself.
463
463
  export { createPlaylistScape } from './ambience';
464
+ export { isPlaylistScape, trackAttribution } from './ambience';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@joewinke/jatui",
3
- "version": "0.54.1",
3
+ "version": "0.55.0",
4
4
  "private": false,
5
5
  "description": "Shared Svelte 5 component library for JAT projects",
6
6
  "type": "module",