@heyramzi/ui 1.53.0 → 1.53.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. package/package.json +1 -1
  2. package/scripts/lib/sync-core.mjs +1 -1
  3. package/scripts/sync-shadcn.mjs +11 -11
  4. package/scripts/sync-transitions.mjs +7 -7
  5. package/src/agents/agent-avatar.svelte +12 -14
  6. package/src/agents/agent-cast.svelte +2 -2
  7. package/src/agents/demo.svelte +1 -1
  8. package/src/agents/helpers.ts +1 -1
  9. package/src/agents/index.ts +2 -2
  10. package/src/agents/page-state.ts +1 -1
  11. package/src/agents/types.ts +3 -3
  12. package/src/components/copy/copy-button.test.ts +3 -1
  13. package/src/components/data-table/infinite-scroll.svelte +1 -1
  14. package/src/components/data-table/pagination.svelte +1 -1
  15. package/src/components/data-table/row-arrow.svelte +1 -1
  16. package/src/components/data-table/row-context-menu.svelte +1 -1
  17. package/src/components/data-table/sortable-head.svelte +1 -1
  18. package/src/components/data-table/table-skeleton.svelte +1 -1
  19. package/src/components/hold-to-confirm/hold-to-confirm.svelte +2 -2
  20. package/src/components/icon/icon.svelte +1 -1
  21. package/src/components/list-page/detail-empty.svelte +1 -1
  22. package/src/components/list-page/detail-header.svelte +1 -1
  23. package/src/components/list-page/detail-panel.svelte +1 -1
  24. package/src/components/list-page/detail-row.svelte +1 -1
  25. package/src/components/list-page/detail-stack.svelte +1 -1
  26. package/src/components/list-page/filter-item.svelte +1 -1
  27. package/src/components/list-page/filter-popover.svelte +1 -1
  28. package/src/components/list-page/grouped-list.svelte +1 -1
  29. package/src/components/list-page/list-page-shell.svelte +1 -1
  30. package/src/components/list-page/master-detail.svelte +1 -1
  31. package/src/components/list-page/prev-next.svelte +1 -1
  32. package/src/components/list-page/rail-body.svelte +1 -1
  33. package/src/components/list-page/rail-head.svelte +1 -1
  34. package/src/components/list-page/rail-row.svelte +1 -1
  35. package/src/components/list-page/section-label.svelte +1 -1
  36. package/src/components/list-page/toolbar-search.svelte +1 -1
  37. package/src/components/locale-switch/locale-switch.svelte +1 -1
  38. package/src/components/mark/mark.svelte +1 -1
  39. package/src/components/picker/mark-picker.svelte +1 -1
  40. package/src/components/picker/picker.svelte +1 -1
  41. package/src/components/segmented/animated-tabs.svelte +2 -2
  42. package/src/components/segmented/segment.svelte +2 -2
  43. package/src/components/segmented/segmented-filter.svelte +1 -1
  44. package/src/components/segmented/segmented-nav.svelte +1 -1
  45. package/src/components/segmented/segmented-overflow.svelte +1 -1
  46. package/src/components/segmented/segmented-track.svelte +9 -4
  47. package/src/components/star-rating-input/star-rating-input.svelte +11 -4
  48. package/src/effects/animated-beam/diagrams/BeamSvg.svelte +1 -1
  49. package/src/effects/animated-beam/path-calculator.svelte.ts +1 -1
  50. package/src/effects/border-beam/types.ts +1 -1
  51. package/src/effects/interactive-hover-button/InteractiveHoverButton.svelte +1 -1
  52. package/src/effects/shader-gradient/shaders.ts +1 -1
  53. package/src/effects/spotlight/Spotlight.svelte +1 -1
  54. package/src/icons/trades.ts +3 -3
  55. package/src/lib/sliding-pill.ts +3 -3
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@heyramzi/ui",
3
- "version": "1.53.0",
3
+ "version": "1.53.1",
4
4
  "description": "Svelte 5 UI primitives: effects, animations, and shared component CSS.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -15,7 +15,7 @@ const get = (url) => fetch(url, { headers: { "user-agent": "heyramzi-ui-sync" }
15
15
 
16
16
  export async function fetchText(url) {
17
17
  const res = await get(url);
18
- if (!res.ok) throw new Error(`${res.status} ${res.statusText} — ${url}`);
18
+ if (!res.ok) throw new Error(`${res.status} ${res.statusText}: ${url}`);
19
19
  return await res.text();
20
20
  }
21
21
 
@@ -238,14 +238,14 @@ for (const { item, payload } of fetched) {
238
238
  };
239
239
 
240
240
  if (!moved.length && !gone.length) unchanged++;
241
- else if (fresh) buckets.added.push(`${item.name} — ${files.length} files, new primitive`);
241
+ else if (fresh) buckets.added.push(`${item.name}: ${files.length} files, new primitive`);
242
242
  else if (blocked)
