@tai42/studio-sdk 15.0.0 → 16.0.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.
- package/dist/components/explorer-body.d.ts +49 -0
- package/dist/components/explorer-body.d.ts.map +1 -0
- package/dist/components/explorer-body.js +67 -0
- package/dist/components/explorer-body.js.map +1 -0
- package/dist/components/explorer-page-size.d.ts +4 -0
- package/dist/components/explorer-page-size.d.ts.map +1 -0
- package/dist/components/explorer-page-size.js +36 -0
- package/dist/components/explorer-page-size.js.map +1 -0
- package/dist/components/explorer-tag-filter.d.ts +17 -0
- package/dist/components/explorer-tag-filter.d.ts.map +1 -0
- package/dist/components/explorer-tag-filter.js +29 -0
- package/dist/components/explorer-tag-filter.js.map +1 -0
- package/dist/components/explorer-tags.d.ts +22 -0
- package/dist/components/explorer-tags.d.ts.map +1 -0
- package/dist/components/explorer-tags.js +51 -0
- package/dist/components/explorer-tags.js.map +1 -0
- package/dist/components/explorer-view.d.ts +3 -17
- package/dist/components/explorer-view.d.ts.map +1 -1
- package/dist/components/explorer-view.js +53 -170
- package/dist/components/explorer-view.js.map +1 -1
- package/dist/components/extension-combo-builder.d.ts +0 -5
- package/dist/components/extension-combo-builder.d.ts.map +1 -1
- package/dist/components/extension-combo-builder.js +4 -42
- package/dist/components/extension-combo-builder.js.map +1 -1
- package/dist/components/extension-combo-parts.d.ts +44 -0
- package/dist/components/extension-combo-parts.d.ts.map +1 -0
- package/dist/components/extension-combo-parts.js +57 -0
- package/dist/components/extension-combo-parts.js.map +1 -0
- package/dist/components/fleet-report.d.ts.map +1 -1
- package/dist/components/fleet-report.js +51 -37
- package/dist/components/fleet-report.js.map +1 -1
- package/dist/components/icons/chrome-marks.d.ts +50 -0
- package/dist/components/icons/chrome-marks.d.ts.map +1 -0
- package/dist/components/icons/chrome-marks.js +51 -0
- package/dist/components/icons/chrome-marks.js.map +1 -0
- package/dist/components/icons/icon-frame.d.ts +29 -0
- package/dist/components/icons/icon-frame.d.ts.map +1 -0
- package/dist/components/icons/icon-frame.js +19 -0
- package/dist/components/icons/icon-frame.js.map +1 -0
- package/dist/components/icons/index.d.ts +40 -0
- package/dist/components/icons/index.d.ts.map +1 -0
- package/dist/components/icons/index.js +6 -0
- package/dist/components/icons/index.js.map +1 -0
- package/dist/components/icons/nav-map.d.ts +14 -0
- package/dist/components/icons/nav-map.d.ts.map +1 -0
- package/dist/components/icons/nav-map.js +28 -0
- package/dist/components/icons/nav-map.js.map +1 -0
- package/dist/components/icons/nav-marks.d.ts +44 -0
- package/dist/components/icons/nav-marks.d.ts.map +1 -0
- package/dist/components/icons/nav-marks.js +45 -0
- package/dist/components/icons/nav-marks.js.map +1 -0
- package/dist/components/icons/status-marks.d.ts +21 -0
- package/dist/components/icons/status-marks.d.ts.map +1 -0
- package/dist/components/icons/status-marks.js +22 -0
- package/dist/components/icons/status-marks.js.map +1 -0
- package/dist/components/icons/theme-marks.d.ts +9 -0
- package/dist/components/icons/theme-marks.d.ts.map +1 -0
- package/dist/components/icons/theme-marks.js +10 -0
- package/dist/components/icons/theme-marks.js.map +1 -0
- package/dist/components/json-diff.d.ts.map +1 -1
- package/dist/components/json-diff.js +1 -3
- package/dist/components/json-diff.js.map +1 -1
- package/dist/components/json-tree-context.d.ts +13 -0
- package/dist/components/json-tree-context.d.ts.map +1 -0
- package/dist/components/json-tree-context.js +15 -0
- package/dist/components/json-tree-context.js.map +1 -0
- package/dist/components/json-tree-model.d.ts +56 -0
- package/dist/components/json-tree-model.d.ts.map +1 -0
- package/dist/components/json-tree-model.js +125 -0
- package/dist/components/json-tree-model.js.map +1 -0
- package/dist/components/json-tree-nodes.d.ts +30 -0
- package/dist/components/json-tree-nodes.d.ts.map +1 -0
- package/dist/components/json-tree-nodes.js +118 -0
- package/dist/components/json-tree-nodes.js.map +1 -0
- package/dist/components/json-tree.d.ts.map +1 -1
- package/dist/components/json-tree.js +7 -295
- package/dist/components/json-tree.js.map +1 -1
- package/dist/components/json-value-format.d.ts +20 -0
- package/dist/components/json-value-format.d.ts.map +1 -0
- package/dist/components/json-value-format.js +63 -0
- package/dist/components/json-value-format.js.map +1 -0
- package/dist/components/markdown-blocks.d.ts +38 -0
- package/dist/components/markdown-blocks.d.ts.map +1 -0
- package/dist/components/markdown-blocks.js +168 -0
- package/dist/components/markdown-blocks.js.map +1 -0
- package/dist/components/markdown-inline.d.ts +24 -0
- package/dist/components/markdown-inline.d.ts.map +1 -0
- package/dist/components/markdown-inline.js +257 -0
- package/dist/components/markdown-inline.js.map +1 -0
- package/dist/components/markdown.d.ts +3 -52
- package/dist/components/markdown.d.ts.map +1 -1
- package/dist/components/markdown.js +4 -376
- package/dist/components/markdown.js.map +1 -1
- package/dist/components/observer-registry.d.ts +38 -0
- package/dist/components/observer-registry.d.ts.map +1 -0
- package/dist/components/observer-registry.js +131 -0
- package/dist/components/observer-registry.js.map +1 -0
- package/dist/components/overflow-measure.d.ts +38 -0
- package/dist/components/overflow-measure.d.ts.map +1 -0
- package/dist/components/overflow-measure.js +54 -0
- package/dist/components/overflow-measure.js.map +1 -0
- package/dist/components/prose-regions.d.ts +81 -0
- package/dist/components/prose-regions.d.ts.map +1 -0
- package/dist/components/prose-regions.js +128 -0
- package/dist/components/prose-regions.js.map +1 -0
- package/dist/components/scroll-region.d.ts.map +1 -1
- package/dist/components/scroll-region.js +3 -298
- package/dist/components/scroll-region.js.map +1 -1
- package/dist/components/tab-stop-parse.d.ts +53 -0
- package/dist/components/tab-stop-parse.d.ts.map +1 -0
- package/dist/components/tab-stop-parse.js +202 -0
- package/dist/components/tab-stop-parse.js.map +1 -0
- package/dist/components/templated-text-field.d.ts.map +1 -1
- package/dist/components/templated-text-field.js +10 -148
- package/dist/components/templated-text-field.js.map +1 -1
- package/dist/components/templated-text-kwargs-editor.d.ts +24 -0
- package/dist/components/templated-text-kwargs-editor.d.ts.map +1 -0
- package/dist/components/templated-text-kwargs-editor.js +48 -0
- package/dist/components/templated-text-kwargs-editor.js.map +1 -0
- package/dist/components/templated-text-kwargs.d.ts +37 -0
- package/dist/components/templated-text-kwargs.d.ts.map +1 -0
- package/dist/components/templated-text-kwargs.js +66 -0
- package/dist/components/templated-text-kwargs.js.map +1 -0
- package/dist/components/templated-text-source.d.ts +33 -0
- package/dist/components/templated-text-source.d.ts.map +1 -0
- package/dist/components/templated-text-source.js +53 -0
- package/dist/components/templated-text-source.js.map +1 -0
- package/dist/components/tool-picker-parts.d.ts +39 -0
- package/dist/components/tool-picker-parts.d.ts.map +1 -0
- package/dist/components/tool-picker-parts.js +87 -0
- package/dist/components/tool-picker-parts.js.map +1 -0
- package/dist/components/tool-picker.d.ts +1 -2
- package/dist/components/tool-picker.d.ts.map +1 -1
- package/dist/components/tool-picker.js +4 -66
- package/dist/components/tool-picker.js.map +1 -1
- package/dist/components/version-history-panel.d.ts.map +1 -1
- package/dist/components/version-history-panel.js +7 -34
- package/dist/components/version-history-panel.js.map +1 -1
- package/dist/components/version-history-rows.d.ts +48 -0
- package/dist/components/version-history-rows.d.ts.map +1 -0
- package/dist/components/version-history-rows.js +51 -0
- package/dist/components/version-history-rows.js.map +1 -0
- package/dist/guards.d.ts +6 -0
- package/dist/guards.d.ts.map +1 -0
- package/dist/guards.js +8 -0
- package/dist/guards.js.map +1 -0
- package/dist/hooks/interactions-connection.d.ts +43 -0
- package/dist/hooks/interactions-connection.d.ts.map +1 -0
- package/dist/hooks/interactions-connection.js +171 -0
- package/dist/hooks/interactions-connection.js.map +1 -0
- package/dist/hooks/interactions-stream.d.ts +80 -0
- package/dist/hooks/interactions-stream.d.ts.map +1 -0
- package/dist/hooks/interactions-stream.js +233 -0
- package/dist/hooks/interactions-stream.js.map +1 -0
- package/dist/hooks/useApi.d.ts +2409 -2293
- package/dist/hooks/useApi.d.ts.map +1 -1
- package/dist/hooks/useSse.d.ts +3 -6
- package/dist/hooks/useSse.d.ts.map +1 -1
- package/dist/hooks/useSse.js +38 -451
- package/dist/hooks/useSse.js.map +1 -1
- package/dist/navigation/app-link.d.ts +25 -0
- package/dist/navigation/app-link.d.ts.map +1 -0
- package/dist/navigation/app-link.js +33 -0
- package/dist/navigation/app-link.js.map +1 -0
- package/dist/navigation/context.d.ts +3 -20
- package/dist/navigation/context.d.ts.map +1 -1
- package/dist/navigation/context.js +7 -267
- package/dist/navigation/context.js.map +1 -1
- package/dist/navigation/entry-state.d.ts +23 -0
- package/dist/navigation/entry-state.d.ts.map +1 -0
- package/dist/navigation/entry-state.js +39 -0
- package/dist/navigation/entry-state.js.map +1 -0
- package/dist/navigation/guard-registry.d.ts +30 -0
- package/dist/navigation/guard-registry.d.ts.map +1 -0
- package/dist/navigation/guard-registry.js +46 -0
- package/dist/navigation/guard-registry.js.map +1 -0
- package/dist/navigation/guarded-navigation.d.ts +24 -0
- package/dist/navigation/guarded-navigation.d.ts.map +1 -0
- package/dist/navigation/guarded-navigation.js +102 -0
- package/dist/navigation/guarded-navigation.js.map +1 -0
- package/dist/navigation/index.d.ts +3 -2
- package/dist/navigation/index.d.ts.map +1 -1
- package/dist/navigation/index.js +2 -1
- package/dist/navigation/index.js.map +1 -1
- package/dist/navigation/types.d.ts +8 -11
- package/dist/navigation/types.d.ts.map +1 -1
- package/dist/navigation/use-back-forward-guard.d.ts +5 -0
- package/dist/navigation/use-back-forward-guard.d.ts.map +1 -0
- package/dist/navigation/use-back-forward-guard.js +82 -0
- package/dist/navigation/use-back-forward-guard.js.map +1 -0
- package/dist/plugin/registry.d.ts +6 -15
- package/dist/plugin/registry.d.ts.map +1 -1
- package/dist/plugin/registry.js +83 -75
- package/dist/plugin/registry.js.map +1 -1
- package/dist/schema-editor/SchemaEditor.d.ts +8 -1
- package/dist/schema-editor/SchemaEditor.d.ts.map +1 -1
- package/dist/schema-editor/SchemaEditor.js +2 -2
- package/dist/schema-editor/SchemaEditor.js.map +1 -1
- package/dist/schema-form/SecretRefField.d.ts +2 -3
- package/dist/schema-form/SecretRefField.d.ts.map +1 -1
- package/dist/schema-form/SecretRefField.js +23 -12
- package/dist/schema-form/SecretRefField.js.map +1 -1
- package/dist/schema-form/classify.d.ts +1 -122
- package/dist/schema-form/classify.d.ts.map +1 -1
- package/dist/schema-form/classify.js +174 -237
- package/dist/schema-form/classify.js.map +1 -1
- package/dist/schema-form/default-value.d.ts.map +1 -1
- package/dist/schema-form/default-value.js +42 -19
- package/dist/schema-form/default-value.js.map +1 -1
- package/dist/schema-form/enum-field.d.ts +1 -1
- package/dist/schema-form/enum-field.d.ts.map +1 -1
- package/dist/schema-form/field-model.d.ts +121 -0
- package/dist/schema-form/field-model.d.ts.map +1 -0
- package/dist/schema-form/field-model.js +2 -0
- package/dist/schema-form/field-model.js.map +1 -0
- package/dist/schema-form/field-node.d.ts.map +1 -1
- package/dist/schema-form/field-node.js +25 -14
- package/dist/schema-form/field-node.js.map +1 -1
- package/dist/schema-form/media-field.d.ts +1 -1
- package/dist/schema-form/media-field.d.ts.map +1 -1
- package/dist/schema-form/media-field.js +47 -28
- package/dist/schema-form/media-field.js.map +1 -1
- package/dist/schema-form/string-annotations.d.ts +20 -0
- package/dist/schema-form/string-annotations.d.ts.map +1 -0
- package/dist/schema-form/string-annotations.js +105 -0
- package/dist/schema-form/string-annotations.js.map +1 -0
- package/dist/schema-form/string-field.d.ts +1 -1
- package/dist/schema-form/string-field.d.ts.map +1 -1
- package/dist/schema-form/union-field.d.ts +1 -1
- package/dist/schema-form/union-field.d.ts.map +1 -1
- package/dist/schema-form/union-field.js.map +1 -1
- package/dist/schema-form/union.d.ts +1 -1
- package/dist/schema-form/union.d.ts.map +1 -1
- package/dist/schema-form/validate.d.ts.map +1 -1
- package/dist/schema-form/validate.js +126 -105
- package/dist/schema-form/validate.js.map +1 -1
- package/dist/state-binding/AdapterMapping.d.ts.map +1 -1
- package/dist/state-binding/AdapterMapping.js +0 -0
- package/dist/state-binding/AdapterMapping.js.map +1 -1
- package/dist/state-binding/StateAttachRow.d.ts.map +1 -1
- package/dist/state-binding/StateAttachRow.js +52 -36
- package/dist/state-binding/StateAttachRow.js.map +1 -1
- package/dist/state-binding/adapter-mapping-row.d.ts +17 -0
- package/dist/state-binding/adapter-mapping-row.d.ts.map +1 -0
- package/dist/state-binding/adapter-mapping-row.js +87 -0
- package/dist/state-binding/adapter-mapping-row.js.map +1 -0
- package/dist/structured-output/StructuredOutput.d.ts.map +1 -1
- package/dist/structured-output/StructuredOutput.js +1 -3
- package/dist/structured-output/StructuredOutput.js.map +1 -1
- package/package.json +2 -2
- package/src/components/explorer-body.tsx +239 -0
- package/src/components/explorer-page-size.ts +39 -0
- package/src/components/explorer-tag-filter.tsx +80 -0
- package/src/components/explorer-tags.ts +64 -0
- package/src/components/explorer-view.tsx +114 -366
- package/src/components/extension-combo-builder.tsx +35 -152
- package/src/components/extension-combo-parts.tsx +237 -0
- package/src/components/fleet-report.tsx +97 -54
- package/src/components/icons/chrome-marks.tsx +195 -0
- package/src/components/icons/icon-frame.tsx +50 -0
- package/src/components/icons/index.ts +84 -0
- package/src/components/icons/nav-map.ts +55 -0
- package/src/components/icons/nav-marks.tsx +205 -0
- package/src/components/icons/status-marks.tsx +46 -0
- package/src/components/icons/theme-marks.tsx +33 -0
- package/src/components/json-diff.tsx +1 -4
- package/src/components/json-tree-context.ts +26 -0
- package/src/components/json-tree-model.ts +139 -0
- package/src/components/json-tree-nodes.tsx +224 -0
- package/src/components/json-tree.tsx +15 -434
- package/src/components/json-value-format.ts +64 -0
- package/src/components/markdown-blocks.ts +198 -0
- package/src/components/markdown-inline.tsx +365 -0
- package/src/components/markdown.tsx +5 -471
- package/src/components/observer-registry.ts +139 -0
- package/src/components/overflow-measure.ts +69 -0
- package/src/components/prose-regions.ts +153 -0
- package/src/components/scroll-region.tsx +20 -360
- package/src/components/tab-stop-parse.ts +217 -0
- package/src/components/templated-text-field.tsx +38 -330
- package/src/components/templated-text-kwargs-editor.tsx +147 -0
- package/src/components/templated-text-kwargs.ts +92 -0
- package/src/components/templated-text-source.tsx +226 -0
- package/src/components/tool-picker-parts.tsx +184 -0
- package/src/components/tool-picker.tsx +22 -114
- package/src/components/version-history-panel.tsx +38 -142
- package/src/components/version-history-rows.tsx +220 -0
- package/src/guards.ts +7 -0
- package/src/hooks/interactions-connection.ts +227 -0
- package/src/hooks/interactions-stream.ts +274 -0
- package/src/hooks/useSse.ts +50 -477
- package/src/navigation/app-link.tsx +70 -0
- package/src/navigation/context.tsx +11 -330
- package/src/navigation/entry-state.ts +42 -0
- package/src/navigation/guard-registry.ts +65 -0
- package/src/navigation/guarded-navigation.ts +147 -0
- package/src/navigation/index.ts +2 -2
- package/src/navigation/types.ts +8 -11
- package/src/navigation/use-back-forward-guard.ts +90 -0
- package/src/plugin/registry.ts +111 -90
- package/src/schema-editor/SchemaEditor.tsx +9 -1
- package/src/schema-form/SecretRefField.tsx +121 -71
- package/src/schema-form/classify.ts +244 -395
- package/src/schema-form/default-value.ts +48 -17
- package/src/schema-form/enum-field.tsx +1 -1
- package/src/schema-form/field-model.ts +133 -0
- package/src/schema-form/field-node.tsx +94 -38
- package/src/schema-form/media-field.tsx +134 -65
- package/src/schema-form/string-annotations.ts +111 -0
- package/src/schema-form/string-field.tsx +1 -1
- package/src/schema-form/union-field.tsx +2 -1
- package/src/schema-form/union.ts +1 -1
- package/src/schema-form/validate.ts +184 -103
- package/src/state-binding/AdapterMapping.tsx +0 -0
- package/src/state-binding/StateAttachRow.tsx +222 -147
- package/src/state-binding/adapter-mapping-row.tsx +232 -0
- package/src/structured-output/StructuredOutput.tsx +1 -4
- package/dist/components/icons.d.ts +0 -160
- package/dist/components/icons.d.ts.map +0 -1
- package/dist/components/icons.js +0 -160
- package/dist/components/icons.js.map +0 -1
- package/src/components/icons.tsx +0 -586
|
@@ -8,81 +8,25 @@
|
|
|
8
8
|
* dir / tags / search in the URL or in local state; only the view mode persists
|
|
9
9
|
* internally (per `viewSurface`).
|
|
10
10
|
*
|
|
11
|
-
* The tag glue lives
|
|
11
|
+
* The tag glue lives in `explorer-tags` as exported helpers ({@link buildTagVocabulary},
|
|
12
12
|
* {@link matchesSelectedTags}, {@link UNTAGGED_TOKEN}) so every consuming screen
|
|
13
13
|
* shares one vocabulary/untagged-sentinel/OR-match rule rather than copying it.
|
|
14
14
|
*/
|
|
15
|
-
import {
|
|
15
|
+
import { useEffect, useState, type ReactNode } from 'react';
|
|
16
16
|
|
|
17
|
-
import {
|
|
18
|
-
import { childFolders, FolderBreadcrumb, FolderRow, type Folder } from './folder-nav';
|
|
17
|
+
import { childFolders, FolderBreadcrumb, type Folder } from './folder-nav';
|
|
19
18
|
import { SearchIcon } from './icons';
|
|
20
19
|
import { TextInput } from './inputs';
|
|
21
20
|
import { openTargetProps, type OpenTargetProps } from './open-target';
|
|
22
|
-
import {
|
|
23
|
-
import {
|
|
24
|
-
import {
|
|
25
|
-
import {
|
|
26
|
-
import {
|
|
27
|
-
|
|
28
|
-
/** Reserved filter token for items carrying NO tag; namespaced so it never
|
|
29
|
-
* collides with a real tag. */
|
|
30
|
-
export const UNTAGGED_TOKEN = '__untagged__';
|
|
31
|
-
|
|
32
|
-
export interface TagVocabularyEntry {
|
|
33
|
-
readonly token: string;
|
|
34
|
-
readonly label: string;
|
|
35
|
-
readonly count: number;
|
|
36
|
-
}
|
|
21
|
+
import { EmptyState } from './primitives';
|
|
22
|
+
import { useViewMode, ViewToggle, type ViewMode } from './view-toggle';
|
|
23
|
+
import { buildTagVocabulary, matchesSelectedTags } from './explorer-tags';
|
|
24
|
+
import { usePageSize } from './explorer-page-size';
|
|
25
|
+
import { TagFilterRow } from './explorer-tag-filter';
|
|
26
|
+
import { ExplorerBody, type ExplorerEntry } from './explorer-body';
|
|
37
27
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
* items vanished still appears (count 0) so it can be cleared. */
|
|
41
|
-
export function buildTagVocabulary<T>(
|
|
42
|
-
items: readonly T[],
|
|
43
|
-
getTags: (item: T) => readonly string[],
|
|
44
|
-
selectedTags: readonly string[],
|
|
45
|
-
untaggedLabel: string,
|
|
46
|
-
): TagVocabularyEntry[] {
|
|
47
|
-
const counts = new Map<string, number>();
|
|
48
|
-
let untagged = 0;
|
|
49
|
-
for (const item of items) {
|
|
50
|
-
const tags = getTags(item);
|
|
51
|
-
if (tags.length === 0) {
|
|
52
|
-
untagged += 1;
|
|
53
|
-
continue;
|
|
54
|
-
}
|
|
55
|
-
for (const tag of tags) counts.set(tag, (counts.get(tag) ?? 0) + 1);
|
|
56
|
-
}
|
|
57
|
-
const vocabulary: TagVocabularyEntry[] = [...counts.entries()]
|
|
58
|
-
.sort(([a], [b]) => a.localeCompare(b))
|
|
59
|
-
.map(([label, count]) => ({ token: label, label, count }));
|
|
60
|
-
if (untagged > 0) {
|
|
61
|
-
vocabulary.push({ token: UNTAGGED_TOKEN, label: untaggedLabel, count: untagged });
|
|
62
|
-
}
|
|
63
|
-
for (const token of selectedTags) {
|
|
64
|
-
if (!vocabulary.some((entry) => entry.token === token)) {
|
|
65
|
-
vocabulary.push({
|
|
66
|
-
token,
|
|
67
|
-
label: token === UNTAGGED_TOKEN ? untaggedLabel : token,
|
|
68
|
-
count: 0,
|
|
69
|
-
});
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
return vocabulary;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
/** OR semantics: an empty selection matches everything; otherwise an item
|
|
76
|
-
* matches when it carries any selected tag, or is untagged and UNTAGGED_TOKEN
|
|
77
|
-
* is selected. */
|
|
78
|
-
export function matchesSelectedTags(
|
|
79
|
-
itemTags: readonly string[],
|
|
80
|
-
selected: ReadonlySet<string>,
|
|
81
|
-
): boolean {
|
|
82
|
-
if (selected.size === 0) return true;
|
|
83
|
-
if (itemTags.length === 0) return selected.has(UNTAGGED_TOKEN);
|
|
84
|
-
return itemTags.some((tag) => selected.has(tag));
|
|
85
|
-
}
|
|
28
|
+
export { UNTAGGED_TOKEN, buildTagVocabulary, matchesSelectedTags } from './explorer-tags';
|
|
29
|
+
export type { TagVocabularyEntry } from './explorer-tags';
|
|
86
30
|
|
|
87
31
|
/** One header cell of the table view. `renderRow` must emit exactly this many
|
|
88
32
|
* `<TD>`s per item; folder rows span all of them. */
|
|
@@ -172,140 +116,85 @@ export interface ExplorerViewProps<T> {
|
|
|
172
116
|
readonly emptyStates: ExplorerEmptyStates;
|
|
173
117
|
}
|
|
174
118
|
|
|
175
|
-
/**
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
}): ReactNode {
|
|
185
|
-
return (
|
|
186
|
-
<button
|
|
187
|
-
type="button"
|
|
188
|
-
className="tai-chip"
|
|
189
|
-
aria-pressed={active}
|
|
190
|
-
aria-label={`${entry.label} (${String(entry.count)})`}
|
|
191
|
-
onClick={onToggle}
|
|
192
|
-
>
|
|
193
|
-
<span>{entry.label}</span>
|
|
194
|
-
<span>{entry.count}</span>
|
|
195
|
-
</button>
|
|
196
|
-
);
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
/** The filter row: every selected chip plus as many unselected chips as fit under
|
|
200
|
-
* the cap, with the remainder collapsed into a STATIC "+N more" count. An unset
|
|
201
|
-
* cap shows every chip. */
|
|
202
|
-
function TagFilterRow({
|
|
203
|
-
vocabulary,
|
|
204
|
-
selectedSet,
|
|
205
|
-
onToggle,
|
|
206
|
-
filterLabel,
|
|
207
|
-
maxVisibleTags,
|
|
208
|
-
}: {
|
|
209
|
-
readonly vocabulary: readonly TagVocabularyEntry[];
|
|
210
|
-
readonly selectedSet: ReadonlySet<string>;
|
|
211
|
-
readonly onToggle: (token: string) => void;
|
|
212
|
-
readonly filterLabel: string;
|
|
213
|
-
readonly maxVisibleTags: number | undefined;
|
|
214
|
-
}): ReactNode {
|
|
215
|
-
const visible: TagVocabularyEntry[] = [];
|
|
216
|
-
let hidden = 0;
|
|
217
|
-
let unselectedShown = 0;
|
|
218
|
-
for (const entry of vocabulary) {
|
|
219
|
-
if (selectedSet.has(entry.token)) {
|
|
220
|
-
visible.push(entry);
|
|
221
|
-
} else if (maxVisibleTags === undefined || unselectedShown < maxVisibleTags) {
|
|
222
|
-
visible.push(entry);
|
|
223
|
-
unselectedShown += 1;
|
|
224
|
-
} else {
|
|
225
|
-
hidden += 1;
|
|
226
|
-
}
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
return (
|
|
230
|
-
<div role="group" aria-label={filterLabel} className="tai-row">
|
|
231
|
-
{visible.map((entry) => (
|
|
232
|
-
<TagChip
|
|
233
|
-
key={entry.token}
|
|
234
|
-
entry={entry}
|
|
235
|
-
active={selectedSet.has(entry.token)}
|
|
236
|
-
onToggle={() => {
|
|
237
|
-
onToggle(entry.token);
|
|
238
|
-
}}
|
|
239
|
-
/>
|
|
240
|
-
))}
|
|
241
|
-
{hidden > 0 ? (
|
|
242
|
-
<span className="tai-chip tai-chip-static">{`+${String(hidden)} more`}</span>
|
|
243
|
-
) : null}
|
|
244
|
-
</div>
|
|
245
|
-
);
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
/** A first-class folder entry — a `FolderRow` (icon + name, navigates inward)
|
|
249
|
-
* beside its optional actions, in the layout shared by the row cell and the card. */
|
|
250
|
-
function FolderEntry({
|
|
251
|
-
folder,
|
|
252
|
-
onNavigate,
|
|
253
|
-
renderFolderActions,
|
|
119
|
+
/** The header row: the optional search field, the item count, and the
|
|
120
|
+
* list/card view toggle. */
|
|
121
|
+
function ExplorerControls<T>({
|
|
122
|
+
itemCount,
|
|
123
|
+
countLabel,
|
|
124
|
+
label,
|
|
125
|
+
search,
|
|
126
|
+
viewMode,
|
|
127
|
+
onViewModeChange,
|
|
254
128
|
}: {
|
|
255
|
-
readonly
|
|
256
|
-
readonly
|
|
257
|
-
readonly
|
|
129
|
+
readonly itemCount: number;
|
|
130
|
+
readonly countLabel: string;
|
|
131
|
+
readonly label: string;
|
|
132
|
+
readonly search: ExplorerSearch<T> | undefined;
|
|
133
|
+
readonly viewMode: ViewMode;
|
|
134
|
+
readonly onViewModeChange: (mode: ViewMode) => void;
|
|
258
135
|
}): ReactNode {
|
|
259
136
|
return (
|
|
260
|
-
<div className="tai-explorer-
|
|
261
|
-
<
|
|
262
|
-
|
|
137
|
+
<div className="tai-explorer-controls">
|
|
138
|
+
<div className="tai-row">
|
|
139
|
+
{search !== undefined ? (
|
|
140
|
+
// The magnifier is a leading icon INSIDE the input, not a detached
|
|
141
|
+
// glyph beside it: the wrapper positions the icon over the input's
|
|
142
|
+
// reserved leading padding.
|
|
143
|
+
<span className="tai-search-field">
|
|
144
|
+
<SearchIcon />
|
|
145
|
+
<TextInput
|
|
146
|
+
value={search.value}
|
|
147
|
+
aria-label={search.label}
|
|
148
|
+
placeholder={search.placeholder}
|
|
149
|
+
onChange={(event) => {
|
|
150
|
+
search.onChange(event.target.value);
|
|
151
|
+
}}
|
|
152
|
+
/>
|
|
153
|
+
</span>
|
|
154
|
+
) : null}
|
|
155
|
+
<span className="tai-muted">{`${String(itemCount)} ${countLabel}`}</span>
|
|
156
|
+
</div>
|
|
157
|
+
<ViewToggle value={viewMode} onValueChange={onViewModeChange} aria-label={`${label} view`} />
|
|
263
158
|
</div>
|
|
264
159
|
);
|
|
265
160
|
}
|
|
266
161
|
|
|
267
|
-
/** The page-size choices, and the default. */
|
|
268
|
-
const PAGE_SIZES = [12, 24, 48, 96] as const;
|
|
269
|
-
const DEFAULT_PAGE_SIZE = 24;
|
|
270
|
-
|
|
271
|
-
const PAGE_SIZE_STORAGE_PREFIX = 'tai-studio.page-size.';
|
|
272
|
-
|
|
273
|
-
function readStoredPageSize(surface: string): number {
|
|
274
|
-
try {
|
|
275
|
-
const raw = globalThis.localStorage.getItem(PAGE_SIZE_STORAGE_PREFIX + surface);
|
|
276
|
-
const parsed = raw === null ? Number.NaN : Number(raw);
|
|
277
|
-
if ((PAGE_SIZES as readonly number[]).includes(parsed)) return parsed;
|
|
278
|
-
} catch {
|
|
279
|
-
// No storage (private mode / non-browser) — fall back to the default.
|
|
280
|
-
}
|
|
281
|
-
return DEFAULT_PAGE_SIZE;
|
|
282
|
-
}
|
|
283
|
-
|
|
284
162
|
/**
|
|
285
|
-
* The
|
|
286
|
-
*
|
|
287
|
-
*
|
|
163
|
+
* The pointer-open props for one item's row/card, via the shared house pattern
|
|
164
|
+
* ({@link openTargetProps}): present only when `onOpenItem` is set, it yields to
|
|
165
|
+
* the item's own nested interactive elements (its name link, an actions kebab)
|
|
166
|
+
* and to a text-selection drag. The accessible activation path stays that nested
|
|
167
|
+
* name link, so no `keyboard` here — the row is never itself a focus stop.
|
|
288
168
|
*/
|
|
289
|
-
function
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
}
|
|
299
|
-
},
|
|
300
|
-
[surface],
|
|
301
|
-
);
|
|
302
|
-
return [size, setSize];
|
|
169
|
+
function itemOpenProps<T>(item: T, onOpenItem: ((item: T) => void) | undefined): OpenTargetProps {
|
|
170
|
+
return openTargetProps({
|
|
171
|
+
onOpen:
|
|
172
|
+
onOpenItem === undefined
|
|
173
|
+
? undefined
|
|
174
|
+
: () => {
|
|
175
|
+
onOpenItem(item);
|
|
176
|
+
},
|
|
177
|
+
});
|
|
303
178
|
}
|
|
304
179
|
|
|
305
|
-
/**
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
180
|
+
/** The current folder's filtered items and the tag vocabulary over the folder. */
|
|
181
|
+
function filterInFolder<T>(
|
|
182
|
+
inFolder: readonly T[],
|
|
183
|
+
tags: ExplorerTags<T> | undefined,
|
|
184
|
+
selectedSet: ReadonlySet<string> | undefined,
|
|
185
|
+
search: ExplorerSearch<T> | undefined,
|
|
186
|
+
query: string,
|
|
187
|
+
): readonly T[] {
|
|
188
|
+
return inFolder.filter((item) => {
|
|
189
|
+
if (tags !== undefined && selectedSet !== undefined && selectedSet.size > 0) {
|
|
190
|
+
if (!matchesSelectedTags(tags.getTags(item), selectedSet)) return false;
|
|
191
|
+
}
|
|
192
|
+
if (search !== undefined && query !== '') {
|
|
193
|
+
if (!search.matches(item, query)) return false;
|
|
194
|
+
}
|
|
195
|
+
return true;
|
|
196
|
+
});
|
|
197
|
+
}
|
|
309
198
|
|
|
310
199
|
export function ExplorerView<T>({
|
|
311
200
|
items,
|
|
@@ -351,15 +240,7 @@ export function ExplorerView<T>({
|
|
|
351
240
|
const inFolder = items.filter((item) => getFolderId(item) === currentFolderId);
|
|
352
241
|
|
|
353
242
|
const selectedSet = tags !== undefined ? new Set(tags.selected) : undefined;
|
|
354
|
-
const filtered = inFolder
|
|
355
|
-
if (tags !== undefined && selectedSet !== undefined && selectedSet.size > 0) {
|
|
356
|
-
if (!matchesSelectedTags(tags.getTags(item), selectedSet)) return false;
|
|
357
|
-
}
|
|
358
|
-
if (search !== undefined && query !== '') {
|
|
359
|
-
if (!search.matches(item, query)) return false;
|
|
360
|
-
}
|
|
361
|
-
return true;
|
|
362
|
-
});
|
|
243
|
+
const filtered = filterInFolder(inFolder, tags, selectedSet, search, query);
|
|
363
244
|
|
|
364
245
|
const vocabulary =
|
|
365
246
|
tags !== undefined
|
|
@@ -374,20 +255,7 @@ export function ExplorerView<T>({
|
|
|
374
255
|
tags.onChange(next);
|
|
375
256
|
};
|
|
376
257
|
|
|
377
|
-
|
|
378
|
-
// ({@link openTargetProps}): present only when `onOpenItem` is set, it yields to
|
|
379
|
-
// the item's own nested interactive elements (its name link, an actions kebab)
|
|
380
|
-
// and to a text-selection drag. The accessible activation path stays that nested
|
|
381
|
-
// name link, so no `keyboard` here — the row is never itself a focus stop.
|
|
382
|
-
const openProps = (item: T): OpenTargetProps =>
|
|
383
|
-
openTargetProps({
|
|
384
|
-
onOpen:
|
|
385
|
-
onOpenItem === undefined
|
|
386
|
-
? undefined
|
|
387
|
-
: () => {
|
|
388
|
-
onOpenItem(item);
|
|
389
|
-
},
|
|
390
|
-
});
|
|
258
|
+
const openProps = (item: T): OpenTargetProps => itemOpenProps(item, onOpenItem);
|
|
391
259
|
|
|
392
260
|
const hasEntries = subfolders.length > 0 || filtered.length > 0;
|
|
393
261
|
|
|
@@ -406,136 +274,6 @@ export function ExplorerView<T>({
|
|
|
406
274
|
const pageStart = (effectivePage - 1) * pageSize;
|
|
407
275
|
const pageEntries = entries.slice(pageStart, pageStart + pageSize);
|
|
408
276
|
|
|
409
|
-
const footer =
|
|
410
|
-
entries.length > 0 ? (
|
|
411
|
-
<nav className="tai-explorer-pagination" aria-label={`${label} pagination`}>
|
|
412
|
-
{/* The pager counts ENTRIES — subfolders plus filtered items in the
|
|
413
|
-
current directory — which is a different unit from the header's total
|
|
414
|
-
item count across every folder. Naming the unit keeps the two honest:
|
|
415
|
-
"1–4 of 4 entries" here versus "7 templates" above. */}
|
|
416
|
-
<span className="tai-muted">{`${String(pageStart + 1)}–${String(Math.min(pageStart + pageSize, entries.length))} of ${String(entries.length)} entries`}</span>
|
|
417
|
-
<div className="tai-row">
|
|
418
|
-
<Select
|
|
419
|
-
aria-label="Items per page"
|
|
420
|
-
value={String(pageSize)}
|
|
421
|
-
onValueChange={(value) => {
|
|
422
|
-
setPageSize(Number(value));
|
|
423
|
-
}}
|
|
424
|
-
options={PAGE_SIZES.map((size) => ({
|
|
425
|
-
value: String(size),
|
|
426
|
-
label: `${String(size)} per page`,
|
|
427
|
-
}))}
|
|
428
|
-
/>
|
|
429
|
-
{/* aria-disabled, never the disabled attribute: a button removed from
|
|
430
|
-
the tab order at the bound drops focus to the body (WCAG 2.4.3).
|
|
431
|
-
The click guard makes activation at the bound an inert no-op. */}
|
|
432
|
-
<Button
|
|
433
|
-
variant="ghost"
|
|
434
|
-
aria-label="Previous page"
|
|
435
|
-
aria-disabled={effectivePage <= 1}
|
|
436
|
-
onClick={() => {
|
|
437
|
-
if (effectivePage <= 1) return;
|
|
438
|
-
setPage(effectivePage - 1);
|
|
439
|
-
}}
|
|
440
|
-
>
|
|
441
|
-
Prev
|
|
442
|
-
</Button>
|
|
443
|
-
<Button
|
|
444
|
-
variant="ghost"
|
|
445
|
-
aria-label="Next page"
|
|
446
|
-
aria-disabled={effectivePage >= pageCount}
|
|
447
|
-
onClick={() => {
|
|
448
|
-
if (effectivePage >= pageCount) return;
|
|
449
|
-
setPage(effectivePage + 1);
|
|
450
|
-
}}
|
|
451
|
-
>
|
|
452
|
-
Next
|
|
453
|
-
</Button>
|
|
454
|
-
<span className="tai-muted">{`Page ${String(effectivePage)} of ${String(pageCount)}`}</span>
|
|
455
|
-
</div>
|
|
456
|
-
</nav>
|
|
457
|
-
) : null;
|
|
458
|
-
|
|
459
|
-
let body: ReactNode;
|
|
460
|
-
if (!hasEntries) {
|
|
461
|
-
// The current folder renders nothing. A filter emptied it (`inFolder` non-empty)
|
|
462
|
-
// → "no match"; otherwise the folder is genuinely empty. The ladder decides on
|
|
463
|
-
// the full, unsliced sets, never on a page.
|
|
464
|
-
body = (
|
|
465
|
-
<EmptyState {...(inFolder.length > 0 ? emptyStates.noMatch : emptyStates.emptyFolder)} />
|
|
466
|
-
);
|
|
467
|
-
} else if (viewMode === 'cards') {
|
|
468
|
-
body = (
|
|
469
|
-
<>
|
|
470
|
-
<EntityCardGrid aria-label={label}>
|
|
471
|
-
{pageEntries.map((entry) =>
|
|
472
|
-
entry.kind === 'folder' ? (
|
|
473
|
-
<div role="listitem" key={`folder:${entry.folder.id}`}>
|
|
474
|
-
<Card interactive>
|
|
475
|
-
<FolderEntry
|
|
476
|
-
folder={entry.folder}
|
|
477
|
-
onNavigate={onNavigate}
|
|
478
|
-
renderFolderActions={renderFolderActions}
|
|
479
|
-
/>
|
|
480
|
-
</Card>
|
|
481
|
-
</div>
|
|
482
|
-
) : (
|
|
483
|
-
<div
|
|
484
|
-
role="listitem"
|
|
485
|
-
key={`item:${getItemKey(entry.item)}`}
|
|
486
|
-
{...openProps(entry.item)}
|
|
487
|
-
>
|
|
488
|
-
{renderCard(entry.item)}
|
|
489
|
-
</div>
|
|
490
|
-
),
|
|
491
|
-
)}
|
|
492
|
-
</EntityCardGrid>
|
|
493
|
-
{footer}
|
|
494
|
-
</>
|
|
495
|
-
);
|
|
496
|
-
} else {
|
|
497
|
-
// Folder rows share the item table, spanning every column, sorted above items —
|
|
498
|
-
// so a folder-with-only-subfolders is folder ROWS, never a header-only table.
|
|
499
|
-
// A too-wide table scrolls inside its own box rather than widening the page.
|
|
500
|
-
body = (
|
|
501
|
-
<>
|
|
502
|
-
<ScrollRegion label={label}>
|
|
503
|
-
<Table>
|
|
504
|
-
<THead>
|
|
505
|
-
<TR>
|
|
506
|
-
{columns.map((column) => (
|
|
507
|
-
<TH key={column.key} numeric={column.numeric}>
|
|
508
|
-
{column.header}
|
|
509
|
-
</TH>
|
|
510
|
-
))}
|
|
511
|
-
</TR>
|
|
512
|
-
</THead>
|
|
513
|
-
<TBody>
|
|
514
|
-
{pageEntries.map((entry) =>
|
|
515
|
-
entry.kind === 'folder' ? (
|
|
516
|
-
<TR key={`folder:${entry.folder.id}`}>
|
|
517
|
-
<TD colSpan={columns.length}>
|
|
518
|
-
<FolderEntry
|
|
519
|
-
folder={entry.folder}
|
|
520
|
-
onNavigate={onNavigate}
|
|
521
|
-
renderFolderActions={renderFolderActions}
|
|
522
|
-
/>
|
|
523
|
-
</TD>
|
|
524
|
-
</TR>
|
|
525
|
-
) : (
|
|
526
|
-
<TR key={`item:${getItemKey(entry.item)}`} {...openProps(entry.item)}>
|
|
527
|
-
{renderRow(entry.item)}
|
|
528
|
-
</TR>
|
|
529
|
-
),
|
|
530
|
-
)}
|
|
531
|
-
</TBody>
|
|
532
|
-
</Table>
|
|
533
|
-
</ScrollRegion>
|
|
534
|
-
{footer}
|
|
535
|
-
</>
|
|
536
|
-
);
|
|
537
|
-
}
|
|
538
|
-
|
|
539
277
|
// Every surface label is a regular English plural (Tools/Templates/Resources/
|
|
540
278
|
// Flows), so a count of one drops the trailing "s".
|
|
541
279
|
const countLabel =
|
|
@@ -543,28 +281,14 @@ export function ExplorerView<T>({
|
|
|
543
281
|
|
|
544
282
|
return (
|
|
545
283
|
<div className="tai-explorer">
|
|
546
|
-
<
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
<TextInput
|
|
555
|
-
value={search.value}
|
|
556
|
-
aria-label={search.label}
|
|
557
|
-
placeholder={search.placeholder}
|
|
558
|
-
onChange={(event) => {
|
|
559
|
-
search.onChange(event.target.value);
|
|
560
|
-
}}
|
|
561
|
-
/>
|
|
562
|
-
</span>
|
|
563
|
-
) : null}
|
|
564
|
-
<span className="tai-muted">{`${String(items.length)} ${countLabel}`}</span>
|
|
565
|
-
</div>
|
|
566
|
-
<ViewToggle value={viewMode} onValueChange={setViewMode} aria-label={`${label} view`} />
|
|
567
|
-
</div>
|
|
284
|
+
<ExplorerControls
|
|
285
|
+
itemCount={items.length}
|
|
286
|
+
countLabel={countLabel}
|
|
287
|
+
label={label}
|
|
288
|
+
search={search}
|
|
289
|
+
viewMode={viewMode}
|
|
290
|
+
onViewModeChange={setViewMode}
|
|
291
|
+
/>
|
|
568
292
|
|
|
569
293
|
<FolderBreadcrumb
|
|
570
294
|
folders={folders}
|
|
@@ -583,7 +307,31 @@ export function ExplorerView<T>({
|
|
|
583
307
|
/>
|
|
584
308
|
) : null}
|
|
585
309
|
|
|
586
|
-
|
|
310
|
+
<ExplorerBody
|
|
311
|
+
hasEntries={hasEntries}
|
|
312
|
+
inFolderCount={inFolder.length}
|
|
313
|
+
emptyStates={emptyStates}
|
|
314
|
+
viewMode={viewMode}
|
|
315
|
+
label={label}
|
|
316
|
+
columns={columns}
|
|
317
|
+
pageEntries={pageEntries}
|
|
318
|
+
getItemKey={getItemKey}
|
|
319
|
+
renderRow={renderRow}
|
|
320
|
+
renderCard={renderCard}
|
|
321
|
+
openProps={openProps}
|
|
322
|
+
onNavigate={onNavigate}
|
|
323
|
+
renderFolderActions={renderFolderActions}
|
|
324
|
+
pager={{
|
|
325
|
+
label,
|
|
326
|
+
pageStart,
|
|
327
|
+
pageSize,
|
|
328
|
+
total: entries.length,
|
|
329
|
+
effectivePage,
|
|
330
|
+
pageCount,
|
|
331
|
+
onPageChange: setPage,
|
|
332
|
+
onPageSizeChange: setPageSize,
|
|
333
|
+
}}
|
|
334
|
+
/>
|
|
587
335
|
</div>
|
|
588
336
|
);
|
|
589
337
|
}
|