@signal9/era-ui 4.14.1 → 4.15.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.
@@ -1,10 +1,9 @@
1
1
  <script lang="ts">
2
2
  import type { Snippet } from 'svelte';
3
- import { Dialog } from 'bits-ui';
4
3
  import X from '@lucide/svelte/icons/x';
5
4
  import { Bar } from '../bar';
6
- import { Button } from '../button';
7
5
  import { cn } from '../../utils/index.js';
6
+ import Close from './dialog-close.svelte';
8
7
 
9
8
  let {
10
9
  dismissable = true,
@@ -18,39 +17,25 @@
18
17
  } = $props();
19
18
  </script>
20
19
 
21
- <!-- The pane handle's chrome, verbatim, for an overlay's top bar — one grammar
22
- for every floating panel's header so a dialog and a pane read as the same
23
- kind of surface:
20
+ <!-- The pane handle's chrome — by reference, not by copy: Bar's `divider`
21
+ variant IS the cap law (outset divider, rd-md corners), shared with
22
+ Pane.Handle. What this part adds:
24
23
 
25
- · an md bar holding xxs controls, walls and gap --era-xxs-inset-md
26
- · the divider drawn OUTSIDE the box (an inset one eats a pixel of every
27
- child's bottom gap — see Pane.Handle)
28
24
  · the leading side at --era-inset-md: a title has no corner to nest, so
29
25
  it takes the text inset, the same asymmetry the pane and the
30
26
  Conversations row document
31
- · the × is the dismiss family's: an xxs Button whose tier default IS the
32
- 12px ink, destructive like Pane.Close
27
+ · the × is the dismiss family's: an xxs `Dialog.Close` whose tier
28
+ default IS the 12px ink, destructive like Pane.Close
33
29
 
34
30
  Rendered flush by Dialog.Content / Sheet.Content when passed as their
35
31
  `header` snippet, so the bar spans the panel edge to edge and the panel's
36
32
  radius follows this corner region (rd-md — the same law that sizes the
37
33
  pane's own corners). -->
38
- <Bar
39
- size="md"
40
- content="xxs"
41
- class={cn(
42
- 'shrink-0 rounded-t-none rounded-b-(--era-rd-md) pl-(--era-inset-md) [box-shadow:0_1px_0_var(--color-divider-faded)] select-none',
43
- className
44
- )}
45
- >
34
+ <Bar size="md" content="xxs" divider class={cn('pl-(--era-inset-md) select-none', className)}>
46
35
  {@render children?.()}
47
36
  {#if dismissable}
48
- <Dialog.Close>
49
- {#snippet child({ props })}
50
- <Button icon size="xxs" tone="destructive" aria-label="Close" class="ml-auto" {...props}>
51
- <X />
52
- </Button>
53
- {/snippet}
54
- </Dialog.Close>
37
+ <Close icon size="xxs" tone="destructive" aria-label="Close" class="ml-auto">
38
+ <X />
39
+ </Close>
55
40
  {/if}
56
41
  </Bar>
@@ -7,11 +7,20 @@
7
7
  let {
8
8
  ref = $bindable(null),
9
9
  children,
10
+ center,
11
+ trail,
10
12
  class: className,
11
13
  ...restProps
12
14
  }: HTMLAttributes<HTMLDivElement> & {
13
15
  ref?: HTMLDivElement | null;
14
16
  children?: Snippet;
17
+ /** Content centred on the BAR itself (a document title, a breadcrumb).
18
+ * Presence switches the handle to its three-track grid — see below. */
19
+ center?: Snippet;
20
+ /** Trailing cluster. With `center` it is the grid's third track; without,
21
+ * it renders as the familiar ml-auto cluster. The close/window controls
22
+ * belong here, always LAST — extra trailing content goes before them. */
23
+ trail?: Snippet;
15
24
  } = $props();
16
25
  </script>
17
26
 
@@ -33,25 +42,44 @@
33
42
  data-pane-handle
34
43
  size="md"
35
44
  content="xxs"
45
+ divider
36
46
  class={cn(
37
- // The divider is drawn OUTSIDE the box (0 1px 0), not inset.
47
+ // The cap chrome (outset divider, rd-md bottom corners) is Bar's `divider`
48
+ // variant — the law and its full rationale live there, shared with
49
+ // Dialog.Header and any app's own section bars.
50
+ 'select-none',
51
+ // THE CENTRE LAW — [minmax(0,1fr) minmax(0,auto) minmax(0,1fr)].
38
52
  //
39
- // An inset shadow paints over the last pixel row INSIDE the bar, which is a
40
- // row belonging to the content's own gap: a centred 18px control in a 24px
41
- // bar has 3px above and 3px below, and the inset line ate one of the three
42
- // below it. Every child of the bar therefore read 3px on top and right but
43
- // 2px on the bottom — even by the box, visibly tight to the eye, which
44
- // measures to the LINE.
53
+ // A centred title must be centred on the BAR, not on whatever space the
54
+ // sides happened to leave (a flex-1 spacer centres on the leftovers, so the
55
+ // title drifts every time the lead or trail grows — the reference terminal
56
+ // app has exactly that drift). Equal fr tracks pin the middle to the true
57
+ // centre no matter how unequal the sides are; when a side outgrows its
58
+ // share it TRUNCATES (minmax 0) instead of shoving the title, and only when
59
+ // both sides are exhausted does the centre itself start to yield. Nothing
60
+ // ever overlaps and nothing ever jumps.
45
61
  //
46
- // Outset costs no layout (so the bar keeps its full 24px content box and the
47
- // concentric 3px walls) and puts the line where it belongs: the boundary
48
- // between the bar and the content beneath it, rather than a pixel of the
49
- // bar itself. It still follows the bottom radius, so the divider curves up
50
- // at the ends exactly as before.
51
- 'shrink-0 rounded-t-none rounded-b-(--era-rd-md) [box-shadow:0_1px_0_var(--color-divider-faded)] select-none',
62
+ // Grid only when centred content exists: the flex flow is the right model
63
+ // for every other handle, and children/trail render in it untouched.
64
+ center && 'grid grid-cols-[minmax(0,1fr)_minmax(0,auto)_minmax(0,1fr)]',
52
65
  className
53
66
  )}
54
67
  {...restProps}
55
68
  >
56
- {@render children?.()}
69
+ {#if center}
70
+ <!-- gap-[inherit] on every cluster: whatever gap the Bar resolved — its
71
+ content pairing, or a caller's override (the os pane sets --era-gap) —
72
+ is also the rhythm INSIDE each cluster, so the two can never drift. -->
73
+ <div class="flex min-w-0 items-center gap-[inherit]">{@render children?.()}</div>
74
+ <!-- Anything interactive dropped in here (an editable title, a picker)
75
+ carries data-pane-control itself: the bar is the drag zone, and only
76
+ the control can know that a press on it means caret, not drag. -->
77
+ <div class="flex min-w-0 items-center justify-center gap-[inherit]">{@render center()}</div>
78
+ <div class="flex min-w-0 items-center justify-end gap-[inherit]">{@render trail?.()}</div>
79
+ {:else}
80
+ {@render children?.()}
81
+ {#if trail}
82
+ <div class="ml-auto flex shrink-0 items-center gap-[inherit]">{@render trail()}</div>
83
+ {/if}
84
+ {/if}
57
85
  </Bar>
@@ -3,6 +3,13 @@ import type { HTMLAttributes } from 'svelte/elements';
3
3
  type $$ComponentProps = HTMLAttributes<HTMLDivElement> & {
4
4
  ref?: HTMLDivElement | null;
5
5
  children?: Snippet;
6
+ /** Content centred on the BAR itself (a document title, a breadcrumb).
7
+ * Presence switches the handle to its three-track grid — see below. */
8
+ center?: Snippet;
9
+ /** Trailing cluster. With `center` it is the grid's third track; without,
10
+ * it renders as the familiar ml-auto cluster. The close/window controls
11
+ * belong here, always LAST — extra trailing content goes before them. */
12
+ trail?: Snippet;
6
13
  };
7
14
  declare const PaneHandle: import("svelte").Component<$$ComponentProps, {}, "ref">;
8
15
  type PaneHandle = ReturnType<typeof PaneHandle>;
@@ -23,6 +23,7 @@
23
23
  onDragEnd,
24
24
  onClose,
25
25
  header,
26
+ center,
26
27
  children,
27
28
  class: className,
28
29
  ...restProps
@@ -51,6 +52,8 @@
51
52
  onClose?: () => void;
52
53
  /** Extra handle-bar content, rendered after the title. */
53
54
  header?: Snippet;
55
+ /** Content centred on the bar itself — Pane.Handle's three-track grid. */
56
+ center?: Snippet;
54
57
  children?: Snippet;
55
58
  } = $props();
56
59
  </script>
@@ -58,6 +61,11 @@
58
61
  <!-- The ergonomic single-component pane: a titled handle + body, composed from
59
62
  the same Pane.Root / Handle / Content / Close parts you can assemble by hand
60
63
  for richer layouts (e.g. tabs in the handle — see Pane.Tabs). -->
64
+ <!-- Declared OUTSIDE <Root> — a snippet that is a direct child of a component
65
+ becomes one of its props, and Root has no `closeTrail`. -->
66
+ {#snippet closeTrail()}
67
+ <Close onclick={onClose} />
68
+ {/snippet}
61
69
  <Root
62
70
  bind:ref
63
71
  bind:position
@@ -71,11 +79,13 @@
71
79
  class={className}
72
80
  {...restProps}
73
81
  >
74
- <Handle>
82
+ <!-- The close cluster rides the `trail` slot, so it lands in the grid's third
83
+ track when `center` is present and in the ml-auto cluster when it isn't —
84
+ one close button, correct in both geometries. -->
85
+ <Handle {center} trail={onClose ? closeTrail : undefined}>
75
86
  {#if icon}{@const Icon = icon}<Icon class="size-(--era-h-xs) shrink-0 text-muted" />{/if}
76
87
  {#if title}<span class="text-body text-bright">{title}</span>{/if}
77
88
  {@render header?.()}
78
- {#if onClose}<Close onclick={onClose} class="ml-auto" />{/if}
79
89
  </Handle>
80
90
  {#if children}
81
91
  <Content>
@@ -33,6 +33,8 @@ type $$ComponentProps = HTMLAttributes<HTMLDivElement> & {
33
33
  onClose?: () => void;
34
34
  /** Extra handle-bar content, rendered after the title. */
35
35
  header?: Snippet;
36
+ /** Content centred on the bar itself — Pane.Handle's three-track grid. */
37
+ center?: Snippet;
36
38
  children?: Snippet;
37
39
  };
38
40
  declare const Pane: Component<$$ComponentProps, {}, "position" | "size" | "ref">;
@@ -9,11 +9,15 @@
9
9
  export const sheetVariants = tv({
10
10
  base: 'fixed z-50 flex flex-col border border-divider bg-(--era-surface-bg) shadow-(--era-shadow-lg) glass-blur',
11
11
  variants: {
12
+ // The two axes meet at the corners: the SIDE picks which corners are
13
+ // detached from the screen edge, the HEADER picks their radius. Stated
14
+ // once each — the side rounds a variable (--sheet-rd) and the header
15
+ // sets it — instead of the 8-row side x header product it replaces.
12
16
  side: {
13
- top: 'inset-x-0 top-0 max-h-[85dvh]',
14
- bottom: 'inset-x-0 bottom-0 max-h-[85dvh]',
15
- left: 'inset-y-0 left-0 w-[min(20rem,85vw)]',
16
- right: 'inset-y-0 right-0 w-[min(20rem,85vw)]'
17
+ top: 'inset-x-0 top-0 max-h-[85dvh] rounded-b-(--sheet-rd)',
18
+ bottom: 'inset-x-0 bottom-0 max-h-[85dvh] rounded-t-(--sheet-rd)',
19
+ left: 'inset-y-0 left-0 w-[min(20rem,85vw)] rounded-r-(--sheet-rd)',
20
+ right: 'inset-y-0 right-0 w-[min(20rem,85vw)] rounded-l-(--sheet-rd)'
17
21
  },
18
22
  /**
19
23
  * With a header the sheet is sectioned (the bar flush, the BODY scrolls
@@ -22,18 +26,11 @@
22
26
  * holding xxs controls, the pane's own corner law. Without one, the
23
27
  * padded rd-lg surface is unchanged.
24
28
  */
25
- header: { true: '', false: 'gap-(--era-gap) overflow-y-auto p-(--era-pad-md)' }
29
+ header: {
30
+ true: '[--sheet-rd:var(--era-rd-md)]',
31
+ false: '[--sheet-rd:var(--era-rd-lg)] gap-(--era-gap) overflow-y-auto p-(--era-pad-md)'
32
+ }
26
33
  },
27
- compoundVariants: [
28
- { side: 'top', header: false, class: 'rounded-b-(--era-rd-lg)' },
29
- { side: 'bottom', header: false, class: 'rounded-t-(--era-rd-lg)' },
30
- { side: 'left', header: false, class: 'rounded-r-(--era-rd-lg)' },
31
- { side: 'right', header: false, class: 'rounded-l-(--era-rd-lg)' },
32
- { side: 'top', header: true, class: 'rounded-b-(--era-rd-md)' },
33
- { side: 'bottom', header: true, class: 'rounded-t-(--era-rd-md)' },
34
- { side: 'left', header: true, class: 'rounded-r-(--era-rd-md)' },
35
- { side: 'right', header: true, class: 'rounded-l-(--era-rd-md)' }
36
- ],
37
34
  defaultVariants: { side: 'bottom', header: false }
38
35
  });
39
36
  </script>
@@ -41,6 +38,7 @@
41
38
  <script lang="ts">
42
39
  import type { Snippet } from 'svelte';
43
40
  import { Dialog } from 'bits-ui';
41
+ import Overlay from '../dialog/dialog-overlay.svelte';
44
42
 
45
43
  let {
46
44
  ref = $bindable(null),
@@ -58,7 +56,7 @@
58
56
  </script>
59
57
 
60
58
  <Dialog.Portal>
61
- <Dialog.Overlay class="fixed inset-0 z-50 bg-(--era-overlay-bg)" />
59
+ <Overlay />
62
60
  <Dialog.Content
63
61
  bind:ref
64
62
  class={cn(sheetVariants({ side, header: header != null }), className)}
@@ -1,10 +1,10 @@
1
1
  import { type PartProps } from '../../utils/index.js';
2
2
  export declare const sheetVariants: import("tailwind-variants").TVReturnType<{
3
3
  side: {
4
- top: "inset-x-0 top-0 max-h-[85dvh]";
5
- bottom: "inset-x-0 bottom-0 max-h-[85dvh]";
6
- left: "inset-y-0 left-0 w-[min(20rem,85vw)]";
7
- right: "inset-y-0 right-0 w-[min(20rem,85vw)]";
4
+ top: "inset-x-0 top-0 max-h-[85dvh] rounded-b-(--sheet-rd)";
5
+ bottom: "inset-x-0 bottom-0 max-h-[85dvh] rounded-t-(--sheet-rd)";
6
+ left: "inset-y-0 left-0 w-[min(20rem,85vw)] rounded-r-(--sheet-rd)";
7
+ right: "inset-y-0 right-0 w-[min(20rem,85vw)] rounded-l-(--sheet-rd)";
8
8
  };
9
9
  /**
10
10
  * With a header the sheet is sectioned (the bar flush, the BODY scrolls
@@ -14,15 +14,15 @@ export declare const sheetVariants: import("tailwind-variants").TVReturnType<{
14
14
  * padded rd-lg surface is unchanged.
15
15
  */
16
16
  header: {
17
- true: "";
18
- false: "gap-(--era-gap) overflow-y-auto p-(--era-pad-md)";
17
+ true: "[--sheet-rd:var(--era-rd-md)]";
18
+ false: "[--sheet-rd:var(--era-rd-lg)] gap-(--era-gap) overflow-y-auto p-(--era-pad-md)";
19
19
  };
20
20
  }, undefined, "fixed z-50 flex flex-col border border-divider bg-(--era-surface-bg) shadow-(--era-shadow-lg) glass-blur", {
21
21
  side: {
22
- top: "inset-x-0 top-0 max-h-[85dvh]";
23
- bottom: "inset-x-0 bottom-0 max-h-[85dvh]";
24
- left: "inset-y-0 left-0 w-[min(20rem,85vw)]";
25
- right: "inset-y-0 right-0 w-[min(20rem,85vw)]";
22
+ top: "inset-x-0 top-0 max-h-[85dvh] rounded-b-(--sheet-rd)";
23
+ bottom: "inset-x-0 bottom-0 max-h-[85dvh] rounded-t-(--sheet-rd)";
24
+ left: "inset-y-0 left-0 w-[min(20rem,85vw)] rounded-r-(--sheet-rd)";
25
+ right: "inset-y-0 right-0 w-[min(20rem,85vw)] rounded-l-(--sheet-rd)";
26
26
  };
27
27
  /**
28
28
  * With a header the sheet is sectioned (the bar flush, the BODY scrolls
@@ -32,15 +32,15 @@ export declare const sheetVariants: import("tailwind-variants").TVReturnType<{
32
32
  * padded rd-lg surface is unchanged.
33
33
  */
34
34
  header: {
35
- true: "";
36
- false: "gap-(--era-gap) overflow-y-auto p-(--era-pad-md)";
35
+ true: "[--sheet-rd:var(--era-rd-md)]";
36
+ false: "[--sheet-rd:var(--era-rd-lg)] gap-(--era-gap) overflow-y-auto p-(--era-pad-md)";
37
37
  };
38
38
  }, undefined, import("tailwind-variants").TVReturnTypeLike<{
39
39
  side: {
40
- top: "inset-x-0 top-0 max-h-[85dvh]";
41
- bottom: "inset-x-0 bottom-0 max-h-[85dvh]";
42
- left: "inset-y-0 left-0 w-[min(20rem,85vw)]";
43
- right: "inset-y-0 right-0 w-[min(20rem,85vw)]";
40
+ top: "inset-x-0 top-0 max-h-[85dvh] rounded-b-(--sheet-rd)";
41
+ bottom: "inset-x-0 bottom-0 max-h-[85dvh] rounded-t-(--sheet-rd)";
42
+ left: "inset-y-0 left-0 w-[min(20rem,85vw)] rounded-r-(--sheet-rd)";
43
+ right: "inset-y-0 right-0 w-[min(20rem,85vw)] rounded-l-(--sheet-rd)";
44
44
  };
45
45
  /**
46
46
  * With a header the sheet is sectioned (the bar flush, the BODY scrolls
@@ -50,8 +50,8 @@ export declare const sheetVariants: import("tailwind-variants").TVReturnType<{
50
50
  * padded rd-lg surface is unchanged.
51
51
  */
52
52
  header: {
53
- true: "";
54
- false: "gap-(--era-gap) overflow-y-auto p-(--era-pad-md)";
53
+ true: "[--sheet-rd:var(--era-rd-md)]";
54
+ false: "[--sheet-rd:var(--era-rd-lg)] gap-(--era-gap) overflow-y-auto p-(--era-pad-md)";
55
55
  };
56
56
  }, undefined>>;
57
57
  import type { Snippet } from 'svelte';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@signal9/era-ui",
3
- "version": "4.14.1",
3
+ "version": "4.15.1",
4
4
  "scripts": {
5
5
  "dev": "vite dev --host",
6
6
  "build": "vite build && npm run prepack",
@@ -90,12 +90,12 @@
90
90
  "devDependencies": {
91
91
  "@eslint/compat": "^2.1.0",
92
92
  "@eslint/js": "^9.39.2",
93
- "@playwright/test": "^1.62.0",
93
+ "@playwright/test": "^1.62.1",
94
94
  "@semantic-release/changelog": "^6.0.3",
95
95
  "@semantic-release/exec": "^7.1.0",
96
96
  "@semantic-release/git": "^10.0.1",
97
97
  "@sveltejs/adapter-cloudflare": "^7.2.9",
98
- "@sveltejs/kit": "^2.70.1",
98
+ "@sveltejs/kit": "^2.70.2",
99
99
  "@sveltejs/package": "^2.5.8",
100
100
  "@sveltejs/vite-plugin-svelte": "^6.2.4",
101
101
  "@tailwindcss/cli": "^4.3.3",
@@ -103,19 +103,19 @@
103
103
  "@threlte/core": "^8.5.16",
104
104
  "@threlte/extras": "^9.21.0",
105
105
  "@tiptap/core": "^3.29.0",
106
- "@tiptap/extension-code-block-lowlight": "^3.29.0",
107
- "@tiptap/extension-details": "^3.29.0",
108
- "@tiptap/extension-highlight": "^3.29.0",
109
- "@tiptap/extension-image": "^3.29.0",
110
- "@tiptap/extension-placeholder": "^3.29.0",
111
- "@tiptap/extension-table": "^3.29.0",
112
- "@tiptap/extension-task-item": "^3.29.0",
113
- "@tiptap/extension-task-list": "^3.29.0",
114
- "@tiptap/extension-typography": "^3.29.0",
115
- "@tiptap/extension-unique-id": "^3.29.0",
106
+ "@tiptap/extension-code-block-lowlight": "^3.29.2",
107
+ "@tiptap/extension-details": "^3.29.2",
108
+ "@tiptap/extension-highlight": "^3.29.2",
109
+ "@tiptap/extension-image": "^3.29.2",
110
+ "@tiptap/extension-placeholder": "^3.29.2",
111
+ "@tiptap/extension-table": "^3.29.2",
112
+ "@tiptap/extension-task-item": "^3.29.2",
113
+ "@tiptap/extension-task-list": "^3.29.2",
114
+ "@tiptap/extension-typography": "^3.29.2",
115
+ "@tiptap/extension-unique-id": "^3.29.2",
116
116
  "@tiptap/pm": "^3.29.0",
117
- "@tiptap/starter-kit": "^3.29.0",
118
- "@tiptap/suggestion": "^3.29.0",
117
+ "@tiptap/starter-kit": "^3.29.2",
118
+ "@tiptap/suggestion": "^3.29.2",
119
119
  "@types/node": "^24.13.3",
120
120
  "@types/three": "^0.184.1",
121
121
  "@xyflow/svelte": "^1.6.2",
@@ -130,9 +130,9 @@
130
130
  "prettier-plugin-tailwindcss": "^0.7.4",
131
131
  "publint": "^0.3.22",
132
132
  "semantic-release": "^25.0.8",
133
- "shiki": "^4.3.1",
133
+ "shiki": "^4.4.1",
134
134
  "svelte": "^5.56.8",
135
- "svelte-check": "^4.7.3",
135
+ "svelte-check": "^4.7.4",
136
136
  "tailwindcss": "^4.1.18",
137
137
  "three": "^0.184.0",
138
138
  "tiptap-markdown": "^0.9.0",
@@ -140,7 +140,7 @@
140
140
  "typescript-eslint": "^8.65.0",
141
141
  "vite": "^7.3.6",
142
142
  "vite-plugin-devtools-json": "^1.1.0",
143
- "wrangler": "^4.114.0"
143
+ "wrangler": "^4.118.0"
144
144
  },
145
145
  "keywords": [
146
146
  "svelte"
@@ -153,7 +153,7 @@
153
153
  "url": "git+https://github.com/sig-nine/era-ui.git"
154
154
  },
155
155
  "dependencies": {
156
- "@internationalized/date": "^3.12.2",
156
+ "@internationalized/date": "^3.12.3",
157
157
  "@lucide/svelte": "^0.577.0",
158
158
  "@neodrag/svelte": "^3.0.0-next.8",
159
159
  "bits-ui": "^2.18.1",