@kitlangton/knobs 0.0.0-stage → 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.
Files changed (123) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +1414 -2
  3. package/dist/dev/chunk-4km87zfx.js +155 -0
  4. package/dist/dev/chunk-5cr85vv6.js +17726 -0
  5. package/dist/dev/chunk-693jd1gj.js +1435 -0
  6. package/dist/dev/chunk-gezhc24g.js +1132 -0
  7. package/dist/dev/index.js +1660 -0
  8. package/dist/index.js +2651 -0
  9. package/dist/panel.js +26404 -0
  10. package/dist/react.js +326 -0
  11. package/dist/solid.js +336 -0
  12. package/dist/themes.js +235 -0
  13. package/dist/types/core/adapter.d.ts +21 -0
  14. package/dist/types/core/code.d.ts +3 -0
  15. package/dist/types/core/color.d.ts +27 -0
  16. package/dist/types/core/easing.d.ts +21 -0
  17. package/dist/types/core/internal.d.ts +18 -0
  18. package/dist/types/core/keys.d.ts +21 -0
  19. package/dist/types/core/mutate.d.ts +21 -0
  20. package/dist/types/core/ramp.d.ts +150 -0
  21. package/dist/types/core/random.d.ts +62 -0
  22. package/dist/types/core/readout.d.ts +73 -0
  23. package/dist/types/core/remote.d.ts +260 -0
  24. package/dist/types/core/schema.d.ts +550 -0
  25. package/dist/types/core/spring.d.ts +38 -0
  26. package/dist/types/core/storage.d.ts +8 -0
  27. package/dist/types/core/store.d.ts +536 -0
  28. package/dist/types/core/theme.d.ts +27 -0
  29. package/dist/types/core/variants.d.ts +25 -0
  30. package/dist/types/index.d.ts +14 -0
  31. package/dist/types/panel/attach.d.ts +5 -0
  32. package/dist/types/panel/choice.d.ts +56 -0
  33. package/dist/types/panel/die.d.ts +121 -0
  34. package/dist/types/panel/editors.d.ts +170 -0
  35. package/dist/types/panel/flash.d.ts +13 -0
  36. package/dist/types/panel/folds.d.ts +14 -0
  37. package/dist/types/panel/give.d.ts +73 -0
  38. package/dist/types/panel/gpu/atlas.d.ts +40 -0
  39. package/dist/types/panel/gpu/color.d.ts +128 -0
  40. package/dist/types/panel/gpu/die.wgsl.d.ts +1 -0
  41. package/dist/types/panel/gpu/renderer.d.ts +784 -0
  42. package/dist/types/panel/gpu/shader.wgsl.d.ts +2 -0
  43. package/dist/types/panel/haptics.d.ts +12 -0
  44. package/dist/types/panel/has.d.ts +13 -0
  45. package/dist/types/panel/hints.d.ts +45 -0
  46. package/dist/types/panel/icons.d.ts +30 -0
  47. package/dist/types/panel/index.d.ts +11 -0
  48. package/dist/types/panel/inspector.d.ts +50 -0
  49. package/dist/types/panel/measure.d.ts +6 -0
  50. package/dist/types/panel/menu.d.ts +38 -0
  51. package/dist/types/panel/morph.d.ts +38 -0
  52. package/dist/types/panel/motion.d.ts +77 -0
  53. package/dist/types/panel/optkeys.d.ts +7 -0
  54. package/dist/types/panel/page-hints.d.ts +82 -0
  55. package/dist/types/panel/page.d.ts +423 -0
  56. package/dist/types/panel/panel.d.ts +43 -0
  57. package/dist/types/panel/position.d.ts +5 -0
  58. package/dist/types/panel/prefs.d.ts +28 -0
  59. package/dist/types/panel/presets.d.ts +60 -0
  60. package/dist/types/panel/ramp-morph.d.ts +117 -0
  61. package/dist/types/panel/ramps.d.ts +14 -0
  62. package/dist/types/panel/readout-row.d.ts +21 -0
  63. package/dist/types/panel/reel.d.ts +43 -0
  64. package/dist/types/panel/reveal.d.ts +15 -0
  65. package/dist/types/panel/rolling-css.d.ts +2 -0
  66. package/dist/types/panel/scope.d.ts +19 -0
  67. package/dist/types/panel/search.d.ts +36 -0
  68. package/dist/types/panel/seg.d.ts +15 -0
  69. package/dist/types/panel/session.d.ts +21 -0
  70. package/dist/types/panel/slider.d.ts +92 -0
  71. package/dist/types/panel/smear.d.ts +7 -0
  72. package/dist/types/panel/sound.d.ts +137 -0
  73. package/dist/types/panel/spin.d.ts +13 -0
  74. package/dist/types/panel/styles.d.ts +11 -0
  75. package/dist/types/panel/swipe.d.ts +114 -0
  76. package/dist/types/panel/switcher.d.ts +20 -0
  77. package/dist/types/panel/tabs.d.ts +31 -0
  78. package/dist/types/panel/thumb.d.ts +36 -0
  79. package/dist/types/panel/toolbar-grid.d.ts +6 -0
  80. package/dist/types/react/Knobs.d.ts +15 -0
  81. package/dist/types/react/VariantSwitcher.d.ts +10 -0
  82. package/dist/types/react/index.d.ts +3 -0
  83. package/dist/types/react/useControls.d.ts +11 -0
  84. package/dist/types/solid/index.d.ts +50 -0
  85. package/dist/types/themes/index.d.ts +1 -0
  86. package/dist/types/ui/activity-css.d.ts +1 -0
  87. package/dist/types/ui/activity-model.d.ts +122 -0
  88. package/dist/types/ui/activity.d.ts +67 -0
  89. package/dist/types/ui/bento-css.d.ts +1 -0
  90. package/dist/types/ui/bento-model.d.ts +96 -0
  91. package/dist/types/ui/bento.d.ts +77 -0
  92. package/dist/types/ui/commands.d.ts +11 -0
  93. package/dist/types/ui/confirm.d.ts +13 -0
  94. package/dist/types/ui/follow.d.ts +17 -0
  95. package/dist/types/ui/index.d.ts +24 -0
  96. package/dist/types/ui/layer.d.ts +70 -0
  97. package/dist/types/ui/look.d.ts +62 -0
  98. package/dist/types/ui/menu.d.ts +63 -0
  99. package/dist/types/ui/overlay-css.d.ts +1 -0
  100. package/dist/types/ui/overlay-model.d.ts +87 -0
  101. package/dist/types/ui/palette.d.ts +38 -0
  102. package/dist/types/ui/picker-css.d.ts +1 -0
  103. package/dist/types/ui/picker-model.d.ts +53 -0
  104. package/dist/types/ui/picker.d.ts +67 -0
  105. package/dist/types/ui/popover.d.ts +35 -0
  106. package/dist/types/ui/react.d.ts +72 -0
  107. package/dist/types/ui/readout-css.d.ts +1 -0
  108. package/dist/types/ui/readout-model.d.ts +137 -0
  109. package/dist/types/ui/readouts.d.ts +85 -0
  110. package/dist/types/ui/rgba.d.ts +6 -0
  111. package/dist/types/ui/solid.d.ts +63 -0
  112. package/dist/types/ui/split-model.d.ts +135 -0
  113. package/dist/types/ui/split.d.ts +92 -0
  114. package/dist/types/ui/timeline-model.d.ts +83 -0
  115. package/dist/types/ui/timeline.d.ts +76 -0
  116. package/dist/types/ui/toast.d.ts +19 -0
  117. package/dist/types/ui/tooltip.d.ts +19 -0
  118. package/dist/types/ui/tree-model.d.ts +125 -0
  119. package/dist/types/ui/tree.d.ts +95 -0
  120. package/dist/ui-react.js +9230 -0
  121. package/dist/ui-solid.js +9162 -0
  122. package/dist/ui.js +11826 -0
  123. package/package.json +99 -4