243
- for (const s of moved) buckets.port.push(`${label(s.path)} — upstream moved, ${s.state} here`);
243
+ for (const s of moved) buckets.port.push(`${label(s.path)}: upstream moved, ${s.state} here`);
244
244
  else if (apply.length)
245
- buckets.synced.push(`${item.name} — ${apply.map((s) => basename(s.path)).join(", ")}`);
245
+ buckets.synced.push(`${item.name}: ${apply.map((s) => basename(s.path)).join(", ")}`);
246
246
 
247
247
  for (const g of gone)
248
- buckets.removed.push(`${label(g.path)} — gone upstream${g.ours ? ", kept (ours)" : ""}`);
248
+ buckets.removed.push(`${label(g.path)}: gone upstream${g.ours ? ", kept (ours)" : ""}`);
249
249
 
250
250
  if (CHECK) continue;
251
251
 
@@ -261,14 +261,14 @@ for (const { item, payload } of fetched) {
261
261
 
262
262
  for (const name of Object.keys(previous)) {
263
263
  if (entries[name]) continue;
264
- buckets.removed.push(`${name} — gone from the registry`);
264
+ buckets.removed.push(`${name}: gone from the registry`);
265
265
  if (!CHECK) await removeDeep(`${VENDOR}/${name}`);
266
266
  }
267
267
 
268
268
  const ours = readdirSync(LOCAL, { withFileTypes: true })
269
269
  .filter((e) => e.isDirectory() && !entries[e.name] && !LOCAL_ONLY.has(e.name))
270
270
  .map((e) => e.name);
271
- for (const name of ours) buckets.unported.push(`${name} — in src/shadcn, not in the registry`);
271
+ for (const name of ours) buckets.unported.push(`${name}: in src/shadcn, not in the registry`);
272
272
 
273
273
  /* The docs site's own demo for each primitive, vendored on the same three-way
274
274
  * rule. Upstream writes them; the site renders them. */
@@ -284,7 +284,7 @@ for (const name of demoNames) {
284
284
  fetchTextIf(`${DOCS}/content/components/${name}.md`),
285
285
  ]);
286
286
  if (raw === null) {
287
- buckets.unported.push(`${name} — no demo upstream`);
287
+ buckets.unported.push(`${name}: no demo upstream`);
288
288
  continue;
289
289
  }
290
290
 
