@svadmin/ui 0.70.0 → 0.73.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +40 -25
- package/THIRD_PARTY_NOTICES.md +52 -0
- package/dist/actions.js +1 -1
- package/dist/app.css +7804 -5188
- package/dist/app.theme.css +7738 -5173
- package/dist/class-properties.generated.d.ts +1 -0
- package/dist/class-properties.generated.js +2 -0
- package/dist/classnames.d.ts +8 -0
- package/dist/classnames.js +26 -0
- package/dist/component-registry.svelte.d.ts +33 -19
- package/dist/component-registry.svelte.js +6 -4
- package/dist/components/AdminApp.svelte +55 -53
- package/dist/components/AdminApp.svelte.d.ts +2 -2
- package/dist/components/ApiSettings.svelte +19 -1
- package/dist/components/AuditLogDrawer.svelte +14 -3
- package/dist/components/AutoForm.svelte +162 -50
- package/dist/components/AutoForm.svelte.d.ts +4 -0
- package/dist/components/AutoTable.svelte +599 -259
- package/dist/components/AutoTable.svelte.d.ts +6 -0
- package/dist/components/BoundRecordDetailDrawer.svelte +172 -0
- package/dist/components/BoundRecordDetailDrawer.svelte.d.ts +11 -0
- package/dist/components/Breadcrumbs.svelte +7 -4
- package/dist/components/CancelTaskButton.svelte +9 -5
- package/dist/components/ColumnHeaderFilter.svelte +4 -3
- package/dist/components/ColumnSettings.svelte +1 -0
- package/dist/components/ComboboxField.svelte +118 -36
- package/dist/components/ComboboxField.svelte.d.ts +5 -4
- package/dist/components/CreatePage.svelte +7 -1
- package/dist/components/CreatePage.svelte.d.ts +2 -0
- package/dist/components/DateRangeInput.svelte +75 -0
- package/dist/components/DateRangeInput.svelte.d.ts +25 -0
- package/dist/components/DateTimeInput.svelte +65 -0
- package/dist/components/DateTimeInput.svelte.d.ts +19 -0
- package/dist/components/DetailDrawer.svelte +4 -0
- package/dist/components/DetailDrawer.svelte.d.ts +1 -0
- package/dist/components/DevTools.svelte +8 -21
- package/dist/components/DraggableGrid.svelte +1 -0
- package/dist/components/DraggableHeader.svelte +5 -9
- package/dist/components/DraggableHeader.svelte.d.ts +43 -18
- package/dist/components/DraggableRowTable.svelte +2 -2
- package/dist/components/DynamicFormList.svelte +17 -17
- package/dist/components/DynamicFormList.svelte.d.ts +2 -1
- package/dist/components/EditPage.svelte +7 -1
- package/dist/components/EditPage.svelte.d.ts +2 -0
- package/dist/components/EditableTable.svelte +4 -0
- package/dist/components/ErrorPage.svelte +3 -1
- package/dist/components/FieldDisplay.svelte +14 -0
- package/dist/components/FieldDisplay.svelte.d.ts +7 -0
- package/dist/components/FieldRenderer.svelte +96 -20
- package/dist/components/FileUpload.svelte +250 -0
- package/dist/components/FileUpload.svelte.d.ts +32 -0
- package/dist/components/FilterBuilder.svelte +236 -268
- package/dist/components/FilterBuilder.svelte.d.ts +3 -0
- package/dist/components/FilterDropdown.svelte.d.ts +1 -1
- package/dist/components/Header.svelte +5 -6
- package/dist/components/ImportWizard.svelte +182 -94
- package/dist/components/InfiniteList.svelte +22 -8
- package/dist/components/InfiniteList.svelte.d.ts +5 -3
- package/dist/components/InlineEdit.svelte +114 -44
- package/dist/components/JsonSchemaForm.svelte +256 -95
- package/dist/components/JsonSchemaForm.svelte.d.ts +12 -1
- package/dist/components/Layout.svelte +20 -17
- package/dist/components/LazyTaskQueueDrawer.svelte +26 -0
- package/dist/components/LazyTaskQueueDrawer.svelte.d.ts +8 -0
- package/dist/components/ListPage.svelte +11 -6
- package/dist/components/ListPage.svelte.d.ts +2 -0
- package/dist/components/LoginPage.svelte +1 -1
- package/dist/components/MasterDetailView.svelte +3 -3
- package/dist/components/MediaLibraryModal.svelte +5 -2
- package/dist/components/MentionsInput.svelte +4 -3
- package/dist/components/MoneyInput.svelte +25 -0
- package/dist/components/MoneyInput.svelte.d.ts +20 -0
- package/dist/components/NavigateToResource.svelte +3 -2
- package/dist/components/NumberInput.svelte +25 -0
- package/dist/components/NumberInput.svelte.d.ts +19 -0
- package/dist/components/NumericInput.svelte +110 -0
- package/dist/components/NumericInput.svelte.d.ts +23 -0
- package/dist/components/PercentInput.svelte +25 -0
- package/dist/components/PercentInput.svelte.d.ts +20 -0
- package/dist/components/PermissionMatrix.svelte +4 -3
- package/dist/components/ProfilePage.svelte +2 -2
- package/dist/components/QuickEditDrawer.svelte +42 -0
- package/dist/components/QuickEditDrawer.svelte.d.ts +10 -0
- package/dist/components/RecordDetailDrawer.svelte +16 -108
- package/dist/components/RecordDetailDrawer.svelte.d.ts +2 -0
- package/dist/components/RecordRowActions.svelte +41 -0
- package/dist/components/RecordRowActions.svelte.d.ts +14 -0
- package/dist/components/ResourceAccessGuard.svelte +7 -5
- package/dist/components/ResourceOperationsPage.svelte +25 -19
- package/dist/components/ResourceOperationsPage.svelte.d.ts +5 -1
- package/dist/components/RetryTaskButton.svelte +9 -5
- package/dist/components/RowActions.svelte +5 -3
- package/dist/components/SensitiveDataMask.svelte +3 -3
- package/dist/components/SettingsPage.svelte +1 -1
- package/dist/components/ShowInferencer.svelte +4 -2
- package/dist/components/ShowInferencer.svelte.d.ts +2 -0
- package/dist/components/ShowPage.svelte +49 -39
- package/dist/components/ShowPage.svelte.d.ts +2 -0
- package/dist/components/Sidebar.svelte +15 -13
- package/dist/components/SplitPaneLayout.svelte +4 -2
- package/dist/components/SpreadsheetView.svelte +81 -286
- package/dist/components/StepsForm.svelte +166 -49
- package/dist/components/StepsForm.svelte.d.ts +1 -0
- package/dist/components/SvarAutoTable.svelte +39 -0
- package/dist/components/SvarAutoTable.svelte.d.ts +13 -0
- package/dist/components/SvarDataGrid.svelte +362 -0
- package/dist/components/SvarDataGrid.svelte.d.ts +57 -0
- package/dist/components/SvarGridCell.svelte +10 -0
- package/dist/components/SvarGridCell.svelte.d.ts +7 -0
- package/dist/components/SvarResourceRead.svelte +50 -0
- package/dist/components/SvarResourceRead.svelte.d.ts +23 -0
- package/dist/components/SvarResourceTable.svelte +63 -0
- package/dist/components/SvarResourceTable.svelte.d.ts +42 -0
- package/dist/components/SvarResourceTableView.svelte +364 -0
- package/dist/components/SvarResourceTableView.svelte.d.ts +4 -0
- package/dist/components/TaskDetails.svelte +20 -7
- package/dist/components/TaskList.svelte +19 -7
- package/dist/components/TaskQueueDrawer.svelte +92 -37
- package/dist/components/Toast.svelte +4 -2
- package/dist/components/TooltipButton.svelte +1 -1
- package/dist/components/TreeSelect.svelte +212 -30
- package/dist/components/TreeSelect.svelte.d.ts +3 -1
- package/dist/components/ValidatedField.svelte +27 -0
- package/dist/components/ValidatedField.svelte.d.ts +35 -0
- package/dist/components/account/ImportMembersPage.svelte +2 -2
- package/dist/components/account/SecurityLogPage.svelte +3 -1
- package/dist/components/account/SettingsEnterprisePage.svelte +7 -5
- package/dist/components/account/TeamMembersPage.svelte +1 -1
- package/dist/components/auto-table-grid.d.ts +26 -0
- package/dist/components/auto-table-grid.js +1 -0
- package/dist/components/buttons/CloneButton.svelte +6 -4
- package/dist/components/buttons/DeleteButton.svelte +95 -29
- package/dist/components/buttons/DeleteButton.svelte.d.ts +2 -0
- package/dist/components/buttons/EditButton.svelte +6 -4
- package/dist/components/buttons/ExportButton.svelte +22 -4
- package/dist/components/buttons/ImportButton.svelte +83 -38
- package/dist/components/buttons/ImportButton.svelte.d.ts +6 -10
- package/dist/components/buttons/RefreshButton.svelte +48 -8
- package/dist/components/buttons/ShowButton.svelte +6 -4
- package/dist/components/content/ApiKeyList.svelte +3 -1
- package/dist/components/content/ContentPageHeader.svelte +12 -9
- package/dist/components/content/ContentPageShell.svelte +8 -9
- package/dist/components/content/FileBrowser.svelte +35 -0
- package/dist/components/content/FileBrowser.svelte.d.ts +14 -0
- package/dist/components/content/FileList.svelte +3 -1
- package/dist/components/content/MemberList.svelte +3 -1
- package/dist/components/content/MetricBlock.svelte +10 -13
- package/dist/components/content/NetworkTable.svelte +6 -4
- package/dist/components/content/NetworkTable.svelte.d.ts +1 -0
- package/dist/components/content/PageToolbar.svelte +6 -10
- package/dist/components/content/SectionHeader.svelte +8 -13
- package/dist/components/content/SecurityEventTable.svelte +3 -1
- package/dist/components/content/SettingsFieldRow.svelte +9 -23
- package/dist/components/content/SettingsGroup.svelte +11 -27
- package/dist/components/content/StatusBadge.svelte +3 -8
- package/dist/components/content/StatusTabs.svelte +2 -2
- package/dist/components/content/WorkspaceLayout.svelte +8 -12
- package/dist/components/enterprise/filter-tree.d.ts +48 -0
- package/dist/components/enterprise/filter-tree.js +224 -0
- package/dist/components/enterprise/json-schema-form.d.ts +39 -0
- package/dist/components/enterprise/json-schema-form.js +195 -0
- package/dist/components/enterprise/recursive-schema-form.d.ts +30 -0
- package/dist/components/enterprise/recursive-schema-form.js +196 -0
- package/dist/components/enterprise/spreadsheet-formula.d.ts +12 -0
- package/dist/components/enterprise/spreadsheet-formula.js +245 -0
- package/dist/components/fieldComponentMap.d.ts +18 -13
- package/dist/components/fieldComponentMap.js +100 -47
- package/dist/components/fields/AvatarField.svelte +1 -1
- package/dist/components/fields/ChoiceField.svelte +16 -0
- package/dist/components/fields/ChoiceField.svelte.d.ts +10 -0
- package/dist/components/fields/CodeField.svelte +11 -12
- package/dist/components/fields/CodeField.svelte.d.ts +1 -1
- package/dist/components/fields/DateField.svelte +4 -3
- package/dist/components/fields/DateRangeField.svelte +2 -4
- package/dist/components/fields/ImageField.svelte +3 -1
- package/dist/components/fields/ImagesField.svelte +23 -0
- package/dist/components/fields/ImagesField.svelte.d.ts +6 -0
- package/dist/components/fields/JsonField.svelte +19 -13
- package/dist/components/fields/PasswordField.svelte +5 -0
- package/dist/components/fields/PasswordField.svelte.d.ts +6 -0
- package/dist/components/fields/date-field-value.d.ts +2 -0
- package/dist/components/fields/date-field-value.js +13 -0
- package/dist/components/fields/json-value.d.ts +11 -0
- package/dist/components/fields/json-value.js +28 -0
- package/dist/components/json-schema-form-state.d.ts +37 -0
- package/dist/components/json-schema-form-state.js +168 -0
- package/dist/components/profile/ActivityTimeline.svelte +2 -2
- package/dist/components/record-detail.svelte.d.ts +19 -0
- package/dist/components/record-detail.svelte.js +34 -0
- package/dist/components/saved-list-views.js +29 -29
- package/dist/components/svar-grid-cells.d.ts +14 -0
- package/dist/components/svar-grid-cells.js +2 -0
- package/dist/components/svar-grid-contract.d.ts +80 -0
- package/dist/components/svar-grid-contract.js +159 -0
- package/dist/components/svar-grid-interactions.d.ts +54 -0
- package/dist/components/svar-grid-interactions.js +174 -0
- package/dist/components/svar-grid-loading.d.ts +40 -0
- package/dist/components/svar-grid-loading.js +180 -0
- package/dist/components/svar-grid-operations.d.ts +37 -0
- package/dist/components/svar-grid-operations.js +186 -0
- package/dist/components/svar-grid-panes.d.ts +34 -0
- package/dist/components/svar-grid-panes.js +66 -0
- package/dist/components/svar-grid-resource-loading.d.ts +18 -0
- package/dist/components/svar-grid-resource-loading.js +68 -0
- package/dist/components/svar-grid-resource-session.svelte.d.ts +29 -0
- package/dist/components/svar-grid-resource-session.svelte.js +61 -0
- package/dist/components/table-contract.d.ts +13 -0
- package/dist/components/table-contract.js +42 -0
- package/dist/components/task-utils.js +4 -2
- package/dist/components/ui/alert/alert-variants.d.ts +2 -3
- package/dist/components/ui/alert/alert-variants.js +1 -2
- package/dist/components/ui/alert/alert.svelte +4 -5
- package/dist/components/ui/alert/alert.svelte.d.ts +3 -8
- package/dist/components/ui/alert/index.js +0 -2
- package/dist/components/ui/avatar/avatar-variants.d.ts +2 -3
- package/dist/components/ui/avatar/avatar-variants.js +1 -2
- package/dist/components/ui/avatar/avatar.svelte +5 -6
- package/dist/components/ui/avatar/avatar.svelte.d.ts +3 -8
- package/dist/components/ui/badge/badge-variants.d.ts +2 -3
- package/dist/components/ui/badge/badge-variants.js +3 -2
- package/dist/components/ui/badge/badge.svelte +4 -5
- package/dist/components/ui/badge/badge.svelte.d.ts +3 -8
- package/dist/components/ui/badge/index.js +0 -2
- package/dist/components/ui/button/button-variants.d.ts +2 -3
- package/dist/components/ui/button/button-variants.js +5 -3
- package/dist/components/ui/button/button.svelte +100 -44
- package/dist/components/ui/button/button.svelte.d.ts +8 -12
- package/dist/components/ui/button/index.js +0 -2
- package/dist/components/ui/command/command-dialog.svelte +3 -1
- package/dist/components/ui/command/command-dialog.svelte.d.ts +1 -1
- package/dist/components/ui/command/command-group.svelte +4 -2
- package/dist/components/ui/command/command-item.svelte +4 -2
- package/dist/components/ui/command/command-separator.svelte +3 -1
- package/dist/components/ui/command/index.d.ts +5 -5
- package/dist/components/ui/input/input.svelte +41 -24
- package/dist/components/ui/input/input.svelte.d.ts +5 -4
- package/dist/components/ui/sheet/sheet.svelte +9 -3
- package/dist/components/ui/sheet/sheet.svelte.d.ts +1 -0
- package/dist/components/ui/textarea/textarea.svelte +2 -1
- package/dist/components/ui/tooltip/tooltip.svelte +4 -2
- package/dist/components.css +3079 -0
- package/dist/design-contract.d.ts +13 -0
- package/dist/design-contract.js +11 -0
- package/dist/design-system.d.ts +15 -0
- package/dist/design-system.js +7 -0
- package/dist/index.d.ts +15 -1
- package/dist/index.js +9 -0
- package/dist/inline-value.d.ts +5 -0
- package/dist/inline-value.js +24 -0
- package/dist/numeric-input.d.ts +2 -0
- package/dist/numeric-input.js +9 -0
- package/dist/page-chrome.css +12 -49
- package/dist/recipes.d.ts +4 -0
- package/dist/recipes.js +4 -0
- package/dist/rendering/index.d.ts +87 -0
- package/dist/rendering/index.js +117 -0
- package/dist/styled-system/css/conditions.js +36 -0
- package/dist/styled-system/css/css.d.ts +22 -0
- package/dist/styled-system/css/css.js +45 -0
- package/dist/styled-system/css/cva.d.ts +6 -0
- package/dist/styled-system/css/cva.js +87 -0
- package/dist/styled-system/css/cx.d.ts +5 -0
- package/dist/styled-system/css/cx.js +15 -0
- package/dist/styled-system/css/index.d.ts +5 -0
- package/dist/styled-system/css/index.js +4 -0
- package/dist/styled-system/css/sva.d.ts +4 -0
- package/dist/styled-system/css/sva.js +46 -0
- package/dist/styled-system/helpers.js +316 -0
- package/dist/styled-system/patterns/aspect-ratio.d.ts +20 -0
- package/dist/styled-system/patterns/aspect-ratio.js +38 -0
- package/dist/styled-system/patterns/bleed.d.ts +21 -0
- package/dist/styled-system/patterns/bleed.js +24 -0
- package/dist/styled-system/patterns/box.d.ts +20 -0
- package/dist/styled-system/patterns/box.js +15 -0
- package/dist/styled-system/patterns/center.d.ts +20 -0
- package/dist/styled-system/patterns/center.js +21 -0
- package/dist/styled-system/patterns/circle.d.ts +20 -0
- package/dist/styled-system/patterns/circle.js +25 -0
- package/dist/styled-system/patterns/container.d.ts +20 -0
- package/dist/styled-system/patterns/container.js +21 -0
- package/dist/styled-system/patterns/cq.d.ts +21 -0
- package/dist/styled-system/patterns/cq.js +21 -0
- package/dist/styled-system/patterns/divider.d.ts +22 -0
- package/dist/styled-system/patterns/divider.js +25 -0
- package/dist/styled-system/patterns/flex.d.ts +26 -0
- package/dist/styled-system/patterns/flex.js +26 -0
- package/dist/styled-system/patterns/float.d.ts +23 -0
- package/dist/styled-system/patterns/float.js +52 -0
- package/dist/styled-system/patterns/grid-item.d.ts +25 -0
- package/dist/styled-system/patterns/grid-item.js +25 -0
- package/dist/styled-system/patterns/grid.d.ts +24 -0
- package/dist/styled-system/patterns/grid.js +27 -0
- package/dist/styled-system/patterns/hstack.d.ts +21 -0
- package/dist/styled-system/patterns/hstack.js +24 -0
- package/dist/styled-system/patterns/index.d.ts +21 -0
- package/dist/styled-system/patterns/index.js +20 -0
- package/dist/styled-system/patterns/link-overlay.d.ts +20 -0
- package/dist/styled-system/patterns/link-overlay.js +24 -0
- package/dist/styled-system/patterns/spacer.d.ts +20 -0
- package/dist/styled-system/patterns/spacer.js +25 -0
- package/dist/styled-system/patterns/square.d.ts +20 -0
- package/dist/styled-system/patterns/square.js +24 -0
- package/dist/styled-system/patterns/stack.d.ts +23 -0
- package/dist/styled-system/patterns/stack.js +24 -0
- package/dist/styled-system/patterns/visually-hidden.d.ts +20 -0
- package/dist/styled-system/patterns/visually-hidden.js +18 -0
- package/dist/styled-system/patterns/vstack.d.ts +21 -0
- package/dist/styled-system/patterns/vstack.js +24 -0
- package/dist/styled-system/patterns/wrap.d.ts +24 -0
- package/dist/styled-system/patterns/wrap.js +25 -0
- package/dist/styled-system/recipes/content-header.d.ts +31 -0
- package/dist/styled-system/recipes/content-header.js +65 -0
- package/dist/styled-system/recipes/content-page.d.ts +34 -0
- package/dist/styled-system/recipes/content-page.js +43 -0
- package/dist/styled-system/recipes/create-recipe.js +82 -0
- package/dist/styled-system/recipes/index.d.ts +17 -0
- package/dist/styled-system/recipes/index.js +16 -0
- package/dist/styled-system/recipes/metric-block.d.ts +34 -0
- package/dist/styled-system/recipes/metric-block.js +76 -0
- package/dist/styled-system/recipes/product-list.d.ts +31 -0
- package/dist/styled-system/recipes/product-list.js +73 -0
- package/dist/styled-system/recipes/product-section.d.ts +31 -0
- package/dist/styled-system/recipes/product-section.js +49 -0
- package/dist/styled-system/recipes/product-settings-row.d.ts +34 -0
- package/dist/styled-system/recipes/product-settings-row.js +58 -0
- package/dist/styled-system/recipes/product-settings.d.ts +31 -0
- package/dist/styled-system/recipes/product-settings.js +57 -0
- package/dist/styled-system/recipes/product-status.d.ts +34 -0
- package/dist/styled-system/recipes/product-status.js +45 -0
- package/dist/styled-system/recipes/product-toolbar.d.ts +31 -0
- package/dist/styled-system/recipes/product-toolbar.js +41 -0
- package/dist/styled-system/recipes/product-workspace.d.ts +34 -0
- package/dist/styled-system/recipes/product-workspace.js +58 -0
- package/dist/styled-system/recipes/surface-metric.d.ts +38 -0
- package/dist/styled-system/recipes/surface-metric.js +63 -0
- package/dist/styled-system/recipes/surface-table.d.ts +34 -0
- package/dist/styled-system/recipes/surface-table.js +62 -0
- package/dist/styled-system/recipes/ui-badge.d.ts +31 -0
- package/dist/styled-system/recipes/ui-badge.js +38 -0
- package/dist/styled-system/recipes/ui-button.d.ts +32 -0
- package/dist/styled-system/recipes/ui-button.js +43 -0
- package/dist/styled-system/recipes/ui-input.d.ts +31 -0
- package/dist/styled-system/recipes/ui-input.js +49 -0
- package/dist/styled-system/recipes/ui-textarea.d.ts +31 -0
- package/dist/styled-system/recipes/ui-textarea.js +24 -0
- package/dist/styled-system/styles.css +1257 -0
- package/dist/styled-system/tokens/index.d.ts +9 -0
- package/dist/styled-system/tokens/index.js +268 -0
- package/dist/styled-system/tokens/tokens.d.ts +36 -0
- package/dist/styled-system/types/composition.d.ts +227 -0
- package/dist/styled-system/types/conditions.d.ts +236 -0
- package/dist/styled-system/types/csstype.d.ts +22570 -0
- package/dist/styled-system/types/index.d.ts +6 -0
- package/dist/styled-system/types/parts.d.ts +8 -0
- package/dist/styled-system/types/pattern.d.ts +78 -0
- package/dist/styled-system/types/prop-type.d.ts +223 -0
- package/dist/styled-system/types/recipe.d.ts +181 -0
- package/dist/styled-system/types/selectors.d.ts +59 -0
- package/dist/styled-system/types/static-css.d.ts +56 -0
- package/dist/styled-system/types/style-props.d.ts +8088 -0
- package/dist/styled-system/types/system-types.d.ts +151 -0
- package/dist/styles/aliases.css +49 -0
- package/dist/styles/compatibility.epilogue.css +384 -0
- package/dist/styles/compatibility.prelude.css +5513 -0
- package/dist/styles/conditional-states.css +28 -0
- package/dist/styles/conditional-utilities.css +22 -0
- package/dist/styles/recipes.css +1262 -0
- package/dist/types.d.ts +3 -3
- package/dist/utils.d.ts +1 -1
- package/dist/utils.js +1 -1
- package/package.json +38 -14
- package/styles-compatibility.json +12 -0
|
@@ -1,310 +1,278 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
+
import { untrack } from 'svelte';
|
|
2
3
|
import { Plus, Trash2, RotateCcw, Filter as FilterIcon, Check } from '@lucide/svelte';
|
|
3
4
|
import { cn } from '../utils.js';
|
|
4
5
|
import { Button } from './ui/button/index.js';
|
|
5
6
|
import { Input } from './ui/input/index.js';
|
|
6
7
|
import { Select } from './ui/select/index.js';
|
|
7
8
|
import { useTranslation } from '@svadmin/core/i18n';
|
|
8
|
-
import type { CrudOperator, FieldDefinition,
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
id: string;
|
|
12
|
-
field: string;
|
|
13
|
-
operator: CrudOperator;
|
|
14
|
-
value: unknown;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
const i18n = useTranslation();
|
|
9
|
+
import type { CrudOperator, FieldDefinition, Filter } from '@svadmin/core';
|
|
10
|
+
import { readFilterTree, compileFilterTree, filterOperators, parseFilterInput, isCollectionOperator, isNullOperator,
|
|
11
|
+
FILTER_EDITOR_LIMITS, type FilterGroupNode, type FilterNode, type FilterRuleNode, type FilterEditorIssue, type FilterIssueCode } from './enterprise/filter-tree.js';
|
|
18
12
|
|
|
13
|
+
export interface FilterRuleItem { id: string; field: string; operator: CrudOperator; value: unknown }
|
|
19
14
|
interface Props {
|
|
20
15
|
fields?: FieldDefinition[];
|
|
21
16
|
filters?: Filter[];
|
|
22
17
|
logicalOperator?: 'and' | 'or';
|
|
18
|
+
disabled?: boolean;
|
|
23
19
|
class?: string;
|
|
24
20
|
onApply?: (filters: Filter[]) => void;
|
|
25
21
|
onReset?: () => void;
|
|
22
|
+
onInvalid?: (issues: FilterEditorIssue[]) => void;
|
|
26
23
|
}
|
|
24
|
+
let { fields = [], filters = $bindable([]), logicalOperator = $bindable('and'), disabled = false,
|
|
25
|
+
class: className, onApply, onReset, onInvalid }: Props = $props();
|
|
26
|
+
const uid = $props.id();
|
|
27
|
+
const i18n = useTranslation();
|
|
28
|
+
const chinese = $derived(i18n.locale.startsWith('zh'));
|
|
29
|
+
const availableFields = $derived(fields.filter((field) => field.filterable !== false));
|
|
30
|
+
let root = $state<FilterGroupNode>({ kind: 'group', id: 'root', operator: 'and', children: [], wrapped: false });
|
|
31
|
+
let loadIssues = $state<FilterEditorIssue[]>([]);
|
|
32
|
+
let attempted = $state(false);
|
|
33
|
+
let nextId = 0;
|
|
34
|
+
let lastInputFilters: Filter[] | undefined;
|
|
35
|
+
let preserved = $state.raw<ReadonlyMap<string, Filter>>(new Map());
|
|
36
|
+
const compilation = $derived(compileFilterTree({ ...root, operator: logicalOperator }, fields, preserved));
|
|
37
|
+
const issues = $derived(loadIssues.length ? loadIssues : attempted && !compilation.ok ? compilation.issues : []);
|
|
38
|
+
function countNodes(node: FilterNode): number { return 1 + (node.kind === 'group' ? node.children.reduce((sum, child) => sum + countNodes(child), 0) : 0); }
|
|
39
|
+
const nodeCount = $derived(countNodes(root));
|
|
40
|
+
const full = $derived(nodeCount >= FILTER_EDITOR_LIMITS.nodes);
|
|
27
41
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
{ value: 'null', label: '为空 (null)' },
|
|
47
|
-
{ value: 'nnull', label: '非空 (not null)' },
|
|
48
|
-
];
|
|
49
|
-
|
|
50
|
-
let rules = $state<FilterRuleItem[]>([]);
|
|
51
|
-
|
|
52
|
-
// Initialize rules from filters prop
|
|
42
|
+
function snapshotNode(node: FilterNode): Filter {
|
|
43
|
+
return node.kind === 'group' ? { operator: node.operator, value: node.children.map(snapshotNode) }
|
|
44
|
+
: { field: node.field, operator: node.operator, value: Array.isArray(node.value) ? [...node.value] : node.value };
|
|
45
|
+
}
|
|
46
|
+
function cannotEdit(node: FilterRuleNode): boolean {
|
|
47
|
+
return isCollectionOperator(node.operator) || node.value === ''
|
|
48
|
+
|| !compileFilterTree({ kind: 'group', id: 'probe', operator: 'and', children: [{ ...node, readonly: false }] }, fields).ok;
|
|
49
|
+
}
|
|
50
|
+
function remember(node: FilterNode, saved: Map<string, Filter>): void {
|
|
51
|
+
saved.set(node.id, snapshotNode(node));
|
|
52
|
+
if (node.kind === 'group') node.children.forEach(child => remember(child, saved));
|
|
53
|
+
else node.readonly = cannotEdit(node);
|
|
54
|
+
}
|
|
55
|
+
function refreshReadonly(node: FilterNode): void {
|
|
56
|
+
if (node.kind === 'group') { node.children.forEach(refreshReadonly); return; }
|
|
57
|
+
const original = preserved.get(node.id);
|
|
58
|
+
node.readonly = original !== undefined && JSON.stringify(original) === JSON.stringify(snapshotNode(node)) && cannotEdit(node);
|
|
59
|
+
}
|
|
53
60
|
$effect(() => {
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
const
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
for (let j = 0; j < item.value.length; j++) {
|
|
70
|
-
const sub = item.value[j];
|
|
71
|
-
if ('field' in sub) {
|
|
72
|
-
parsedRules.push({
|
|
73
|
-
id: `rule-${i}-${j}-${Date.now()}`,
|
|
74
|
-
field: sub.field,
|
|
75
|
-
operator: sub.operator,
|
|
76
|
-
value: sub.value,
|
|
77
|
-
});
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
}
|
|
61
|
+
const metadata = availableFields;
|
|
62
|
+
if (filters !== lastInputFilters) {
|
|
63
|
+
lastInputFilters = filters;
|
|
64
|
+
const parsed = readFilterTree(filters);
|
|
65
|
+
if (parsed.ok) {
|
|
66
|
+
const saved = new Map<string, Filter>();
|
|
67
|
+
untrack(() => remember(parsed.root, saved));
|
|
68
|
+
root = parsed.root;
|
|
69
|
+
preserved = saved;
|
|
70
|
+
loadIssues = [];
|
|
71
|
+
if (filters.length) logicalOperator = parsed.root.operator;
|
|
72
|
+
} else {
|
|
73
|
+
preserved = new Map();
|
|
74
|
+
loadIssues = parsed.issues;
|
|
75
|
+
root = { kind: 'group', id: 'root', operator: 'and', children: [], wrapped: false };
|
|
81
76
|
}
|
|
82
|
-
|
|
83
|
-
logicalOperator = nextLogical;
|
|
77
|
+
attempted = false;
|
|
84
78
|
} else {
|
|
85
|
-
|
|
79
|
+
untrack(() => { void metadata; refreshReadonly(root); });
|
|
86
80
|
}
|
|
87
81
|
});
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
const firstFieldDef = availableFields.find((f) => f.key === firstField);
|
|
96
|
-
const defaultOp: CrudOperator = firstFieldDef?.type === 'number' ? 'eq' : 'contains';
|
|
97
|
-
|
|
98
|
-
rules = [
|
|
99
|
-
...rules,
|
|
100
|
-
{
|
|
101
|
-
id: `rule-${Date.now()}-${Math.random().toString(36).slice(2, 6)}`,
|
|
102
|
-
field: firstField,
|
|
103
|
-
operator: defaultOp,
|
|
104
|
-
value: '',
|
|
105
|
-
},
|
|
106
|
-
];
|
|
82
|
+
function depthOf(target: FilterGroupNode, node: FilterGroupNode = root, depth = root.wrapped || logicalOperator === 'or' ? 1 : 0): number {
|
|
83
|
+
if (node === target) return depth;
|
|
84
|
+
for (const child of node.children) if (child.kind === 'group') {
|
|
85
|
+
const found = depthOf(target, child, depth + 1);
|
|
86
|
+
if (found >= 0) return found;
|
|
87
|
+
}
|
|
88
|
+
return -1;
|
|
107
89
|
}
|
|
108
90
|
|
|
109
|
-
|
|
110
|
-
|
|
91
|
+
function message(code: FilterIssueCode): string {
|
|
92
|
+
const labels: Record<FilterIssueCode, [string, string]> = {
|
|
93
|
+
'invalid-filter': ['条件结构不受支持,原条件未被修改', 'Unsupported filter structure; the original query is unchanged'],
|
|
94
|
+
limit: ['条件数量或深度超过限制', 'Filter count or depth exceeds the limit'],
|
|
95
|
+
'unknown-field': ['字段不可用于筛选', 'Field is unavailable for filtering'], operator: ['操作符不适用于此字段', 'Operator is not supported for this field'],
|
|
96
|
+
value: ['请输入有效的类型化值;集合与范围使用 JSON 数组', 'Enter a valid typed value; sets and ranges use JSON arrays'],
|
|
97
|
+
'empty-group': ['条件组不能为空', 'A filter group cannot be empty'],
|
|
98
|
+
};
|
|
99
|
+
return labels[code][chinese ? 0 : 1];
|
|
111
100
|
}
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
101
|
+
function appendRule(group: FilterGroupNode): void {
|
|
102
|
+
if (disabled || loadIssues.length || full || depthOf(group) < 0 || depthOf(group) >= FILTER_EDITOR_LIMITS.depth) return;
|
|
103
|
+
const first = availableFields[0];
|
|
104
|
+
if (!first) return;
|
|
105
|
+
group.children = [...group.children, { kind: 'rule', id: `draft-${++nextId}`, field: first.key,
|
|
106
|
+
operator: filterOperators(first).includes('contains') ? 'contains' : 'eq', value: undefined }];
|
|
107
|
+
}
|
|
108
|
+
function appendGroup(group: FilterGroupNode): void {
|
|
109
|
+
if (disabled || loadIssues.length || full || depthOf(group) < 0 || depthOf(group) >= FILTER_EDITOR_LIMITS.depth) return;
|
|
110
|
+
group.children = [...group.children, { kind: 'group', id: `draft-${++nextId}`, operator: 'and', children: [] }];
|
|
111
|
+
}
|
|
112
|
+
function removeNode(group: FilterGroupNode, id: string): void {
|
|
113
|
+
if (disabled || loadIssues.length) return;
|
|
114
|
+
group.children = group.children.filter((node) => node.id !== id);
|
|
115
|
+
}
|
|
116
|
+
function changeField(rule: FilterRuleNode, key: string): void {
|
|
117
|
+
if (disabled || rule.readonly) return;
|
|
118
|
+
rule.field = key;
|
|
119
|
+
rule.operator = filterOperators(fields.find((field) => field.key === key)).includes('contains') ? 'contains' : 'eq';
|
|
120
|
+
rule.value = undefined;
|
|
121
|
+
}
|
|
122
|
+
function changeOperator(rule: FilterRuleNode, next: CrudOperator): void {
|
|
123
|
+
if (disabled || rule.readonly) return;
|
|
124
|
+
if (isNullOperator(next)) rule.value = null;
|
|
125
|
+
else if (isNullOperator(rule.operator) || isCollectionOperator(rule.operator) !== isCollectionOperator(next)) rule.value = undefined;
|
|
126
|
+
rule.operator = next;
|
|
127
|
+
}
|
|
128
|
+
function changeValue(rule: FilterRuleNode, raw: string): void {
|
|
129
|
+
if (disabled || rule.readonly) return;
|
|
130
|
+
try { rule.value = parseFilterInput(raw, fields.find((field) => field.key === rule.field), rule.operator); }
|
|
131
|
+
catch { rule.value = raw; }
|
|
132
|
+
}
|
|
133
|
+
function valueText(value: unknown): string { return Array.isArray(value) ? JSON.stringify(value) : value === undefined || value === null ? '' : String(value); }
|
|
134
|
+
export function addRule(): void { appendRule(root); }
|
|
135
|
+
export function removeRule(index: number): void { const node = root.children[index]; if (node) removeNode(root, node.id); }
|
|
136
|
+
export function reset(): void {
|
|
137
|
+
if (disabled) return;
|
|
138
|
+
root = { kind: 'group', id: 'root', operator: 'and', children: [], wrapped: false };
|
|
139
|
+
loadIssues = [];
|
|
140
|
+
preserved = new Map();
|
|
141
|
+
logicalOperator = 'and';
|
|
115
142
|
filters = [];
|
|
143
|
+
attempted = false;
|
|
116
144
|
onReset?.();
|
|
117
145
|
}
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
let compiledFilters: Filter[] = [];
|
|
129
|
-
if (validFieldFilters.length === 1 || logicalOperator === 'and') {
|
|
130
|
-
compiledFilters = validFieldFilters;
|
|
131
|
-
} else if (validFieldFilters.length > 1 && logicalOperator === 'or') {
|
|
132
|
-
const logical: LogicalFilter = {
|
|
133
|
-
operator: 'or',
|
|
134
|
-
value: validFieldFilters,
|
|
135
|
-
};
|
|
136
|
-
compiledFilters = [logical];
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
filters = compiledFilters;
|
|
140
|
-
onApply?.(compiledFilters);
|
|
146
|
+
export function apply(): void {
|
|
147
|
+
if (disabled) return;
|
|
148
|
+
attempted = true;
|
|
149
|
+
if (loadIssues.length) { onInvalid?.(loadIssues); return; }
|
|
150
|
+
if (!compilation.ok) { onInvalid?.(compilation.issues); return; }
|
|
151
|
+
// 失败时绝不输出“剩余的有效条件”,避免改变查询的逻辑含义。
|
|
152
|
+
filters = compilation.filters;
|
|
153
|
+
lastInputFilters = filters;
|
|
154
|
+
onApply?.(compilation.filters);
|
|
141
155
|
}
|
|
142
156
|
</script>
|
|
143
157
|
|
|
144
|
-
|
|
145
|
-
<
|
|
146
|
-
<
|
|
147
|
-
|
|
148
|
-
<
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
</button>
|
|
163
|
-
<button
|
|
164
|
-
type="button"
|
|
165
|
-
class={cn(
|
|
166
|
-
'svadmin-u-07389a777c1f svadmin-u-d5eab218aa34 svadmin-u-465609a240a8 svadmin-u-359090c2d529 svadmin-u-ceb69a6b0e5f',
|
|
167
|
-
logicalOperator === 'or' ? 'svadmin-u-e6f9e383a762 svadmin-u-e83a7042bc91 svadmin-u-d4108abe6359 svadmin-u-cef5b893cf23' : 'svadmin-u-bfa603190748 svadmin-u-ea7b2e9e070e'
|
|
168
|
-
)}
|
|
169
|
-
onclick={() => (logicalOperator = 'or')}
|
|
170
|
-
>
|
|
171
|
-
OR (或)
|
|
172
|
-
</button>
|
|
173
|
-
</div>
|
|
174
|
-
{/if}
|
|
175
|
-
</div>
|
|
176
|
-
|
|
177
|
-
<div class="svadmin-u-60fbb7713999 svadmin-u-3960ffc248d9 svadmin-u-58284b4ea568">
|
|
178
|
-
<Button
|
|
179
|
-
variant="ghost"
|
|
180
|
-
size="sm"
|
|
181
|
-
class="svadmin-u-ed8a5df7b2fb svadmin-u-44ee8ba0a421 svadmin-u-359090c2d529"
|
|
182
|
-
data-testid="filter-builder-reset"
|
|
183
|
-
onclick={reset}
|
|
184
|
-
>
|
|
185
|
-
<RotateCcw class="svadmin-u-7fc7f732bf7e svadmin-u-bf600f8e029c" />
|
|
186
|
-
{i18n.t('common.reset', undefined) ?? '重置'}
|
|
187
|
-
</Button>
|
|
188
|
-
<Button
|
|
189
|
-
variant="default"
|
|
190
|
-
size="sm"
|
|
191
|
-
class="svadmin-u-ed8a5df7b2fb svadmin-u-44ee8ba0a421 svadmin-u-359090c2d529"
|
|
192
|
-
data-testid="filter-builder-apply"
|
|
193
|
-
onclick={apply}
|
|
194
|
-
>
|
|
195
|
-
<Check class="svadmin-u-7fc7f732bf7e svadmin-u-bf600f8e029c" />
|
|
196
|
-
{i18n.t('common.confirm', undefined) ?? '应用'}
|
|
158
|
+
{#snippet groupEditor(group: FilterGroupNode, depth: number, top: boolean)}
|
|
159
|
+
<fieldset class="filter-group" data-testid={top ? 'filter-builder-root' : 'filter-builder-group'} data-filter-group={group.id} disabled={disabled}>
|
|
160
|
+
<legend>{top ? (chinese ? '筛选条件' : 'Filters') : (chinese ? '条件组' : 'Filter group')}</legend>
|
|
161
|
+
<div class="group-actions">
|
|
162
|
+
<label for={`${uid}-${group.id}-logic`}>{chinese ? '逻辑' : 'Logic'}</label>
|
|
163
|
+
<Select id={`${uid}-${group.id}-logic`} aria-label={chinese ? '组合逻辑' : 'Group logic'}
|
|
164
|
+
value={top ? logicalOperator : group.operator}
|
|
165
|
+
onchange={(event: Event) => {
|
|
166
|
+
const target = event.currentTarget;
|
|
167
|
+
if (disabled || !(target instanceof HTMLSelectElement)) return;
|
|
168
|
+
const next = target.value === 'or' ? 'or' : 'and';
|
|
169
|
+
if (top) logicalOperator = next; else group.operator = next;
|
|
170
|
+
}}>
|
|
171
|
+
<option value="and">AND</option><option value="or">OR</option>
|
|
172
|
+
</Select>
|
|
173
|
+
<Button type="button" size="sm" variant="outline" disabled={full || depth >= FILTER_EDITOR_LIMITS.depth || !availableFields.length}
|
|
174
|
+
data-testid={top ? 'filter-builder-add-rule' : 'filter-group-add-rule'} onclick={() => appendRule(group)}>
|
|
175
|
+
<Plus size={14} aria-hidden="true" />{i18n.t('common.addRule')}
|
|
197
176
|
</Button>
|
|
177
|
+
<Button type="button" size="sm" variant="ghost" disabled={full || depth >= FILTER_EDITOR_LIMITS.depth - 1}
|
|
178
|
+
data-testid="filter-builder-add-group" onclick={() => appendGroup(group)}>{chinese ? '添加条件组' : 'Add group'}</Button>
|
|
198
179
|
</div>
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
<div class="svadmin-u-60fbb7713999 svadmin-u-1eb5c6df38c1 svadmin-u-3fd0f778c8d9 svadmin-u-3960ffc248d9 svadmin-u-77a2a20e90d4 svadmin-u-421ac2be5045 svadmin-u-967d113a1451 svadmin-u-7660b450905a svadmin-u-ca6bcd4b6f3f svadmin-u-6ee2d41e2d2d">
|
|
205
|
-
<!-- Field select -->
|
|
206
|
-
<div class="svadmin-u-df403bbae8fc svadmin-u-012fbd121f37">
|
|
207
|
-
<Select
|
|
208
|
-
class="svadmin-u-ed8a5df7b2fb svadmin-u-359090c2d529"
|
|
209
|
-
value={rule.field}
|
|
210
|
-
onchange={(e: Event) => {
|
|
211
|
-
rule.field = (e.currentTarget as HTMLSelectElement).value;
|
|
212
|
-
}}
|
|
213
|
-
>
|
|
214
|
-
{#each availableFields as f (f.key)}
|
|
215
|
-
<option value={f.key}>{f.label || f.key}</option>
|
|
216
|
-
{/each}
|
|
217
|
-
</Select>
|
|
180
|
+
{#each group.children as node (node.id)}
|
|
181
|
+
{#if node.kind === 'group'}
|
|
182
|
+
<div class="nested-group">
|
|
183
|
+
{@render groupEditor(node, depth + 1, false)}
|
|
184
|
+
<Button type="button" size="sm" variant="ghost" aria-label={chinese ? '删除条件组' : 'Remove group'} onclick={() => removeNode(group, node.id)}><Trash2 size={14} aria-hidden="true" /></Button>
|
|
218
185
|
</div>
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
>
|
|
229
|
-
{
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
</div>
|
|
234
|
-
|
|
235
|
-
<!-- Value input -->
|
|
236
|
-
<div class="svadmin-u-36e579c0b41c svadmin-u-a9ef791a0777">
|
|
237
|
-
{#if rule.operator === 'null' || rule.operator === 'nnull'}
|
|
238
|
-
<div class="svadmin-u-ed8a5df7b2fb svadmin-u-60fbb7713999 svadmin-u-3960ffc248d9 svadmin-u-0e17f2bd9074 svadmin-u-359090c2d529 svadmin-u-bfa603190748 italic svadmin-u-2859c861d7de svadmin-u-421ac2be5045">
|
|
239
|
-
无需填值
|
|
240
|
-
</div>
|
|
241
|
-
{:else if fieldDef?.type === 'select' && fieldDef.options}
|
|
242
|
-
<Select
|
|
243
|
-
class="svadmin-u-ed8a5df7b2fb svadmin-u-359090c2d529"
|
|
244
|
-
value={String(rule.value ?? '')}
|
|
245
|
-
onchange={(e: Event) => {
|
|
246
|
-
rule.value = (e.currentTarget as HTMLSelectElement).value;
|
|
247
|
-
}}
|
|
248
|
-
>
|
|
249
|
-
<option value="">{i18n.t('field.selectPlaceholder', undefined) ?? '请选择'}</option>
|
|
250
|
-
{#each fieldDef.options as opt (opt.value)}
|
|
251
|
-
<option value={String(opt.value)}>{opt.label}</option>
|
|
252
|
-
{/each}
|
|
186
|
+
{:else}
|
|
187
|
+
{@const field = availableFields.find((item) => item.key === node.field)}
|
|
188
|
+
{@const allowed = filterOperators(field)}
|
|
189
|
+
{@const collection = isCollectionOperator(node.operator)}
|
|
190
|
+
{@const invalid = issues.some((issue) => issue.path === node.id)}
|
|
191
|
+
<div class="filter-rule" data-testid="filter-builder-rule" data-filter-rule={node.id}>
|
|
192
|
+
{#if node.readonly}
|
|
193
|
+
<output aria-label={i18n.t('filter.readonly')}>{node.field} {node.operator} {JSON.stringify(node.value)}</output>
|
|
194
|
+
{:else}
|
|
195
|
+
<div>
|
|
196
|
+
<label for={`${uid}-${node.id}-field`}>{chinese ? '字段' : 'Field'}</label>
|
|
197
|
+
<Select id={`${uid}-${node.id}-field`} value={node.field} onchange={(event: Event) => { if (event.currentTarget instanceof HTMLSelectElement) changeField(node, event.currentTarget.value); }}>
|
|
198
|
+
{#if !field}<option value={node.field} disabled>{node.field}</option>{/if}
|
|
199
|
+
{#each availableFields as option (option.key)}<option value={option.key}>{option.label || option.key}</option>{/each}
|
|
253
200
|
</Select>
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
rule.value = val === 'true' ? true : val === 'false' ? false : '';
|
|
261
|
-
}}
|
|
262
|
-
>
|
|
263
|
-
<option value="">{i18n.t('field.selectPlaceholder', undefined) ?? '请选择'}</option>
|
|
264
|
-
<option value="true">{i18n.t('common.yes', undefined) ?? '是 (true)'}</option>
|
|
265
|
-
<option value="false">{i18n.t('common.no', undefined) ?? '否 (false)'}</option>
|
|
201
|
+
</div>
|
|
202
|
+
<div>
|
|
203
|
+
<label for={`${uid}-${node.id}-operator`}>{chinese ? '操作符' : 'Operator'}</label>
|
|
204
|
+
<Select id={`${uid}-${node.id}-operator`} value={node.operator} onchange={(event: Event) => { if (event.currentTarget instanceof HTMLSelectElement) changeOperator(node, event.currentTarget.value as CrudOperator); }}>
|
|
205
|
+
{#if !allowed.includes(node.operator)}<option value={node.operator} disabled>{node.operator}</option>{/if}
|
|
206
|
+
{#each allowed as operator (operator)}<option value={operator}>{operator}</option>{/each}
|
|
266
207
|
</Select>
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
208
|
+
</div>
|
|
209
|
+
<div>
|
|
210
|
+
<label for={`${uid}-${node.id}-value`}>{collection ? (chinese ? '值(JSON 数组)' : 'Value (JSON array)') : (chinese ? '值' : 'Value')}</label>
|
|
211
|
+
{#if isNullOperator(node.operator)}
|
|
212
|
+
<Input id={`${uid}-${node.id}-value`} value={chinese ? '无需填值' : 'No value required'} disabled />
|
|
213
|
+
{:else if !collection && field?.options?.length}
|
|
214
|
+
<Select id={`${uid}-${node.id}-value`} aria-invalid={invalid}
|
|
215
|
+
value={field.options.findIndex((option) => option.value === node.value) < 0 ? '' : String(field.options.findIndex((option) => option.value === node.value))}
|
|
216
|
+
onchange={(event: Event) => {
|
|
217
|
+
if (disabled || node.readonly || !(event.currentTarget instanceof HTMLSelectElement)) return;
|
|
218
|
+
const raw = event.currentTarget.value;
|
|
219
|
+
if (raw === '') { node.value = undefined; return; }
|
|
220
|
+
if (!/^(0|[1-9][0-9]*)$/u.test(raw)) return;
|
|
221
|
+
const option = field.options?.[Number(raw)];
|
|
222
|
+
if (option && !option.disabled) node.value = option.value;
|
|
223
|
+
}}>
|
|
224
|
+
<option value="">{chinese ? '请选择' : 'Choose'}</option>
|
|
225
|
+
{#each field.options as option, index (index)}<option value={String(index)} disabled={option.disabled}>{option.label}</option>{/each}
|
|
226
|
+
</Select>
|
|
227
|
+
{:else if !collection && field?.type === 'boolean'}
|
|
228
|
+
<Select id={`${uid}-${node.id}-value`} aria-invalid={invalid} value={valueText(node.value)}
|
|
229
|
+
onchange={(event: Event) => { if (event.currentTarget instanceof HTMLSelectElement) changeValue(node, event.currentTarget.value); }}>
|
|
230
|
+
<option value="">{chinese ? '请选择' : 'Choose'}</option><option value="true">true</option><option value="false">false</option>
|
|
231
|
+
</Select>
|
|
232
|
+
{:else if !collection && field?.type === 'number'}
|
|
233
|
+
<Input id={`${uid}-${node.id}-value`} type="number" step="any" aria-invalid={invalid} value={typeof node.value === 'number' ? node.value : undefined}
|
|
234
|
+
oninput={(event) => { if (event.currentTarget instanceof HTMLInputElement) changeValue(node, event.currentTarget.value); }} />
|
|
235
|
+
{:else}
|
|
236
|
+
<Input id={`${uid}-${node.id}-value`} type="text" aria-invalid={invalid} value={valueText(node.value)} placeholder={collection ? '[1, 2]' : ''}
|
|
237
|
+
oninput={(event) => { if (event.currentTarget instanceof HTMLInputElement) changeValue(node, event.currentTarget.value); }} />
|
|
238
|
+
{/if}
|
|
239
|
+
</div>
|
|
278
240
|
{/if}
|
|
241
|
+
<Button type="button" size="sm" variant="ghost" aria-label={chinese ? '删除条件' : 'Remove rule'} onclick={() => removeNode(group, node.id)}><Trash2 size={14} aria-hidden="true" /></Button>
|
|
279
242
|
</div>
|
|
280
|
-
|
|
281
|
-
<!-- Remove button -->
|
|
282
|
-
<Button
|
|
283
|
-
type="button"
|
|
284
|
-
variant="ghost"
|
|
285
|
-
size="icon-sm"
|
|
286
|
-
class="svadmin-u-012fbd121f37 svadmin-u-bfa603190748 svadmin-u-51e95020d6f2"
|
|
287
|
-
onclick={() => removeRule(index)}
|
|
288
|
-
>
|
|
289
|
-
<Trash2 class="svadmin-u-7fc7f732bf7e svadmin-u-bf600f8e029c" />
|
|
290
|
-
</Button>
|
|
291
|
-
</div>
|
|
243
|
+
{/if}
|
|
292
244
|
{:else}
|
|
293
|
-
<
|
|
294
|
-
暂无筛选条件,点击下方按钮添加规则
|
|
295
|
-
</div>
|
|
245
|
+
<p class="empty">{chinese ? '暂无筛选条件,请添加规则。' : 'No filters yet. Add a rule.'}</p>
|
|
296
246
|
{/each}
|
|
297
|
-
</
|
|
247
|
+
</fieldset>
|
|
248
|
+
{/snippet}
|
|
298
249
|
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
>
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
</Button>
|
|
250
|
+
<div class={cn('svadmin-filter-builder', className)} data-testid="filter-builder">
|
|
251
|
+
<div class="builder-header">
|
|
252
|
+
<span><FilterIcon size={16} aria-hidden="true" /> {i18n.t('common.filterBuilder')}</span>
|
|
253
|
+
<div class="group-actions">
|
|
254
|
+
<Button type="button" variant="ghost" size="sm" disabled={disabled} data-testid="filter-builder-reset" onclick={reset}><RotateCcw size={14} aria-hidden="true" />{i18n.t('common.reset')}</Button>
|
|
255
|
+
<Button type="button" size="sm" disabled={disabled} data-testid="filter-builder-apply" onclick={apply}><Check size={14} aria-hidden="true" />{i18n.t('common.confirm')}</Button>
|
|
256
|
+
</div>
|
|
257
|
+
</div>
|
|
258
|
+
{#if issues.length}<div role="alert" class="errors" data-testid="filter-builder-errors">{#each issues as issue, index (`${issue.path}-${issue.code}-${index}`)}<p>{issue.path}: {message(issue.code)}</p>{/each}</div>{/if}
|
|
259
|
+
{#if !loadIssues.length}{@render groupEditor(root, root.wrapped || logicalOperator === 'or' ? 1 : 0, true)}{/if}
|
|
310
260
|
</div>
|
|
261
|
+
|
|
262
|
+
<style>
|
|
263
|
+
.svadmin-filter-builder { color: var(--foreground); background: var(--card, var(--background)); border: 1px solid var(--border); border-radius: var(--radius, .5rem); padding: .75rem; font-size: .875rem; }
|
|
264
|
+
.builder-header, .group-actions, .nested-group { display: flex; align-items: center; gap: .5rem; }
|
|
265
|
+
.builder-header { justify-content: space-between; flex-wrap: wrap; padding-block-end: .75rem; }
|
|
266
|
+
.builder-header > span { display: inline-flex; align-items: center; gap: .5rem; font-weight: 600; }
|
|
267
|
+
.group-actions { flex-wrap: wrap; }
|
|
268
|
+
.filter-group { min-width: 0; width: 100%; border: 1px solid var(--border); border-radius: var(--radius, .5rem); padding: .75rem; margin: .5rem 0 0; }
|
|
269
|
+
legend { padding-inline: .25rem; color: var(--muted-foreground); }
|
|
270
|
+
.filter-rule { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr) minmax(0, 2fr) auto; align-items: end; gap: .5rem; margin-block-start: .75rem; }
|
|
271
|
+
.filter-rule > div { min-width: 0; }
|
|
272
|
+
.filter-rule > output { grid-column: 1 / -2; overflow-wrap: anywhere; }
|
|
273
|
+
.filter-rule label { display: block; margin-block-end: .25rem; color: var(--muted-foreground); }
|
|
274
|
+
.empty { padding-block: .75rem; color: var(--muted-foreground); }
|
|
275
|
+
.errors { color: var(--destructive); overflow-wrap: anywhere; }
|
|
276
|
+
.nested-group { align-items: flex-start; }
|
|
277
|
+
@media (max-width: 640px) { .filter-rule { grid-template-columns: minmax(0, 1fr); } .nested-group { gap: .25rem; } }
|
|
278
|
+
</style>
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { CrudOperator, FieldDefinition, Filter } from '@svadmin/core';
|
|
2
|
+
import { type FilterEditorIssue } from './enterprise/filter-tree.js';
|
|
2
3
|
export interface FilterRuleItem {
|
|
3
4
|
id: string;
|
|
4
5
|
field: string;
|
|
@@ -9,9 +10,11 @@ interface Props {
|
|
|
9
10
|
fields?: FieldDefinition[];
|
|
10
11
|
filters?: Filter[];
|
|
11
12
|
logicalOperator?: 'and' | 'or';
|
|
13
|
+
disabled?: boolean;
|
|
12
14
|
class?: string;
|
|
13
15
|
onApply?: (filters: Filter[]) => void;
|
|
14
16
|
onReset?: () => void;
|
|
17
|
+
onInvalid?: (issues: FilterEditorIssue[]) => void;
|
|
15
18
|
}
|
|
16
19
|
declare const FilterBuilder: import("svelte").Component<Props, {
|
|
17
20
|
addRule: () => void;
|
|
@@ -19,6 +19,6 @@ interface Props {
|
|
|
19
19
|
trigger?: Snippet;
|
|
20
20
|
class?: string;
|
|
21
21
|
}
|
|
22
|
-
declare const FilterDropdown: import("svelte").Component<Props, {}, "
|
|
22
|
+
declare const FilterDropdown: import("svelte").Component<Props, {}, "open" | "value">;
|
|
23
23
|
type FilterDropdown = ReturnType<typeof FilterDropdown>;
|
|
24
24
|
export default FilterDropdown;
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
-
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
3
2
|
import { Moon, Sun, Search, MonitorUp } from '@lucide/svelte';
|
|
4
3
|
import { Button } from './ui/button';
|
|
5
4
|
import TooltipButton from './TooltipButton.svelte';
|
|
@@ -33,11 +32,11 @@
|
|
|
33
32
|
}>();
|
|
34
33
|
|
|
35
34
|
// Retrieve optional component overrides from registry
|
|
36
|
-
const registry = getComponentRegistry()
|
|
37
|
-
const CustomBreadcrumbs = registry
|
|
38
|
-
const CustomThemeToggle = registry
|
|
39
|
-
const CustomUserMenu = registry
|
|
40
|
-
const CustomNotificationPanel = registry
|
|
35
|
+
const registry = getComponentRegistry();
|
|
36
|
+
const CustomBreadcrumbs = registry?.Breadcrumbs;
|
|
37
|
+
const CustomThemeToggle = registry?.ThemeToggle;
|
|
38
|
+
const CustomUserMenu = registry?.UserMenu;
|
|
39
|
+
const CustomNotificationPanel = registry?.NotificationPanel;
|
|
41
40
|
</script>
|
|
42
41
|
|
|
43
42
|
<header class="svadmin-u-3e0fd166d494 svadmin-u-2167406b24d7 svadmin-u-0f2fff0ae96e svadmin-u-60fbb7713999 svadmin-u-07017cde3e97 svadmin-u-6da6a3c3f741 svadmin-u-012fbd121f37 svadmin-u-3960ffc248d9 svadmin-u-8ef2268efbbc svadmin-u-65fdbade2025 svadmin-u-6ee2d41e2d2d svadmin-u-16d6f96aa157 svadmin-u-1ca6dd1e47c4 svadmin-u-f0faeb26d656 svadmin-u-8a3831239aa5">
|