@@ -0,0 +1,114 @@
1
+ /** A pause this long (ms) with no wheel events ends a gesture. */
2
+ export declare const GESTURE_GAP = 120;
3
+ export interface WheelSample {
4
+ readonly deltaX: number;
5
+ readonly deltaY: number;
6
+ readonly deltaMode: number;
7
+ readonly shiftKey: boolean;
8
+ /** ms */
9
+ readonly timeStamp: number;
10
+ /** Safari only: false when natural scrolling is off, so the deltas follow the fingers. */
11
+ readonly webkitDirectionInvertedFromDevice?: boolean;
12
+ /**
13
+ * Chromium 151+ (Chrome, Edge, Brave): true for the inertia the OS sends after the fingers lift
14
+ * (the platform's own momentum phase). Where it exists the pan needs no guessing; where it
15
+ * doesn't (Safari, Firefox), the lift is recognised from the stream's shape.
16
+ */
17
+ readonly momentum?: boolean;
18
+ }
19
+ export interface PanHandlers {
20
+ /** Asked at the start of each gesture or notch; false leaves it to the page. */
21
+ accept?(): boolean;
22
+ /** Finger travel (px) since the last call: positive rightwards and downwards. */
23
+ move(dx: number, dy: number): void;
24
+ /**
25
+ * The fingers seem to have lifted: a cue to let go visually. It comes as soon as a lift is
26
+ * suspected, before `end` (which waits for proof); a move after it means they hadn't.
27
+ */
28
+ lift?(): void;
29
+ /** After one or more moves: the fingers lifted (their momentum is ignored) or went still. */
30
+ end(): void;
31
+ /** A mouse wheel notch (or ⇧ + wheel): one step, rightwards or down being +1. */
32
+ step?(direction: -1 | 1): void;
33
+ }
34
+ /**
35
+ * Turns wheel events into direct manipulation. A horizontal two-finger pan moves 1:1 with the
36
+ * fingers from its first event; when they lift, the momentum macOS keeps sending is recognised
37
+ * and ignored, so things stop where the fingers stopped. Vertical gestures are never claimed.
38
+ *
39
+ * A lift is spotted the moment it happens, by its pause and spike, or by a steady fade when a
40
+ * stream has no pause. Suspect events are held back rather than applied: a finger event in their
41
+ * place releases them, and so does one breaking the coast with no pause (a new touch always
42
+ * pauses), so a false alarm costs a few ms, never distance. A push stronger than the momentum it
43
+ * interrupts starts a new gesture at once.
44
+ *
45
+ * `feed` returns whether the event belongs to a claimed gesture, in which case the caller should
46
+ * `preventDefault()` it (so the browser doesn't scroll or swipe back a page). `idle` ends the
47
+ * gesture once the wheel has been quiet for `GESTURE_GAP`.
48
+ */
49
+ export declare function createPan(handlers: PanHandlers, options?: {
50
+ axes?: "x" | "xy";
51
+ }): {
52
+ feed: (event: WheelSample) => boolean;
53
+ /** The wheel went quiet: whatever was in hand is over. */
54
+ idle(): void;
55
+ };
56
+ /** Feeds `el`'s wheel events to a pan (see `createPan`), ending gestures after a quiet spell. */
57
+ export declare function onPan(el: HTMLElement, handlers: PanHandlers, options?: {
58
+ axes?: "x" | "xy";
59
+ }): {
60
+ feed: (event: WheelSample) => boolean;
61
+ /** The wheel went quiet: whatever was in hand is over. */
62
+ idle(): void;
63
+ };
64
+ /** An option's box in its container (px): where the selection pill sits on it. */
65
+ export interface Slot {
66
+ readonly left: number;
67
+ readonly right: number;
68
+ readonly top: number;
69
+ }
70
+ /** A pill mid-pan: the option it belongs to, and how far (px) the fingers have pushed it off. */
71
+ export interface Slide {
72
+ readonly index: number;
73
+ readonly offset: number;
74
+ }
75
+ /** Whether a pill's offset is past an end: its give (see give.ts). */
76
+ export declare const giving: (slots: readonly Slot[], { index, offset }: Slide) => boolean;
77
+ /**
78
+ * Pushes the pill `delta` px; it joins a neighbour once past halfway to it. Past an end it gives
79
+ * like every control does (see give.ts): a little, less and less, and turning back takes the give
80
+ * up 1:1 before it moves on.
81
+ */
82
+ export declare function slide(slots: readonly Slot[], from: Slide, delta: number): Slide;
83
+ /**
84
+ * The pill's live edges: its centre is its option's plus the offset, and its width blends toward
85
+ * the neighbour it heads for, so it's continuous as the selection changes hands. Past the ends the
86
+ * offset is its give.
87
+ */
88
+ export declare function pillAt(slots: readonly Slot[], { index, offset }: Slide): Slot;
89
+ /** The selection pill's edges while a pan holds it (px in `box`), so the GPU can draw it there. */
90
+ export declare const livePill: (box: Element) => Slot | undefined;
91
+ /**
92
+ * Pans a row of options: the selection pill follows the fingers, the selection changes to
93
+ * whichever option the pill passes halfway to (so one pan can cross several), and when the
94
+ * gesture ends the pill springs back into its option's slot. A notch steps one option.
95
+ */
96
+ export declare function panOptions(el: HTMLElement, o: {
97
+ /** The pill's positioning box. */
98
+ readonly box: HTMLElement;
99
+ readonly pill: HTMLElement;
100
+ buttons(): readonly HTMLElement[];
101
+ /** The selected option's index, or -1. */
102
+ selected(): number;
103
+ /** `coalesce`: a later change in the same gesture, folded into its undo step. */
104
+ select(index: number, coalesce: boolean): void;
105
+ /** Puts the pill back on the selected option, with its transition. */
106
+ rest(): void;
107
+ wake?(): void;
108
+ /** The pill's narrowest width (px). */
109
+ readonly minWidth?: number;
110
+ /** Places the pill's top too, for options that wrap onto rows. */
111
+ readonly rows?: boolean;
112
+ /** A row held in its active state (`knobs-active`) while the pill is in hand. */
113
+ readonly row?: HTMLElement;
114
+ }): void;
@@ -0,0 +1,20 @@
1
+ import type { ChoiceKey } from "../core/adapter.js";
2
+ import { type Schema, type Values } from "../core/schema.js";
3
+ import type { Controls } from "../core/store.js";
4
+ export type { ChoiceKey };
5
+ export interface SwitcherOptions<S extends Schema> {
6
+ readonly field?: ChoiceKey<S>;
7
+ readonly label?: string;
8
+ readonly onUpdate?: (activeName: string) => void;
9
+ }
10
+ export declare function getSwitcherOptions<S extends Schema>(controls: Controls<S>, field?: ChoiceKey<S>): {
11
+ options: {
12
+ id: string;
13
+ name: string;
14
+ values?: Values<S>;
15
+ choose(): void;
16
+ }[];
17
+ index: number;
18
+ activeName: string;
19
+ };
20
+ export declare function attachVariantSwitcher<S extends Schema>(element: HTMLElement, controls: Controls<S>, switcherOptions?: SwitcherOptions<S>): () => void;
@@ -0,0 +1,31 @@
1
+ import { Edges } from "./motion.js";
2
+ export interface TabSpec {
3
+ readonly id: string;
4
+ readonly label: string;
5
+ }
6
+ /**
7
+ * A panel's tab strip: text tabs over a hairline, the chosen one marked by an underline that
8
+ * slides to it (its leading edge first, the trailing one following, as the selection pills do).
9
+ * The strip is one ↑↓ stop; ←/→ on it switch tabs.
10
+ */
11
+ export interface TabStrip {
12
+ readonly element: HTMLElement;
13
+ readonly bar: HTMLElement;
14
+ /** The underline's edges for the GPU overlay (px from the strip's left). */
15
+ readonly edges: Edges;
16
+ buttons: HTMLButtonElement[];
17
+ readonly tabs: readonly TabSpec[];
18
+ readonly active: string | undefined;
19
+ /** Swaps in another set of tabs, keeping the strip. */
20
+ set(tabs: readonly TabSpec[], active: string | undefined): void;
21
+ /** Marks `id` chosen and slides the underline to it (or places it, when `animate` is false). */
22
+ select(id: string, animate?: boolean): void;
23
+ /**
24
+ * A small count after a tab's label ("" takes it away); `label` is what it means, for
25
+ * assistive tech and the tooltip.
26
+ */
27
+ count(id: string, text: string, label?: string): void;
28
+ focus(options?: FocusOptions): void;
29
+ dispose(): void;
30
+ }
31
+ export declare function createTabStrip(doc: Document, choose: (id: string) => void): TabStrip;
@@ -0,0 +1,36 @@
1
+ import { type Band } from "./give.js";
2
+ /** A switch's geometry (px): the thumb's free travel at rest, its size, its inset, and how much wider a press makes it. */
3
+ export interface SwitchBox {
4
+ readonly travel: number;
5
+ readonly size: number;
6
+ readonly pad: number;
7
+ readonly press: number;
8
+ }
9
+ /** How far a held thumb's leading edge stretches past an end (px): a switch is small. */
10
+ export declare const SWITCH_GIVE = 3;
11
+ /** A lift faster than this (px/ms) is a flick: it decides by its direction. */
12
+ export declare const FLICK = 0.2;
13
+ /** Where a pressed thumb sits for a value (px from its leftmost spot); mixed waits in the middle. */
14
+ export declare const heldAt: (box: SwitchBox, on: boolean | "mixed") => number;
15
+ /** Whether a thumb at `at` reads as on: past the middle. */
16
+ export declare const onAt: (box: SwitchBox, at: number) => boolean;
17
+ /** Moves a held thumb `d` px, giving at the ends. */
18
+ export declare const pushThumb: (box: SwitchBox, from: Band, d: number) => Band;
19
+ /** Finger travel (px) over which a held thumb widens to its pressed size, so it never jumps. */
20
+ export declare const PRESS_IN = 6;
21
+ /**
22
+ * A held thumb's edges (px from the switch's left): pressed wide by `pressed` (0…1) of its press,
23
+ * toward the side it can go (so unpressed it's exactly the resting thumb), its leading edge
24
+ * giving past an end.
25
+ */
26
+ export declare function thumbEdges(box: SwitchBox, { at, give }: Band, pressed?: number): [number, number];
27
+ /** How a pan lets go: a flick goes its way, anything slower stays on the side the thumb is on. */
28
+ export declare const settleOn: (box: SwitchBox, at: number, speed: number) => boolean;
29
+ /** A held thumb's edges (px from the switch's left), so the GPU can draw it there too. */
30
+ export declare const liveThumb: (toggle: Element) => readonly [number, number] | undefined;
31
+ /** Pans `toggle`'s thumb from anywhere on `row`; `set` changes the value (`coalesce`: a later change in one gesture). */
32
+ export declare function panSwitch(row: HTMLElement, toggle: HTMLElement, o: {
33
+ value(): boolean | "mixed";
34
+ set(on: boolean, coalesce: boolean): void;
35
+ wake(): void;
36
+ }): void;
@@ -0,0 +1,6 @@
1
+ /**
2
+ * How many equal columns a toolbar's buttons take in `avail` px: as many as fit at the widest
3
+ * button's width, then balanced so the rows hold counts as even as they can (four buttons wrap as
4
+ * two and two, never three and one; five as three and two).
5
+ */
6
+ export declare function toolbarColumns(count: number, widest: number, avail: number, gap: number): number;
@@ -0,0 +1,15 @@
1
+ import { type ReactElement, type Ref } from "react";
2
+ import { type KnobsPanelProps } from "../core/adapter.js";
3
+ import type { Schema } from "../core/schema.js";
4
+ import type { Controls } from "../core/store.js";
5
+ export type { KnobsPanelProps };
6
+ export declare function useKnobs<S extends Schema, E extends HTMLElement = HTMLElement>(controls: Controls<S>): (element: E | null) => void;
7
+ export declare function Knobs<S extends Schema>({ controls, children, }: {
8
+ controls: Controls<S>;
9
+ children: ReactElement<{
10
+ ref?: Ref<HTMLElement>;
11
+ }>;
12
+ }): ReactElement<{
13
+ ref?: Ref<HTMLElement>;
14
+ }, string | import("react").JSXElementConstructor<any>>;
15
+ export declare function KnobsPanel<S extends Schema>(props: KnobsPanelProps<S>): import("react").JSX.Element | null;
@@ -0,0 +1,10 @@
1
+ import { type ReactNode } from "react";
2
+ import type { ChoiceKey } from "../core/adapter.js";
3
+ import type { Schema } from "../core/schema.js";
4
+ import type { Controls } from "../core/store.js";
5
+ export declare function VariantSwitcher<S extends Schema>({ controls, field, children, label, }: {
6
+ controls: Controls<S>;
7
+ field?: ChoiceKey<S>;
8
+ children?: ReactNode;
9
+ label?: string;
10
+ }): import("react").JSX.Element;
@@ -0,0 +1,3 @@
1
+ export { useControls, useModified, useValue } from "./useControls.js";
2
+ export { Knobs, KnobsPanel, useKnobs, type KnobsPanelProps } from "./Knobs.js";
3
+ export { VariantSwitcher } from "./VariantSwitcher.js";
@@ -0,0 +1,11 @@
1
+ import type { Schema, Values } from "../core/schema.js";
2
+ import type { Controls } from "../core/store.js";
3
+ export declare function useControls<S extends Schema>(controls: Controls<S>): Values<S>;
4
+ /**
5
+ * One key's value, re-rendering the component only when that key changes.
6
+ */
7
+ export declare function useValue<S extends Schema, K extends keyof Values<S> & string>(controls: Controls<S>, key: K): Values<S>[K];
8
+ /**
9
+ * Whether a key (or, without one, anything) differs from its default: a "modified" dot beside a heading.
10
+ */
11
+ export declare function useModified<S extends Schema>(controls: Controls<S>, key?: keyof S & string): boolean;
@@ -0,0 +1,50 @@
1
+ import { type Accessor, type JSX } from "solid-js";
2
+ import { type ChoiceKey, type KnobsPanelProps } from "../core/adapter.js";
3
+ import { type Schema, type Values } from "../core/schema.js";
4
+ import type { Controls } from "../core/store.js";
5
+ export type { KnobsPanelProps };
6
+ /**
7
+ * Reactive Solid accessor for a controls store.
8
+ * Returns `controls.getServerSnapshot()` (`locked`) during SSR, and updates reactively on the client.
9
+ */
10
+ export declare function useControls<S extends Schema>(controls: Controls<S>): Accessor<Values<S>>;
11
+ /**
12
+ * One key's value as an accessor that changes only when that key does, so a component reading
13
+ * it re-runs for its own row and no other.
14
+ */
15
+ export declare function useValue<S extends Schema, K extends keyof Values<S> & string>(controls: Controls<S>, key: K): Accessor<Values<S>[K]>;
16
+ /**
17
+ * Whether a key (or, without one, anything) differs from its default, as an accessor that
18
+ * changes only when the answer does: a "modified" dot beside a heading.
19
+ */
20
+ export declare function useModified<S extends Schema>(controls: Controls<S>, key?: keyof S & string): Accessor<boolean>;
21
+ /**
22
+ * Solid ref callback (`ref={useKnobs(controls)}`) that attaches the floating tuning chip and panel.
23
+ */
24
+ export declare function useKnobs<S extends Schema, E extends HTMLElement = HTMLElement>(controls: Controls<S>): (element: E) => void;
25
+ /**
26
+ * Solid `use:knobs={controls}` directive for attaching controls to an element.
27
+ */
28
+ export declare function knobs<S extends Schema>(element: HTMLElement, accessor: Accessor<Controls<S>>): void;
29
+ /**
30
+ * Attaches `props.controls` directly to its single HTML child element without adding wrapper DOM.
31
+ */
32
+ export declare function Knobs<S extends Schema>(props: {
33
+ controls: Controls<S>;
34
+ children: JSX.Element;
35
+ }): JSX.Element;
36
+ /**
37
+ * Mounts an inline `knobs` panel inside a Solid component tree. The panel mounts once per
38
+ * `controls`; `descriptors` and `values` flow into it as updates, so a change touches only the rows
39
+ * it changes and the panel never re-renders as a whole.
40
+ */
41
+ export declare function KnobsPanel<S extends Schema>(props: KnobsPanelProps<S>): JSX.Element;
42
+ /**
43
+ * In-place keyboard (`1–9`, `←/→`) and hover switcher for a choice field or presets/variants in Solid.
44
+ */
45
+ export declare function VariantSwitcher<S extends Schema>(props: {
46
+ controls: Controls<S>;
47
+ field?: ChoiceKey<S>;
48
+ label?: string;
49
+ children?: JSX.Element;
50
+ }): JSX.Element;
@@ -0,0 +1 @@
1
+ export { labPreset, presetCss, problemsPreset, type TokenPreset } from "../core/theme.js";
@@ -0,0 +1 @@
1
+ export declare const activityCss: string;
@@ -0,0 +1,122 @@
1
+ /** The built-in kinds, each with its icon and tint. */
2
+ export type ActivityKind = "you" | "agent" | "proposal" | "review" | "pr" | "build" | "blocked" | "shipped";
3
+ export interface ActivityEvent {
4
+ /** Unique, and the same across `set()`: rows keep their identity and glide. */
5
+ readonly id: string;
6
+ /** A built-in kind (its icon and tint), or a name of your own with `icon` and `tint`. */
7
+ readonly kind?: ActivityKind | (string & {});
8
+ /** Who: "You", "Agent", "Ryan". */
9
+ readonly actor?: string;
10
+ /** One line; it fades at its end when cut off. A group without one says "3 updates". */
11
+ readonly title?: string;
12
+ /** Trailing, already formatted ("2m", "9:41"); it rolls when it changes. */
13
+ readonly time?: string;
14
+ /** A duration chip, "▶ 0:32": seconds, or text as it should read. */
15
+ readonly duration?: number | string;
16
+ /** Small thumbnails before the time (image URLs; four at most show). */
17
+ readonly thumbs?: readonly string[];
18
+ /** SVG markup, a glyph or a node in place of the kind's icon. */
19
+ readonly icon?: string | Node;
20
+ /** A CSS colour in place of the kind's tint. */
21
+ readonly tint?: string;
22
+ /** Makes a group that opens in place ("Agent · 3 updates"). One level deep. */
23
+ readonly children?: readonly ActivityEvent[];
24
+ /** The live or latest step: its node takes a ring. */
25
+ readonly current?: boolean;
26
+ /** Quieter: past steps, minor updates. */
27
+ readonly dimmed?: boolean;
28
+ }
29
+ export interface ActivityRow {
30
+ readonly id: string;
31
+ readonly event: ActivityEvent;
32
+ /** 0 for an event, 1 for one inside an open group. */
33
+ readonly depth: 0 | 1;
34
+ readonly parent: string | null;
35
+ /** Has children: it opens and closes. */
36
+ readonly group: boolean;
37
+ readonly expanded: boolean;
38
+ }
39
+ /** The rows on show: every event, and the children of open groups right after their group. */
40
+ export declare function flattenActivity(events: readonly ActivityEvent[], expanded: ReadonlySet<string>): ActivityRow[];
41
+ /** Every event by id, children included, with its group. */
42
+ export declare function indexActivity(events: readonly ActivityEvent[]): Map<string, {
43
+ event: ActivityEvent;
44
+ parent: string | null;
45
+ }>;
46
+ export type ActivityKey = "ArrowUp" | "ArrowDown" | "ArrowLeft" | "ArrowRight" | "Home" | "End";
47
+ export declare const isActivityKey: (key: string) => key is ActivityKey;
48
+ /**
49
+ * What a key does from the selected row: ↑/↓ move (from nothing, to the first or the last),
50
+ * Home/End go to the ends, → opens a closed group and steps into an open one, ← closes an open
51
+ * group and steps out of a child to its group.
52
+ */
53
+ export declare function navigateActivity(rows: readonly ActivityRow[], selected: string | null, key: ActivityKey): {
54
+ select?: string;
55
+ toggle?: {
56
+ id: string;
57
+ expanded: boolean;
58
+ };
59
+ };
60
+ /**
61
+ * Whether the rail takes a key it navigated: ↑/↓ and Home/End whenever there are rows (at an end
62
+ * they hold the selection), ← and → only when they open, close or step. A ← or → that does
63
+ * nothing is left to the page (a review stage stepping frames, say).
64
+ */
65
+ export declare function takesActivityKey(key: ActivityKey, result: {
66
+ select?: string;
67
+ toggle?: unknown;
68
+ }, rows: number): boolean;
69
+ /**
70
+ * Where the selection goes when its row leaves the rows on show: to its group when that closed
71
+ * over it, else nowhere.
72
+ */
73
+ export declare function keepSelection(rows: readonly ActivityRow[], selected: string | null, parentOf: (id: string) => string | null | undefined): string | null;
74
+ /** What last moved the selection: keys show it as a tint, a pointer hides it. */
75
+ export type ActivityInput = "key" | "pointer" | "blur" | "code";
76
+ /**
77
+ * Whether the selection's tint shows: keys turn it on, a pointer turns it off (a click selects
78
+ * with no ring), losing focus turns it off unless the page's keys drive it, and a host's
79
+ * `select()` leaves it as it was.
80
+ */
81
+ export declare function showsSelection(showing: boolean, input: ActivityInput, keys?: "focus" | "page"): boolean;
82
+ /** Which ids came, went and stayed between two orders of rows. */
83
+ export declare function diffActivity(before: readonly string[], after: readonly string[]): {
84
+ entered: string[];
85
+ left: string[];
86
+ kept: string[];
87
+ };
88
+ /** Rows stacked top to bottom from `top`: each one's y, and the bottom of the last. */
89
+ export declare function stackRows(heights: readonly number[], top?: number, gap?: number): {
90
+ y: number[];
91
+ bottom: number;
92
+ };
93
+ export interface RailNode {
94
+ /** The node's centre. */
95
+ readonly y: number;
96
+ /** How far the line keeps clear of it on each side. */
97
+ readonly r: number;
98
+ }
99
+ /**
100
+ * The line through the nodes, as segments between neighbours that stop clear of each node, and
101
+ * only within `[from, to]` (the line's ends, which glide as rows come and go). One continuous
102
+ * thread at rest: nothing past the last node, nothing before the first.
103
+ */
104
+ export declare function railSegments(nodes: readonly RailNode[], from: number, to: number): [number, number][];
105
+ /**
106
+ * The scroll while a change takes the content from height `h0` to `h1` (now at `height`): it goes
107
+ * from `from` to where it has to end (no further down than the shorter content allows) in step
108
+ * with the height, and the space stays tall enough for it meanwhile. Closing a group at the
109
+ * scroll's end, the rows above glide down with the drawer instead of dropping in a frame.
110
+ */
111
+ export declare function holdScroll(hold: {
112
+ readonly from: number;
113
+ readonly h0: number;
114
+ readonly h1: number;
115
+ }, height: number, view: number): {
116
+ scroll: number;
117
+ space: number;
118
+ };
119
+ /** "0:32", "4:05", "1:02:03"; text passes through as it is. */
120
+ export declare function formatDuration(duration: number | string): string;
121
+ /** A group's title when it has none of its own: "1 update", "3 updates". */
122
+ export declare function groupTitle(count: number): string;
@@ -0,0 +1,67 @@
1
+ import { type ActivityEvent } from "./activity-model.js";
2
+ import { type UiLook } from "./look.js";
3
+ export interface ActivityOptions extends UiLook {
4
+ readonly events: readonly ActivityEvent[];
5
+ /** The selected event's id. */
6
+ readonly selected?: string | null;
7
+ /** Groups open from the start. */
8
+ readonly expanded?: Iterable<string>;
9
+ /** Keys and clicks moved the selection (a host's `select()` doesn't call it). */
10
+ readonly onSelect?: (id: string, event: ActivityEvent, source: "key" | "pointer") => void;
11
+ /** Enter on the selected row (on a group with no `onActivate`, Enter opens or closes it). */
12
+ readonly onActivate?: (id: string, event: ActivityEvent) => void;
13
+ readonly onToggle?: (id: string, expanded: boolean) => void;
14
+ /** Makes the duration chip a button. */
15
+ readonly onPlay?: (id: string, event: ActivityEvent) => void;
16
+ /**
17
+ * Where the newest events are: `"bottom"` (the default; oldest first) or `"top"`. `add()` puts
18
+ * an event there, and new events slide in from that side.
19
+ */
20
+ readonly newest?: "top" | "bottom";
21
+ /** Stay pinned to the newest end when it was in view as events arrive (default true). */
22
+ readonly follow?: boolean;
23
+ /**
24
+ * Where the keys come from: `"focus"` (the default) while the rail has focus, or `"page"` from
25
+ * anywhere a focused field or widget doesn't take them.
26
+ */
27
+ readonly keys?: "focus" | "page";
28
+ /** Icons (SVG markup or a glyph) for your own kinds, or in place of built-in ones. */
29
+ readonly icons?: Readonly<Record<string, string>>;
30
+ /** Tints (CSS colours) for your own kinds, or in place of built-in ones. */
31
+ readonly tints?: Readonly<Record<string, string>>;
32
+ /** The rail's accessible name. */
33
+ readonly label?: string;
34
+ /** Shown with no events. */
35
+ readonly empty?: string;
36
+ }
37
+ export interface Activity {
38
+ readonly element: HTMLElement;
39
+ /** New events: those that stay glide to their places, new ones bloom in, gone ones soften out. */
40
+ set(events: readonly ActivityEvent[]): void;
41
+ /** One more event at the newest end. */
42
+ add(event: ActivityEvent): void;
43
+ /**
44
+ * New events and where the newest are, in one change: rows glide to their new places, as with
45
+ * `set` (flipping `newest` with the events reversed reverses the rail in place).
46
+ */
47
+ update(next: {
48
+ events?: readonly ActivityEvent[];
49
+ newest?: "top" | "bottom";
50
+ }): void;
51
+ /** Selects (opening its group); `null` clears. Doesn't call `onSelect`. */
52
+ select(id: string | null): void;
53
+ readonly selected: string | null;
54
+ expand(id: string, expanded?: boolean): void;
55
+ isExpanded(id: string): boolean;
56
+ focus(): void;
57
+ destroy(): void;
58
+ /** The same as `destroy`, as the other pieces name it. */
59
+ dispose(): void;
60
+ }
61
+ /**
62
+ * A vertical rail of events: who did what, one line each, with a kind's icon on one continuous
63
+ * line. Groups open in place with their children on the same line. ↑/↓, Home/End move the
64
+ * selection (a tint that shows only once keys move it), ←/→ close and open groups, Enter
65
+ * activates. New events bloom in while the rest glide; every move is an interruptible spring.
66
+ */
67
+ export declare function activity(container: HTMLElement, options: ActivityOptions): Activity;
@@ -0,0 +1 @@
1
+ export declare const bentoCss = "\n.knobs-ui.knobs-bento{--ku-bento-radius:var(--k-ui-bento-radius,max(0px,calc(var(--ku-radius) - 5px)));\n position:relative;width:100%;height:100%;overflow:hidden auto;outline:none;background:var(--k-ui-bento-bg,var(--ku-page));\n scrollbar-width:thin;scrollbar-gutter:stable;overscroll-behavior:contain;touch-action:pan-y;user-select:none;-webkit-user-select:none;\n font:var(--ku-font-weight) var(--ku-font-size)/1.35 var(--ku-font);color:var(--ku-text);letter-spacing:normal;text-align:left}\n.knobs-bento[data-frame=\"flat\"]{--ku-bento-radius:var(--k-ui-bento-radius,0px)}\n.knobs-bento[data-pending] .knobs-bento-space{visibility:hidden}\n:where(.knobs-bento-space,.knobs-bento-space *){box-sizing:border-box}\n.knobs-bento-space{position:relative;width:100%}\n.knobs-bento-tile{position:absolute;left:0;top:0;transform-origin:0 0;overflow:hidden;border-radius:var(--ku-bento-radius);\n background:var(--ku-surface);contain:strict;cursor:zoom-in}\n.knobs-bento-tile[data-focus]{z-index:2;cursor:zoom-out}\n.knobs-bento-tile[data-focus][data-live]{cursor:auto}\n.knobs-bento-img{display:block;width:100%;height:100%;object-fit:cover;opacity:0;transition:opacity .18s cubic-bezier(.2,.8,.2,1);-webkit-user-drag:none}\n.knobs-bento-tile[data-ready] .knobs-bento-img{opacity:1}\n.knobs-bento-tile[data-instant] .knobs-bento-img{transition:none}\n.knobs-bento-body{position:absolute;inset:0;pointer-events:none}\n.knobs-bento-tile[data-focus] .knobs-bento-body{pointer-events:auto}\n.knobs-bento-label{position:absolute;left:9px;right:9px;bottom:7px;color:#fff;font-size:var(--ku-text-meta);line-height:1.3;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;\n text-shadow:0 0 6px rgba(0,0,0,.55),0 0 1px rgba(0,0,0,.5);opacity:0;pointer-events:none;transition:opacity calc(var(--ku-hover-out) * 1ms)}\n.knobs-bento:not([data-keyboard]) .knobs-bento-tile:hover .knobs-bento-label,\n.knobs-bento[data-keyboard]:focus .knobs-bento-tile[data-active] .knobs-bento-label,\n.knobs-bento-tile[data-focus] .knobs-bento-label{opacity:1;transition-duration:calc(var(--ku-hover-in) * 1ms)}\n/* A pick: a small dot in the accent, ringed in the page so it reads on any picture. */\n.knobs-bento-tile::after{content:\"\";position:absolute;top:8px;right:8px;width:8px;height:8px;border-radius:50%;background:var(--ku-accent);\n box-shadow:0 0 0 1.5px color-mix(in srgb,var(--ku-page) 70%,transparent);opacity:0;scale:.4;pointer-events:none;\n transition:opacity .11s cubic-bezier(.4,0,1,1),scale var(--ku-ease-ms) var(--ku-ease)}\n.knobs-bento-tile[aria-selected=\"true\"]::after{opacity:1;scale:1;transition:opacity .15s cubic-bezier(.2,.8,.2,1),scale var(--ku-ease-ms) var(--ku-ease)}\n.knobs-bento-wash{position:absolute;left:0;top:0;width:100px;height:100px;transform-origin:0 0;z-index:3;pointer-events:none;border-radius:var(--ku-bento-radius);\n background:color-mix(in srgb,var(--ku-accent) 12%,transparent);box-shadow:inset 0 0 0 1px color-mix(in srgb,var(--ku-accent) 45%,transparent);\n opacity:0;transition:opacity .15s}\n.knobs-bento[data-keyboard]:focus:not([data-zoomed]) .knobs-bento-wash[data-on]{opacity:1}\n.knobs-bento-scrim{position:absolute;left:0;top:0;width:100%;z-index:1;background:var(--k-ui-scrim,var(--k-ui-bento-bg,var(--ku-page)));opacity:0;pointer-events:none;cursor:zoom-out}\n.knobs-bento[data-zoomed] .knobs-bento-scrim{pointer-events:auto}\n.knobs-bento-head{position:absolute;left:0;top:0;display:flex;align-items:flex-end;padding:0 0 6px 2px;color:var(--ku-label);font-weight:var(--ku-label-weight);\n font-size:var(--ku-text-meta);white-space:nowrap;pointer-events:none}\n";
@@ -0,0 +1,96 @@
1
+ export interface BentoRect {
2
+ readonly x: number;
3
+ readonly y: number;
4
+ readonly w: number;
5
+ readonly h: number;
6
+ }
7
+ /**
8
+ * `fit`: everything on one screen in justified rows. `hero`: one screen, one tile large beside or
9
+ * above the rest. `rows`: justified rows that scroll.
10
+ */
11
+ export type BentoMode = "fit" | "hero" | "rows";
12
+ export interface BentoPlan {
13
+ readonly mode: BentoMode;
14
+ /** One per item, in the items' order, in content coordinates. */
15
+ readonly rects: readonly BentoRect[];
16
+ readonly width: number;
17
+ /** The content's height: the view's in `fit` and `hero`, every row's in `rows`. */
18
+ readonly height: number;
19
+ /** Tile area over the area it lays out in (the view for one screen, the content for rows). */
20
+ readonly coverage: number;
21
+ /** The most any tile is cropped: its shown aspect over its own, less one (0: none). */
22
+ readonly crop: number;
23
+ /** Group headings (`rows` with groups): the group, its top and the first item's index. */
24
+ readonly headers: readonly {
25
+ readonly group: string;
26
+ readonly y: number;
27
+ readonly index: number;
28
+ }[];
29
+ }
30
+ export interface BentoLayout {
31
+ /** The view's size. */
32
+ readonly width: number;
33
+ readonly height: number;
34
+ /** Between tiles (2). */
35
+ readonly gap?: number;
36
+ /** Around the tiles (the gap). */
37
+ readonly padding?: number;
38
+ /** How far a tile may be cropped to fill a row, as a fraction of its width (.08). */
39
+ readonly tolerance?: number;
40
+ /** Scrolling rows' target height (about a third of the view, 120–420). */
41
+ readonly rowHeight?: number;
42
+ /** Whether a few items may lay out on one screen (true). */
43
+ readonly fit?: boolean;
44
+ /** The shortest a tile may be on one screen before the set scrolls instead (a sixth of the view, at least 80). */
45
+ readonly minTile?: number;
46
+ /** One per item: items with the same group sit together under its heading (scrolling rows). */
47
+ readonly groups?: readonly (string | undefined)[];
48
+ /** Heading height for groups (28). */
49
+ readonly header?: number;
50
+ /** An item that asks to be the hero; otherwise the first item becomes one when that shows more. */
51
+ readonly hero?: number;
52
+ }
53
+ /** Above this many items, sets scroll rather than fit one screen. */
54
+ export declare const FIT_MAX = 24;
55
+ /** Scrolling rows' target height for a view. */
56
+ export declare const rowTarget: (width: number, height: number) => number;
57
+ interface Row {
58
+ start: number;
59
+ end: number;
60
+ h: number;
61
+ /** Widths, one per item. */
62
+ ws: number[];
63
+ }
64
+ /**
65
+ * Greedy justified rows: items join a row until it would be shorter than `target`, and the row
66
+ * ends at whichever count lands nearer the target. Every full row spans the width at its own
67
+ * height, so nothing is cropped. A row only looks at items from its own start, so a change never
68
+ * moves the rows before it. The last row spans the width when that leaves it no more than a
69
+ * quarter taller than the target, or when cropping its tiles within `tol` fills it; otherwise it
70
+ * stays at the target height, left-aligned.
71
+ */
72
+ export declare function justify(aspects: readonly number[], start: number, end: number, width: number, target: number, gap: number, tol: number): Row[];
73
+ /** Splits `xs` in order into `k` runs whose largest total is as small as possible. */
74
+ export declare function partition(xs: readonly number[], k: number): number[];
75
+ /**
76
+ * Plans the tiles for a view. A set of up to `FIT_MAX` items lays out on one screen when its
77
+ * smallest tile stays at least `minTile` tall: as justified rows (the row count that shows the
78
+ * most), or with a hero when one is asked for or the first item as a hero shows 4% more. Larger
79
+ * sets, groups and narrow views scroll in greedy justified rows near `rowHeight`. Tiles keep their
80
+ * own aspect except where a crop within `tolerance` fills a row.
81
+ */
82
+ export declare function planBento(aspects: readonly number[], layout: BentoLayout): BentoPlan;
83
+ /** The indices whose rects meet `top…bottom`; `rows` plans are in reading order, so this bisects. */
84
+ export declare function windowOf(plan: BentoPlan, top: number, bottom: number): number[];
85
+ export type BentoDirection = "left" | "right" | "up" | "down";
86
+ /**
87
+ * The tile an arrow reaches, by geometry. Left and right step along the row and wrap to the next
88
+ * or previous item at its ends; up and down go to the nearest row that way and the tile there
89
+ * whose centre is closest. Returns `from` when there's nowhere to go.
90
+ */
91
+ export declare function neighbour(rects: readonly BentoRect[], from: number, dir: BentoDirection): number;
92
+ /** `rect` fitted inside `w × h` at `aspect`, centred, `inset` from the edges. */
93
+ export declare function fitRect(aspect: number, w: number, h: number, inset?: number): BentoRect;
94
+ /** Motion blur for a tile moving at `speed` px/s: rises with speed to `max`, 0 below a crawl. */
95
+ export declare function motionBlur(speed: number, max?: number, per?: number): number;
96
+ export {};