@@ -296,12 +296,12 @@ for (const name of demoNames) {
296
296
  // The imports of the demo that renders: ours when kept, upstream's when written.
297
297
  const shown = state === "diverged" ? local : next;
298
298
 
299
- if (state === "dropped") buckets.removed.push(`${label(path)} — dropped here, kept out`);
299
+ if (state === "dropped") buckets.removed.push(`${label(path)}: dropped here, kept out`);
300
300
  else manifest.push({ name, ...meta(doc, name), imports: publicImports(shown) });
301
301
 
302
302
  if (base !== raw) {
303
- if (state === "diverged") buckets.port.push(`${label(path)} — upstream moved, ours here`);
304
- else if (state !== "current") buckets.synced.push(`${name} — demo`);
303
+ if (state === "diverged") buckets.port.push(`${label(path)}: upstream moved, ours here`);
304
+ else if (state !== "current") buckets.synced.push(`${name}: demo`);
305
305
  }
306
306
  if (CHECK) continue;
307
307
 
@@ -345,7 +345,7 @@ if (!CHECK) {
345
345
 
346
346
  process.exit(
347
347
  report({
348
- title: `shadcn-svelte — ${items.length} primitives`,
348
+ title: `shadcn-svelte: ${items.length} primitives`,
349
349
  buckets,
350
350
  unchanged,
351
351
  footer: `${Object.values(entries).filter((e) => e.adapted).length} adapted here, ${LOCAL_ONLY.size} ours`,
@@ -12,7 +12,7 @@
12
12
  */
13
13
 
14
14
  /*
15
- * Paid ones are dropped here and never vendored — Ramzi, 14 Sep 2026: "the pro effects we don't need them".
15
+ * Paid ones are dropped here and never vendored (Ramzi, 14 Sep 2026: "the pro effects we don't need them").
16
16
  */
17
17
 
18
18
  /*
@@ -100,20 +100,20 @@ for (const { slug, name, css } of free) {
100
100
  const hash = sha256(css);
101
101
  entries[slug] = { name, sha256: hash, local: PORTED[slug] ?? null };
102
102
 
103
- if (!previous[slug]) buckets.added.push(`${slug} — ${name}`);
104
- else if (previous[slug].sha256 !== hash) buckets.changed.push(`${slug} — ${name}`);
103
+ if (!previous[slug]) buckets.added.push(`${slug}: ${name}`);
104
+ else if (previous[slug].sha256 !== hash) buckets.changed.push(`${slug}: ${name}`);
105
105
  else unchanged++;
106
106
 
107
- if (!PORTED[slug]) buckets.unported.push(`${slug} — nothing under src/ owns it`);
107
+ if (!PORTED[slug]) buckets.unported.push(`${slug}: nothing under src/ owns it`);
108
108
  else if (!existsSync(`${SRC}/${PORTED[slug]}`))
109
- buckets.unported.push(`${slug} — PORTED says ${PORTED[slug]}, which is missing`);
109
+ buckets.unported.push(`${slug}: PORTED says ${PORTED[slug]}, which is missing`);
110
110
 
111
111
  if (!CHECK) await writeFileDeep(`${VENDOR}/${slug}.css`, css);
112
112
  }
113
113
 
114
114
  for (const slug of Object.keys(previous)) {
115
115
  if (entries[slug]) continue;
116
- buckets.removed.push(`${slug} — gone upstream or moved to pro`);
116
+ buckets.removed.push(`${slug}: gone upstream or moved to pro`);
117
117
  if (!CHECK) await removeDeep(`${VENDOR}/${slug}.css`);
118
118
  }
119
119
 
@@ -138,7 +138,7 @@ if (!CHECK) {
138
138
 
139
139
  process.exit(
140
140
  report({
141
- title: `transitions.dev — ${free.length} free`,
141
+ title: `transitions.dev: ${free.length} free`,
142
142
  buckets,
143
143
  unchanged,
144
144
  footer: `${paid} pro skipped`,
@@ -4,15 +4,15 @@
4
4
  BotAvatarShading,
5
5
  BotAvatarState,
6
6
  BotAvatarType,
7
- } from './vendor/types';
8
- import type { AgentAvatarProps } from './types';
9
- import { botAvatarPresets, stateLabels } from './vendor/presets';
10
- import { SHAPE_PATHS, SHAPE_PARTS } from './vendor/shapes';
11
- import { autoInk, richer, shade } from './vendor/color';
12
- import { Sim, restPose } from './vendor/engine';
13
- import { draw, LIGHT_DEFAULTS, OVERSCAN, RISE, type DrawConfig } from './vendor/draw';
14
- import { bakesPending, hurry, onLanded, takeMissed, warmPlastic, type FurStyle } from './vendor/plastic';
15
- import { subscribe, pointer } from './vendor/ticker';
7
+ } from './vendor/types.js';
8
+ import type { AgentAvatarProps } from './types.js';
9
+ import { botAvatarPresets, stateLabels } from './vendor/presets.js';
10
+ import { SHAPE_PATHS, SHAPE_PARTS } from './vendor/shapes.js';
11
+ import { autoInk, richer, shade } from './vendor/color.js';
12
+ import { Sim, restPose } from './vendor/engine.js';
13
+ import { draw, LIGHT_DEFAULTS, OVERSCAN, RISE, type DrawConfig } from './vendor/draw.js';
14
+ import { bakesPending, hurry, onLanded, takeMissed, warmPlastic, type FurStyle } from './vendor/plastic.js';
15
+ import { subscribe, pointer } from './vendor/ticker.js';
16
16
  import {
17
17
  SHOW_WITHIN,
18
18
  bodyPath,
@@ -21,7 +21,7 @@
21
21
  partPaths,
22
22
  showReady,
23
23
  waiters,
24
- } from './page-state';
24
+ } from './page-state.js';
25
25
  import {
26
26
  clamp,
27
27
  clampOpt,
@@ -33,9 +33,7 @@
33
33
  reducedMotion,
34
34
  resolveTheme as themeOf,
35
35
  warmIdle,
36
- } from './helpers';
37
-
38
- type Props = AgentAvatarProps;
36
+ } from './helpers.js';
39
37
 
40
38
  let {
41
39
  name,
@@ -106,7 +104,7 @@
106
104
  'aria-hidden': ariaHidden,
107
105
  onclick: userClick,
108
106
  ...rest
109
- }: Props = $props();
107
+ }: AgentAvatarProps = $props();
110
108
 
111
109
  const shapePath = (t: BotAvatarType) => SHAPE_PATHS[t] ?? SHAPE_PATHS.clover;
112
110
 
@@ -1,8 +1,8 @@
1
1
  <script lang="ts">
2
2
  import AgentAvatar from './agent-avatar.svelte';
3
- import type { Agent, AgentState } from './types';
3
+ import type { Agent, AgentState } from './types.js';
4
4
 
5
- type Props = {
5
+ interface Props {
6
6
  /** The cast to lay out. */
7
7
  agents: Agent[];
8
8
  /** Selected agent id, when the cast is a picker. */
@@ -1,7 +1,7 @@
1
1
  <script lang="ts">
2
2
  import AgentAvatar from './agent-avatar.svelte';
3
3
  import AgentCast from './agent-cast.svelte';
4
- import type { Agent, AgentState } from './types';
4
+ import type { Agent, AgentState } from './types.js';
5
5
 
6
6
  const cast: Agent[] = [
7
7
  { id: 'ada', name: 'Ada', role: 'Researcher', shape: 'clover', color: '#41C4FF', group: 'Research' },
@@ -1,5 +1,5 @@
1
1
  /* Pure helpers for the avatar: numbers, theme, lights, idle scheduling. */
2
- import { stockLights } from './vendor/plastic';
2
+ import { stockLights } from './vendor/plastic.js';
3
3
 
4
4
  export const clamp = (v: number, lo: number, hi: number) =>
5
5
  Math.min(hi, Math.max(lo, Number.isFinite(v) ? v : 1));
@@ -1,6 +1,6 @@
1
1
  export { default as AgentAvatar } from './agent-avatar.svelte';
2
2
  export { default as AgentCast } from './agent-cast.svelte';
3
- export type { Agent, AgentAvatarProps, AgentShape } from './types';
3
+ export type { Agent, AgentAvatarProps, AgentShape } from './types.js';
4
4
  export {
5
5
  agentPresets,
6
6
  agentShapes,
@@ -8,4 +8,4 @@ export {
8
8
  agentStates,
9
9
  agentPalette,
10
10
  agentStateLabels,
11
- } from './types';
11
+ } from './types.js';
@@ -1,6 +1,6 @@
1
1
  /* What every avatar on the page shares: the batching shown-gate, so a row of
2
2
  them comes in together, the path caches, and one theme watcher. */
3
- import { bakesPending } from './vendor/plastic';
3
+ import { bakesPending } from './vendor/plastic.js';
4
4
 
5
5
  export const SHOW_WITHIN = 4000;
6
6
  export const waiters = new Map<object, { ready: () => boolean; show: () => void }>();
@@ -1,4 +1,4 @@
1
- import type { BotAvatarFace, BotAvatarProps, BotAvatarType } from './vendor/types';
1
+ import type { BotAvatarFace, BotAvatarProps, BotAvatarType } from './vendor/types.js';
2
2
 
3
3
  /** The body shapes an agent can be drawn as. */
4
4
  export type AgentShape = BotAvatarType;
@@ -11,7 +11,7 @@ export type {
11
11
  BotAvatarHat as AgentHat,
12
12
  BotAvatarGlasses as AgentGlasses,
13
13
  BotAvatarPreset as AgentPreset,
14
- } from './vendor/types';
14
+ } from './vendor/types.js';
15
15
 
16
16
  export {
17
17
  botAvatarPresets as agentPresets,
@@ -20,7 +20,7 @@ export {
20
20
  botAvatarStates as agentStates,
21
21
  botAvatarPalette as agentPalette,
22
22
  stateLabels as agentStateLabels,
23
- } from './vendor/presets';
23
+ } from './vendor/presets.js';
24
24
 
25
25
  /** One member of a cast: a name, a role, and the face they draw. */
26
26
  export type Agent = {
@@ -16,8 +16,10 @@ function render(props: Props) {
16
16
  document.body.appendChild(target);
17
17
  const app = mount(CopyButton, { target, props });
18
18
  flushSync();
19
+ const button = target.querySelector("button");
20
+ if (!button) throw new Error("CopyButton rendered no button");
19
21
  return {
20
- button: target.querySelector("button")!,
22
+ button,
21
23
  target,
22
24
  done: () => {
23
25
  void unmount(app);
@@ -4,7 +4,7 @@ import type { Snippet } from "svelte";
4
4
  import { Spinner } from "../../shadcn/spinner/index.js";
5
5
 
6
6
  // ========================= TYPES
7
- type Props = {
7
+ interface Props {
8
8
  items: T[];
9
9
  isDone: boolean;
10
10
  isLoading: boolean;
@@ -4,7 +4,7 @@ import { Button } from "../../shadcn/button/index.js";
4
4
  import { totalPages as calcTotalPages } from "./data-table.js";
5
5
 
6
6
  // ========================= TYPES
7
- type Props = {
7
+ interface Props {
8
8
  page: number;
9
9
  pageSize: number;
10
10
  total: number;
@@ -8,7 +8,7 @@ import { cn } from "@heyramzi/utils";
8
8
  // =========================
9
9
  // TYPES
10
10
  // =========================
11
- type Props = {
11
+ interface Props {
12
12
  class?: string;
13
13
  };
14
14
 
@@ -10,7 +10,7 @@ import type { Snippet } from "svelte";
10
10
  // =========================
11
11
  // TYPES
12
12
  // =========================
13
- type Props = {
13
+ interface Props {
14
14
  onDelete: () => void;
15
15
  href?: string;
16
16
  id?: string;
@@ -5,7 +5,7 @@ import { cn } from "@heyramzi/utils";
5
5
  import type { SortDirection, SortState } from "./data-table.js";
6
6
 
7
7
  // ========================= TYPES
8
- type Props = {
8
+ interface Props {
9
9
  key: keyof T & string;
10
10
  sort: SortState<T>;
11
11
  onSort: (key: keyof T & string) => void;
@@ -10,7 +10,7 @@ type ColumnDef = {
10
10
  circle?: boolean;
11
11
  };
12
12
 
13
- type Props = {
13
+ interface Props {
14
14
  /** Number of skeleton rows to render */
15
15
  rows?: number;
16
16
  /** Column definitions control skeleton widths and alignment */
@@ -6,7 +6,7 @@
6
6
  import type { HTMLButtonAttributes } from "svelte/elements";
7
7
 
8
8
  // ========================= TYPES
9
- type Props = {
9
+ interface Props extends Omit<HTMLButtonAttributes, "class"> {
10
10
  duration?: number;
11
11
  onConfirm: () => void;
12
12
  disabled?: boolean;
@@ -19,7 +19,7 @@
19
19
  class?: string;
20
20
  idle: Snippet;
21
21
  holding: Snippet;
22
- } & HTMLButtonAttributes;
22
+ }
23
23
 
24
24
  // ========================= PROPS
25
25
  let {
@@ -4,7 +4,7 @@ import type { PhosphorIcon, IconWeight } from "../../lib/icon-types.js";
4
4
  // =========================
5
5
  // TYPES
6
6
  // =========================
7
- type Props = {
7
+ interface Props {
8
8
  icon: PhosphorIcon;
9
9
  size?: number;
10
10
  class?: string;
@@ -9,7 +9,7 @@ import type { PhosphorIcon, IconWeight } from "../../lib/icon-types.js";
9
9
  // =========================
10
10
  // TYPES
11
11
  // =========================
12
- type Props = {
12
+ interface Props {
13
13
  // Shown when nothing is picked or the board is empty. Both mean "content
14
14
  // goes here", so both get a dashed `border-2` edge.
15
15
  icon?: PhosphorIcon;
@@ -8,7 +8,7 @@ import { cn } from "@heyramzi/utils";
8
8
  // =========================
9
9
  // TYPES
10
10
  // =========================
11
- type Props = {
11
+ interface Props {
12
12
  // Answers three questions in one order: what is this, what state is it in,
13
13
  // what can I do with it. Each queue page used to order them differently.
14
14
  title: string;
@@ -11,7 +11,7 @@ import type { PhosphorIcon } from "../../lib/icon-types.js";
11
11
  // =========================
12
12
  // TYPES
13
13
  // =========================
14
- type Props = {
14
+ interface Props {
15
15
  // One panel per idea in a detail pane. Hand-rolled copies drifted between
16
16
  // 8px and 12px corners, so rounded-xl lives here once.
17
17
  label?: string;
@@ -8,7 +8,7 @@ import { cn } from "@heyramzi/utils";
8
8
  // =========================
9
9
  // TYPES
10
10
  // =========================
11
- type Props = {
11
+ interface Props {
12
12
  // A hairline band inside `DetailHeader` or `DetailPanel` for the record's
13
13
  // own facts, so the page never nests cards inside cards.
14
14
  muted?: boolean;
@@ -8,7 +8,7 @@ import { cn } from "@heyramzi/utils";
8
8
  // =========================
9
9
  // TYPES
10
10
  // =========================
11
- type Props = {
11
+ interface Props {
12
12
  // A flat stack of panels at one gap. `xl:pr-1` keeps the top panel's
13
13
  // corner clear of the shell's scrollbar.
14
14
 
@@ -10,7 +10,7 @@ import { cn } from "@heyramzi/utils";
10
10
  // =========================
11
11
  // TYPES
12
12
  // =========================
13
- type Props = {
13
+ interface Props {
14
14
  checked: boolean;
15
15
  onclick: () => void;
16
16
  variant?: "checkbox" | "radio";
@@ -17,7 +17,7 @@ const DefaultFunnelIcon = FunnelIcon as unknown as PhosphorIcon;
17
17
  // =========================
18
18
  // TYPES
19
19
  // =========================
20
- type Props = {
20
+ interface Props {
21
21
  label: string;
22
22
  icon?: PhosphorIcon;
23
23
  count?: number;
@@ -10,7 +10,7 @@
10
10
  // =========================
11
11
  // TYPES
12
12
  // =========================
13
- type Props = {
13
+ interface Props {
14
14
  groups: TGroup[];
15
15
  getKey: (group: TGroup) => string;
16
16
  defaultCollapsed?: string[];
@@ -23,7 +23,7 @@
23
23
  actionHref?: string;
24
24
  };
25
25
 
26
- type Props = {
26
+ interface Props {
27
27
  isEmpty?: boolean;
28
28
  emptyState?: EmptyConfig;
29
29
  hideToolbarWhenEmpty?: boolean;
@@ -12,7 +12,7 @@ import { MASTER_DETAIL_PANE, type MasterDetailPane } from "./queue-rail.js";
12
12
  // =========================
13
13
  // TYPES
14
14
  // =========================
15
- type Props = {
15
+ interface Props {
16
16
  // The list rail (an <aside>) and the content pane. Pages style both; this
17
17
  // shell owns the two columns and the min-h-0/min-w-0 scroll containment.
18
18
  rail: Snippet;
@@ -10,7 +10,7 @@ import { stepTo } from "./step.js";
10
10
  // =========================
11
11
  // TYPES
12
12
  // =========================
13
- type Props = {
13
+ interface Props {
14
14
  /** Ids in the order the list shows them. */
15
15
  ids: readonly string[];
16
16
  current: string | null | undefined;
@@ -8,7 +8,7 @@ import { cn } from "@heyramzi/utils";
8
8
  // =========================
9
9
  // TYPES
10
10
  // =========================
11
- type Props = {
11
+ interface Props {
12
12
  // The scroller under `RailHead`. `min-h-0` makes a flex child scroll instead
13
13
  // of growing its parent, and it's the line hand-rolled rails forgot.
14
14
  children: Snippet;
@@ -9,7 +9,7 @@ import { cn } from "@heyramzi/utils";
9
9
  // =========================
10
10
  // TYPES
11
11
  // =========================
12
- type Props = {
12
+ interface Props {
13
13
  // The fixed top of a queue rail: label, filter, search. A fixed height, so
14
14
  // rows start on the same line on every queue page.
15
15
  label?: string;
@@ -10,7 +10,7 @@ import { MASTER_DETAIL_PANE, type MasterDetailPane } from "./queue-rail.js";
10
10
  // =========================
11
11
  // TYPES
12
12
  // =========================
13
- type Props = {
13
+ interface Props {
14
14
  // One row shape for every queue rail, in a fixed order: identity, preview,
15
15
  // facts. Three pages used to draw three different rows.
16
16
  title: string;
@@ -9,7 +9,7 @@ import type { PhosphorIcon } from "../../lib/icon-types.js";
9
9
  // =========================
10
10
  // TYPES
11
11
  // =========================
12
- type Props = {
12
+ interface Props {
13
13
  // The one label style for panel, rail group and sub-section headers. Retyped
14
14
  // copies drifted to a different tracking value.
15
15
  icon?: PhosphorIcon;
@@ -9,7 +9,7 @@ import { cn } from "@heyramzi/utils";
9
9
  // =========================
10
10
  // TYPES
11
11
  // =========================
12
- type Props = {
12
+ interface Props {
13
13
  value: string;
14
14
  placeholder?: string;
15
15
  size?: "sm" | "default";
@@ -13,7 +13,7 @@ type LocaleOption = {
13
13
  flag: string;
14
14
  };
15
15
 
16
- type Props = {
16
+ interface Props {
17
17
  // Wraps `SegmentedFilter` so it shares one track with every other strip. It
18
18
  // only adds each language's flag.
19
19
  value: string;
@@ -12,7 +12,7 @@ import { cn } from "@heyramzi/utils";
12
12
  * A brand's mark at glyph size: a kit icon, a channel avatar, a client logo.
13
13
  * One renderer, because three places each handled a missing file their own way.
14
14
  */
15
- type Props = {
15
+ interface Props {
16
16
  /** The file. A missing or broken one falls back to the label's initial. */
17
17
  src?: string;
18
18
  /** Read for the fallback letter. The mark itself is decorative. */
@@ -18,7 +18,7 @@ import type { PickerOption } from "./types.js";
18
18
  * wraps `Picker` because the kit strip and the channel row wrote the same two
19
19
  * snippets.
20
20
  */
21
- type Props = {
21
+ interface Props {
22
22
  options: PickerOption<V>[];
23
23
  value: V | null;
24
24
  onSelect: (value: V) => void;
@@ -12,7 +12,7 @@
12
12
  // It replaced four pickers in one sidebar, each built differently. Only one
13
13
  // had search, only one had arrow keys, and the check mark sat in three places.
14
14
 
15
- type Props = {
15
+ interface Props {
16
16
  /** Flat list. Mutually exclusive with `groups`. */
17
17
  options?: PickerOption<V>[];
18
18
  /** Titled runs, when the menu has a shape worth naming. */
@@ -40,7 +40,7 @@
40
40
  // WHY re-declare `value` and `onValueChange`: the primitive types them as a
41
41
  // bare `string`, so fourteen pages cast back to their union. Generic now, the
42
42
  // one narrowing happens here.
43
- type Props = Omit<TabsPrimitive.RootProps, "value" | "onValueChange"> & {
43
+ interface Props extends Omit<TabsPrimitive.RootProps, "value" | "onValueChange"> {
44
44
  tabs: readonly TabDef<T>[];
45
45
  value?: T;
46
46
  onValueChange?: (value: T) => void;
@@ -67,7 +67,7 @@
67
67
  max?: number;
68
68
  // What the menu segment says. Translate at the call site.
69
69
  moreLabel?: string;
70
- };
70
+ }
71
71
 
72
72
  let {
73
73
  ref = $bindable(null),
@@ -36,13 +36,13 @@ import { Mark } from "../mark/index.js";
36
36
  // =========================
37
37
  // TYPES
38
38
  // =========================
39
- type Props = SegmentContent & {
39
+ interface Props extends SegmentContent {
40
40
  /**
41
41
  * `true`/`false` when the caller owns the state. `"auto"` when bits-ui owns it
42
42
  * in a `data-state` on the trigger, and the stylesheet answers.
43
43
  */
44
44
  active?: boolean | "auto";
45
- };
45
+ }
46
46
 
47
47
  // =========================
48
48
  // PROPS
@@ -13,7 +13,7 @@ import { segmentedTriggerClass } from "./segmented.js";
13
13
  // =========================
14
14
  // TYPES
15
15
  // =========================
16
- type Props = {
16
+ interface Props {
17
17
  // WHY: picks which rows are listed, with no panel of its own. That's a
18
18
  // segmented control, not a tab set.
19
19
 
@@ -17,7 +17,7 @@ import { segmentedTriggerClass } from "./segmented.js";
17
17
  // =========================
18
18
  // TYPES
19
19
  // =========================
20
- type Props = {
20
+ interface Props {
21
21
  // WHY a third component: tabs are a `tablist`, filters a `group` of toggles,
22
22
  // and this goes to sibling pages, so it's a `nav` of links with `aria-current`.
23
23
 
@@ -18,7 +18,7 @@ import type { SegmentContent } from "./segment.svelte";
18
18
 
19
19
  // WHY never active: `splitSegments` pulls the current item into a visible slot,
20
20
  // so this never fights bits-ui's `data-state="open"`.
21
- type Props = {
21
+ interface Props {
22
22
  items: readonly (SegmentContent & { value: V })[];
23
23
  onSelect: (value: V) => void;
24
24
  /** What the segment says. Translate at the call site. */
@@ -10,7 +10,7 @@ import { slidingPill } from "../../lib/sliding-pill.js";
10
10
  // =========================
11
11
  // TYPES
12
12
  // =========================
13
- type Props = {
13
+ interface Props {
14
14
  /** Fill the row rather than sit at its natural width. */
15
15
  fill?: boolean;
16
16
  /** `pill`, a bordered well with a travelling highlight, or `underline`, a travelling rule. */
@@ -86,12 +86,17 @@ let {
86
86
  .track :global(.ui-segment[data-state="active"] + .ui-segment)::before,
87
87
  .track :global(.ui-segment[aria-current="page"] + .ui-segment)::before,
88
88
  .track :global(.ui-segment[aria-pressed="true"] + .ui-segment)::before,
89
- .track :global([data-slot="tabs-list"]:has(> .ui-segment:last-child[data-state="active"]) + .ui-segment)::before,
90
- .track :global(.ui-segment:hover)::before,
91
- .track :global(.ui-segment:hover + .ui-segment)::before {
89
+ .track :global([data-slot="tabs-list"]:has(> .ui-segment:last-child[data-state="active"]) + .ui-segment)::before {
92
90
  opacity: 0;
93
91
  }
94
92
 
93
+ @media (hover: hover) and (pointer: fine) {
94
+ .track :global(.ui-segment:hover)::before,
95
+ .track :global(.ui-segment:hover + .ui-segment)::before {
96
+ opacity: 0;
97
+ }
98
+ }
99
+
95
100
  /* The label's colour lands with the pill, on the same fast step. */
96
101
  .track :global(.ui-segment) {
97
102
  transition:
@@ -25,7 +25,7 @@ const POP_WINDOW_MS = 350 + 4 * 40;
25
25
  import { Star } from "phosphor-svelte";
26
26
  import { cn } from "@heyramzi/utils";
27
27
 
28
- type Props = {
28
+ interface Props {
29
29
  /** Current rating value (0 = unrated). */
30
30
  value: number;
31
31
  /** Maximum rating. Defaults to 5. */
@@ -187,8 +187,10 @@ function handleLeave() {
187
187
  box-shadow: 0 0 0 2px var(--color-ring);
188
188
  }
189
189
 
190
- .hit-button:not(.is-disabled):hover {
191
- transform: scale(1.1);
190
+ @media (hover: hover) and (pointer: fine) {
191
+ .hit-button:not(.is-disabled):hover {
192
+ transform: scale(1.1);
193
+ }
192
194
  }
193
195
 
194
196
  .hit-button:not(.is-disabled):active {
@@ -248,9 +250,14 @@ function handleLeave() {
248
250
  transition: none;
249
251
  }
250
252
 
251
- .hit-button:not(.is-disabled):hover,
252
253
  .hit-button:not(.is-disabled):active {
253
254
  transform: none;
254
255
  }
256
+
257
+ @media (hover: hover) and (pointer: fine) {
258
+ .hit-button:not(.is-disabled):hover {
259
+ transform: none;
260
+ }
261
+ }
255
262
  }
256
263
  </style>
@@ -2,7 +2,7 @@
2
2
  // =========================
3
3
  // PROPS
4
4
  // =========================
5
- type Props = {
5
+ interface Props {
6
6
  id: string;
7
7
  class?: string;
8
8
  width: number;
@@ -43,7 +43,7 @@ export function usePathCalculator() {
43
43
 
44
44
  // A cubic with horizontal handles, not a quadratic through a raised midpoint.
45
45
 
46
- // The quadratic put its single control point at `midY - curvature`, which for a fan into one node lands past the far end:
46
+ // The quadratic put its single control point at `midY` minus `curvature`, which for a fan into one node lands past the far end:
47
47
 
48
48
  // the top beam dove below the hub and crossed the middle beam before arriving, and three beams bundled into a lens for the last stretch.
49
49
 
@@ -24,7 +24,7 @@ export interface BorderBeamProps extends Omit<HTMLAttributes<HTMLDivElement>, "c
24
24
  // preset stop is mapped by index (cycling if shorter) so layout/timing
25
25
  // stay identical, only hues swap. Accepts hex (#rrggbb), rgb(), rgba().
26
26
  colors?: string[];
27
- // WHY: the bloom layer is the single most expensive part of the beam — a blurred conic-gradient re-rasterized every frame.
27
+ // WHY: the bloom layer is the single most expensive part of the beam: a blurred conic-gradient re-rasterized every frame.
28
28
 
29
29
  // Opt-out gives a 2-3x perf win for callers who only need the stroke and inner shimmer.
30
30
  bloom?: boolean;
@@ -7,7 +7,7 @@ import { ArrowRight } from "phosphor-svelte";
7
7
  // =========================
8
8
  // TYPES
9
9
  // =========================
10
- type Props = {
10
+ interface Props {
11
11
  href?: string;
12
12
  /** `submit` posts the form the button sits in. */
13
13
  type?: "button" | "submit";
@@ -11,7 +11,7 @@
11
11
  */
12
12
 
13
13
  /*
14
- * irradiance * BRDF_Lambert(diffuse * (1 - metalness)) = (brightness * PI) * (1 / PI) * (base * 0.8) = brightness * 0.8 * base
14
+ * irradiance * BRDF_Lambert(diffuse * (1 minus metalness)) = (brightness * PI) * (1 / PI) * (base * 0.8) = brightness * 0.8 * base
15
15
  */
16
16
 
17
17
  /*
@@ -8,7 +8,7 @@
8
8
  // =========================
9
9
  // TYPES
10
10
  // =========================
11
- type Props = {
11
+ interface Props {
12
12
  class?: string;
13
13
  fill?: string;
14
14
  };
@@ -1,13 +1,13 @@
1
1
  /**
2
- * Trade icon map — Tabler covers the domain Lucide + Phosphor miss.
2
+ * Trade icon map. Tabler covers the domain Lucide and Phosphor miss.
3
3
  */
4
4
 
5
5
  /*
6
- * Lucide/Phosphor gap audit (Sept 2026) against the 14 trade pills in the "NOT SURE? TAP WHAT YOU DO" strip: - Lucide has `hammer`, `wrench`, `hard-hat`, `bolt`, `brick-wall`,
6
+ * Lucide/Phosphor gap audit (Sept 2026) against the 14 trade pills in the "NOT SURE? TAP WHAT YOU DO" strip. Lucide has `hammer`, `wrench`, `hard-hat`, `bolt`, `brick-wall`,
7
7
  */
8
8
 
9
9
  /*
10
- * `building`, `car` — but no `trowel`, no tiling grid, no `window` cleaning `spray/squeegee`, no `crane`, no dedicated `handyman` toolset. - Phosphor has `hammer`, `wrench`, `hard-hat`, `paint-roller` — also no `trowel`.
10
+ * `building`, `car`, but no `trowel`, no tiling grid, no `window` cleaning `spray/squeegee`, no `crane`, no dedicated `handyman` toolset. Phosphor has `hammer`, `wrench`, `hard-hat`, `paint-roller`, and also no `trowel`.
11
11
  */
12
12
 
13
13
  /*
@@ -18,8 +18,8 @@ export const slidingPill: Attachment<HTMLElement> = (pill) => {
18
18
 
19
19
  let settled = false;
20
20
 
21
- function move(animate: boolean) {
22
- const active = list!.querySelector<HTMLElement>(ACTIVE);
21
+ const move = (animate: boolean) => {
22
+ const active = list.querySelector<HTMLElement>(ACTIVE);
23
23
  if (!active) {
24
24
  pill.style.opacity = "0";
25
25
  return;
@@ -37,7 +37,7 @@ export const slidingPill: Attachment<HTMLElement> = (pill) => {
37
37
  pill.style.width = `${active.offsetWidth}px`;
38
38
  void pill.offsetWidth;
39
39
  pill.style.transition = "";
40
- }
40
+ };
41
41
 
42
42
  // Position first, THEN hand the highlight over. Swapped, one frame draws both
43
43
  // the trigger's own background and a zero-width pill: a flicker on hydration.