@selvajs/ui 6.3.0 → 6.3.2

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.
@@ -42,6 +42,10 @@
42
42
  dynamicOptions = {}
43
43
  }: Props = $props();
44
44
 
45
+ const visibleGroups = $derived(
46
+ tab.groups.filter((group) => evaluateGroupVisibility(group, values))
47
+ );
48
+
45
49
  function getInputById(paramId: string): SchemaInput | undefined {
46
50
  return inputs.find((i) => i.id === paramId);
47
51
  }
@@ -82,25 +86,23 @@
82
86
  <Tabs.Content value={tab.id} class="min-h-0 p-0 flex-1">
83
87
  <ScrollArea class="h-full" orientation="vertical">
84
88
  <div class="p-4 tab-content-container">
85
- {#if tab.groups.length === 0}
89
+ {#if visibleGroups.length === 0}
86
90
  <StateDisplay type="empty" size="medium" message={t.tabNoGroups} />
87
91
  {:else}
88
92
  <div class="gap-8 flex flex-col">
89
- {#each tab.groups as group (group.id)}
90
- {#if evaluateGroupVisibility(group, values)}
91
- <Group
92
- label={group.label}
93
- description={group.description}
94
- items={group.items}
95
- columns={group.columns ?? 1}
96
- collapsed={collapsedGroups[group.id] ?? false}
97
- {values}
98
- onToggle={() => onToggleGroup(group.id)}
99
- inputSnippet={renderInput}
100
- outputSnippet={renderOutput}
101
- flat={tab.groups.length === 1}
102
- />
103
- {/if}
93
+ {#each visibleGroups as group (group.id)}
94
+ <Group
95
+ label={group.label}
96
+ description={group.description}
97
+ items={group.items}
98
+ columns={group.columns ?? 1}
99
+ collapsed={collapsedGroups[group.id] ?? false}
100
+ {values}
101
+ onToggle={() => onToggleGroup(group.id)}
102
+ inputSnippet={renderInput}
103
+ outputSnippet={renderOutput}
104
+ flat={visibleGroups.length === 1}
105
+ />
104
106
  {/each}
105
107
  </div>
106
108
  {/if}
@@ -134,6 +134,8 @@
134
134
  for (const [layerName, entries] of layerGroups) {
135
135
  flat.push({
136
136
  kind: 'layer',
137
+ // Prefixed, like the object rows below: a layer row must never share a key with
138
+ // an object row whose identity happens to read like a layer name.
137
139
  key: `layer:${layerName}`,
138
140
  top,
139
141
  height: LAYER_ROW_HEIGHT,
@@ -145,7 +147,7 @@
145
147
  for (const entry of entries) {
146
148
  flat.push({
147
149
  kind: 'object',
148
- key: entry.key,
150
+ key: `obj:${entry.rowKey}`,
149
151
  top,
150
152
  height: OBJECT_ROW_HEIGHT,
151
153
  entry
@@ -17,4 +17,10 @@ export declare function itemKey(item: LayoutItem): string;
17
17
  export declare function buildVisibilityMap(items: LayoutItem[], values: Record<string, unknown>): Record<string, VisibilityResult>;
18
18
  export declare function evaluateGroupVisibility(group: {
19
19
  visibilityCondition?: GroupVisibilityCondition;
20
+ items?: LayoutItem[];
20
21
  }, values: Record<string, unknown>): boolean;
22
+ /**
23
+ * A linebreak is always "visible" but renders as a divider only, so a group left with
24
+ * nothing but separators counts as empty.
25
+ */
26
+ export declare function hasVisibleItems(items: LayoutItem[], values: Record<string, unknown>): boolean;
@@ -74,9 +74,18 @@ export function buildVisibilityMap(items, values) {
74
74
  return map;
75
75
  }
76
76
  export function evaluateGroupVisibility(group, values) {
77
+ if (group.items && !hasVisibleItems(group.items, values))
78
+ return false;
77
79
  if (!group.visibilityCondition?.rules)
78
80
  return true;
79
81
  const { action = 'show' } = group.visibilityCondition;
80
82
  const met = evaluateCondition(group.visibilityCondition, values);
81
83
  return action === 'hide' ? !met : met;
82
84
  }
85
+ /**
86
+ * A linebreak is always "visible" but renders as a divider only, so a group left with
87
+ * nothing but separators counts as empty.
88
+ */
89
+ export function hasVisibleItems(items, values) {
90
+ return items.some((item) => item.type !== 'linebreak' && evaluateVisibility(item, values).visible);
91
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@selvajs/ui",
3
- "version": "6.3.0",
3
+ "version": "6.3.2",
4
4
  "description": "Shared UI components and utilities for Selva applications",
5
5
  "license": "MIT",
6
6
  "author": "VektorNode",
@@ -58,11 +58,11 @@
58
58
  "bits-ui": "^2.18.0",
59
59
  "svelte": "^5",
60
60
  "tailwind-variants": "^3.3.1",
61
- "three": "^0.185.1",
62
- "@selvajs/compute": "^4.1.1",
63
- "@selvajs/visualization": "^1.2.0",
61
+ "three": "^0.186.0",
62
+ "@selvajs/compute": "^4.1.3",
64
63
  "@selvajs/schemas": "^5.0.2",
65
- "@selvajs/solve": "^1.0.8"
64
+ "@selvajs/visualization": "^1.3.1",
65
+ "@selvajs/solve": "^1.1.1"
66
66
  },
67
67
  "peerDependenciesMeta": {
68
68
  "three": {
@@ -72,7 +72,7 @@
72
72
  "dependencies": {
73
73
  "@floating-ui/dom": "^1.8.0",
74
74
  "@iconify/svelte": "^5.2.1",
75
- "@lucide/svelte": "^1.35.0",
75
+ "@lucide/svelte": "^1.45.0",
76
76
  "clsx": "^2.1.1",
77
77
  "mode-watcher": "^1.1.0",
78
78
  "paneforge": "^1.0.2",
@@ -81,20 +81,20 @@
81
81
  "tw-animate-css": "^1.4.0"
82
82
  },
83
83
  "devDependencies": {
84
- "@internationalized/date": "^3.12.3",
84
+ "@internationalized/date": "^3.12.4",
85
85
  "@sveltejs/kit": "2.70.3",
86
86
  "@sveltejs/vite-plugin-svelte": "^7.3.0",
87
- "@types/three": "^0.185.4",
88
- "bits-ui": "^2.19.0",
87
+ "@types/three": "^0.186.0",
88
+ "bits-ui": "^2.19.2",
89
89
  "rimraf": "^6.1.3",
90
- "svelte": "5.56.10",
90
+ "svelte": "5.57.0",
91
91
  "tailwind-variants": "^3.3.1",
92
92
  "vitest": "^4.1.11",
93
- "@selvajs/compute": "4.1.1",
94
93
  "@selvajs/config": "0.0.4",
95
- "@selvajs/visualization": "1.2.0",
94
+ "@selvajs/visualization": "1.3.1",
96
95
  "@selvajs/schemas": "5.0.2",
97
- "@selvajs/solve": "1.0.8"
96
+ "@selvajs/compute": "4.1.3",
97
+ "@selvajs/solve": "1.1.1"
98
98
  },
99
99
  "scripts": {
100
100
  "predev": "node ../../scripts/sync-shared-assets.js",
@@ -42,6 +42,10 @@
42
42
  dynamicOptions = {}
43
43
  }: Props = $props();
44
44
 
45
+ const visibleGroups = $derived(
46
+ tab.groups.filter((group) => evaluateGroupVisibility(group, values))
47
+ );
48
+
45
49
  function getInputById(paramId: string): SchemaInput | undefined {
46
50
  return inputs.find((i) => i.id === paramId);
47
51
  }
@@ -82,25 +86,23 @@
82
86
  <Tabs.Content value={tab.id} class="min-h-0 p-0 flex-1">
83
87
  <ScrollArea class="h-full" orientation="vertical">
84
88
  <div class="p-4 tab-content-container">
85
- {#if tab.groups.length === 0}
89
+ {#if visibleGroups.length === 0}
86
90
  <StateDisplay type="empty" size="medium" message={t.tabNoGroups} />
87
91
  {:else}
88
92
  <div class="gap-8 flex flex-col">
89
- {#each tab.groups as group (group.id)}
90
- {#if evaluateGroupVisibility(group, values)}
91
- <Group
92
- label={group.label}
93
- description={group.description}
94
- items={group.items}
95
- columns={group.columns ?? 1}
96
- collapsed={collapsedGroups[group.id] ?? false}
97
- {values}
98
- onToggle={() => onToggleGroup(group.id)}
99
- inputSnippet={renderInput}
100
- outputSnippet={renderOutput}
101
- flat={tab.groups.length === 1}
102
- />
103
- {/if}
93
+ {#each visibleGroups as group (group.id)}
94
+ <Group
95
+ label={group.label}
96
+ description={group.description}
97
+ items={group.items}
98
+ columns={group.columns ?? 1}
99
+ collapsed={collapsedGroups[group.id] ?? false}
100
+ {values}
101
+ onToggle={() => onToggleGroup(group.id)}
102
+ inputSnippet={renderInput}
103
+ outputSnippet={renderOutput}
104
+ flat={visibleGroups.length === 1}
105
+ />
104
106
  {/each}
105
107
  </div>
106
108
  {/if}
@@ -134,6 +134,8 @@
134
134
  for (const [layerName, entries] of layerGroups) {
135
135
  flat.push({
136
136
  kind: 'layer',
137
+ // Prefixed, like the object rows below: a layer row must never share a key with
138
+ // an object row whose identity happens to read like a layer name.
137
139
  key: `layer:${layerName}`,
138
140
  top,
139
141
  height: LAYER_ROW_HEIGHT,
@@ -145,7 +147,7 @@
145
147
  for (const entry of entries) {
146
148
  flat.push({
147
149
  kind: 'object',
148
- key: entry.key,
150
+ key: `obj:${entry.rowKey}`,
149
151
  top,
150
152
  height: OBJECT_ROW_HEIGHT,
151
153
  entry
@@ -106,9 +106,10 @@ export function buildVisibilityMap(
106
106
  }
107
107
 
108
108
  export function evaluateGroupVisibility(
109
- group: { visibilityCondition?: GroupVisibilityCondition },
109
+ group: { visibilityCondition?: GroupVisibilityCondition; items?: LayoutItem[] },
110
110
  values: Record<string, unknown>
111
111
  ): boolean {
112
+ if (group.items && !hasVisibleItems(group.items, values)) return false;
112
113
  if (!group.visibilityCondition?.rules) return true;
113
114
 
114
115
  const { action = 'show' } = group.visibilityCondition;
@@ -116,3 +117,13 @@ export function evaluateGroupVisibility(
116
117
 
117
118
  return action === 'hide' ? !met : met;
118
119
  }
120
+
121
+ /**
122
+ * A linebreak is always "visible" but renders as a divider only, so a group left with
123
+ * nothing but separators counts as empty.
124
+ */
125
+ export function hasVisibleItems(items: LayoutItem[], values: Record<string, unknown>): boolean {
126
+ return items.some(
127
+ (item) => item.type !== 'linebreak' && evaluateVisibility(item, values).visible
128
+ );
129
+ }