@ngbplatform/ui 1.3.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/LICENSE +201 -0
- package/README.md +44 -0
- package/package.json +61 -0
- package/public/favicon.svg +6 -0
- package/public/silent-check-sso.html +8 -0
- package/src/assets/ngb_logo.svg +17 -0
- package/src/assets/ngb_logo_color.svg +17 -0
- package/src/index.ts +1023 -0
- package/src/ngb/README.md +27 -0
- package/src/ngb/accounting/NgbAccountingPeriodClosingPage.vue +1157 -0
- package/src/ngb/accounting/NgbChartOfAccountEditor.vue +590 -0
- package/src/ngb/accounting/NgbChartOfAccountsPage.vue +420 -0
- package/src/ngb/accounting/NgbGeneralJournalEntryEditPage.vue +796 -0
- package/src/ngb/accounting/NgbGeneralJournalEntryLinesEditor.vue +454 -0
- package/src/ngb/accounting/NgbGeneralJournalEntryListPage.vue +186 -0
- package/src/ngb/accounting/api.ts +79 -0
- package/src/ngb/accounting/generalJournalEntry.ts +127 -0
- package/src/ngb/accounting/generalJournalEntryApi.ts +112 -0
- package/src/ngb/accounting/generalJournalEntryTypes.ts +155 -0
- package/src/ngb/accounting/navigation.ts +83 -0
- package/src/ngb/accounting/periodClosing.ts +75 -0
- package/src/ngb/accounting/periodClosingApi.ts +60 -0
- package/src/ngb/accounting/periodClosingTypes.ts +73 -0
- package/src/ngb/accounting/types.ts +57 -0
- package/src/ngb/api/audit.ts +23 -0
- package/src/ngb/api/catalogs.ts +50 -0
- package/src/ngb/api/contracts.ts +285 -0
- package/src/ngb/api/documents.ts +170 -0
- package/src/ngb/api/http.ts +446 -0
- package/src/ngb/api/lookups.ts +14 -0
- package/src/ngb/api/types.ts +6 -0
- package/src/ngb/auth/index.ts +13 -0
- package/src/ngb/auth/keycloak.ts +374 -0
- package/src/ngb/auth/router.ts +35 -0
- package/src/ngb/auth/types.ts +12 -0
- package/src/ngb/auth/useAuthStore.ts +209 -0
- package/src/ngb/command-palette/NgbCommandPaletteDialog.vue +342 -0
- package/src/ngb/command-palette/NgbCommandPaletteEmptyState.vue +55 -0
- package/src/ngb/command-palette/NgbCommandPaletteFooterHints.vue +29 -0
- package/src/ngb/command-palette/api.ts +10 -0
- package/src/ngb/command-palette/config.ts +16 -0
- package/src/ngb/command-palette/presentation.ts +85 -0
- package/src/ngb/command-palette/search.ts +130 -0
- package/src/ngb/command-palette/staticItems.ts +243 -0
- package/src/ngb/command-palette/storage.ts +45 -0
- package/src/ngb/command-palette/store.ts +716 -0
- package/src/ngb/command-palette/types.ts +120 -0
- package/src/ngb/command-palette/useCommandPaletteHotkeys.ts +27 -0
- package/src/ngb/command-palette/useCommandPalettePageContext.ts +18 -0
- package/src/ngb/components/NgbConfirmDialog.vue +63 -0
- package/src/ngb/components/NgbDialog.vue +44 -0
- package/src/ngb/components/NgbDrawer.vue +156 -0
- package/src/ngb/components/NgbHeaderActionCluster.vue +140 -0
- package/src/ngb/components/NgbModalShell.vue +99 -0
- package/src/ngb/components/NgbToolbar.vue +14 -0
- package/src/ngb/components/forms/NgbFormLayout.vue +13 -0
- package/src/ngb/components/forms/NgbFormRow.vue +21 -0
- package/src/ngb/components/forms/NgbFormSection.vue +16 -0
- package/src/ngb/components/forms/NgbValidationSummary.vue +18 -0
- package/src/ngb/components/navigation/NgbNavigationTree.vue +68 -0
- package/src/ngb/components/navigation/NgbTreeNode.vue +94 -0
- package/src/ngb/components/register/NgbColumnChooser.vue +101 -0
- package/src/ngb/components/register/NgbRegisterGrid.vue +473 -0
- package/src/ngb/components/register/registerTypes.ts +51 -0
- package/src/ngb/components/register/useRegisterColumnResize.ts +99 -0
- package/src/ngb/components/register/useRegisterColumnState.ts +220 -0
- package/src/ngb/components/register/useRegisterRows.ts +209 -0
- package/src/ngb/components/register/useRegisterViewport.ts +56 -0
- package/src/ngb/editor/NgbDocumentEffectsPage.vue +512 -0
- package/src/ngb/editor/NgbDocumentFlowPage.vue +409 -0
- package/src/ngb/editor/NgbDocumentPrintPage.vue +712 -0
- package/src/ngb/editor/NgbEditorDiscardDialog.vue +40 -0
- package/src/ngb/editor/NgbEntityAuditSidebar.vue +288 -0
- package/src/ngb/editor/NgbEntityEditor.vue +250 -0
- package/src/ngb/editor/NgbEntityEditorDrawerActions.vue +88 -0
- package/src/ngb/editor/NgbEntityEditorHeader.vue +114 -0
- package/src/ngb/editor/catalogNavigation.ts +13 -0
- package/src/ngb/editor/config.ts +252 -0
- package/src/ngb/editor/documentCopyDraft.ts +245 -0
- package/src/ngb/editor/documentEffects.ts +43 -0
- package/src/ngb/editor/documentEffectsDateFormatting.ts +18 -0
- package/src/ngb/editor/documentNavigation.ts +115 -0
- package/src/ngb/editor/documentStatus.ts +45 -0
- package/src/ngb/editor/entityEditorErrors.ts +162 -0
- package/src/ngb/editor/entityEditorPersistence.ts +241 -0
- package/src/ngb/editor/extensions.ts +40 -0
- package/src/ngb/editor/types.ts +295 -0
- package/src/ngb/editor/useConfiguredEntityEditorDocumentActions.ts +183 -0
- package/src/ngb/editor/useDocumentEditorDrawerState.ts +152 -0
- package/src/ngb/editor/useEditorDrawerState.ts +88 -0
- package/src/ngb/editor/useEntityEditorBusinessContext.ts +97 -0
- package/src/ngb/editor/useEntityEditorCapabilities.ts +190 -0
- package/src/ngb/editor/useEntityEditorCommandPalette.ts +154 -0
- package/src/ngb/editor/useEntityEditorCommitHandlers.ts +102 -0
- package/src/ngb/editor/useEntityEditorHeaderActions.ts +253 -0
- package/src/ngb/editor/useEntityEditorLeaveGuard.ts +66 -0
- package/src/ngb/editor/useEntityEditorNavigationActions.ts +235 -0
- package/src/ngb/editor/useEntityEditorOutputs.ts +59 -0
- package/src/ngb/editor/useEntityEditorPageActions.ts +57 -0
- package/src/ngb/editor/useRouteQueryEditorDrawer.ts +173 -0
- package/src/ngb/env/runtimeConfig.ts +25 -0
- package/src/ngb/lookup/config.ts +42 -0
- package/src/ngb/lookup/defaultConfig.ts +136 -0
- package/src/ngb/lookup/navigation.ts +76 -0
- package/src/ngb/lookup/prefetch.ts +40 -0
- package/src/ngb/lookup/store.ts +261 -0
- package/src/ngb/metadata/NgbDocumentListFiltersDrawer.vue +78 -0
- package/src/ngb/metadata/NgbDocumentPeriodFilter.vue +53 -0
- package/src/ngb/metadata/NgbEntityForm.vue +84 -0
- package/src/ngb/metadata/NgbEntityFormFieldsBlock.vue +110 -0
- package/src/ngb/metadata/NgbEntityListPageHeader.vue +80 -0
- package/src/ngb/metadata/NgbFilterFieldControl.vue +104 -0
- package/src/ngb/metadata/NgbMetadataCatalogEditPage.vue +42 -0
- package/src/ngb/metadata/NgbMetadataCatalogListPage.vue +269 -0
- package/src/ngb/metadata/NgbMetadataDocumentEditPage.vue +68 -0
- package/src/ngb/metadata/NgbMetadataDocumentListPage.vue +353 -0
- package/src/ngb/metadata/NgbMetadataFieldRenderer.vue +124 -0
- package/src/ngb/metadata/NgbMetadataLookupControl.vue +92 -0
- package/src/ngb/metadata/NgbRecycleBinFilter.vue +53 -0
- package/src/ngb/metadata/NgbRegisterPageLayout.vue +143 -0
- package/src/ngb/metadata/config.ts +28 -0
- package/src/ngb/metadata/dataTypes.ts +27 -0
- package/src/ngb/metadata/entityForm.ts +114 -0
- package/src/ngb/metadata/entityModel.ts +34 -0
- package/src/ngb/metadata/entityValueFormatting.ts +83 -0
- package/src/ngb/metadata/fieldRendererState.ts +84 -0
- package/src/ngb/metadata/filtering.ts +153 -0
- package/src/ngb/metadata/lookup.ts +25 -0
- package/src/ngb/metadata/monthPagedListQuery.ts +108 -0
- package/src/ngb/metadata/normalization.ts +100 -0
- package/src/ngb/metadata/referenceHydration.ts +99 -0
- package/src/ngb/metadata/register.ts +135 -0
- package/src/ngb/metadata/routePages.ts +86 -0
- package/src/ngb/metadata/store.ts +35 -0
- package/src/ngb/metadata/types.ts +276 -0
- package/src/ngb/metadata/useMetadataListFilters.ts +306 -0
- package/src/ngb/metadata/useMetadataRegisterPageData.ts +246 -0
- package/src/ngb/metadata/useValidationFocus.ts +49 -0
- package/src/ngb/primitives/NgbBadge.vue +31 -0
- package/src/ngb/primitives/NgbButton.vue +53 -0
- package/src/ngb/primitives/NgbCheckbox.vue +33 -0
- package/src/ngb/primitives/NgbDatePicker.vue +141 -0
- package/src/ngb/primitives/NgbIcon.vue +545 -0
- package/src/ngb/primitives/NgbInput.vue +57 -0
- package/src/ngb/primitives/NgbLookup.vue +264 -0
- package/src/ngb/primitives/NgbMonthPicker.vue +132 -0
- package/src/ngb/primitives/NgbMultiSelect.vue +179 -0
- package/src/ngb/primitives/NgbPeriodPicker.vue +125 -0
- package/src/ngb/primitives/NgbPickerNavButton.vue +31 -0
- package/src/ngb/primitives/NgbPickerPopover.vue +79 -0
- package/src/ngb/primitives/NgbRadioGroup.vue +53 -0
- package/src/ngb/primitives/NgbSelect.vue +108 -0
- package/src/ngb/primitives/NgbStatusIcon.vue +63 -0
- package/src/ngb/primitives/NgbSwitch.vue +42 -0
- package/src/ngb/primitives/NgbTabs.vue +116 -0
- package/src/ngb/primitives/NgbToastHost.vue +49 -0
- package/src/ngb/primitives/NgbTooltip.vue +27 -0
- package/src/ngb/primitives/iconNames.ts +83 -0
- package/src/ngb/primitives/toast.ts +66 -0
- package/src/ngb/primitives/useAsyncComboboxQuery.ts +85 -0
- package/src/ngb/primitives/useFloatingDropdownPosition.ts +129 -0
- package/src/ngb/reporting/NgbReportComposerCollectionSection.vue +106 -0
- package/src/ngb/reporting/NgbReportComposerPanel.vue +1187 -0
- package/src/ngb/reporting/NgbReportDateRangeFilter.vue +54 -0
- package/src/ngb/reporting/NgbReportPage.vue +1124 -0
- package/src/ngb/reporting/NgbReportSheet.vue +467 -0
- package/src/ngb/reporting/api.ts +35 -0
- package/src/ngb/reporting/composer.ts +904 -0
- package/src/ngb/reporting/config.ts +120 -0
- package/src/ngb/reporting/defaultConfig.ts +15 -0
- package/src/ngb/reporting/lookupFilters.ts +43 -0
- package/src/ngb/reporting/navigation.ts +181 -0
- package/src/ngb/reporting/pageHelpers.ts +73 -0
- package/src/ngb/reporting/pageSession.ts +92 -0
- package/src/ngb/reporting/paging.ts +66 -0
- package/src/ngb/reporting/types.ts +333 -0
- package/src/ngb/router/backNavigation.ts +122 -0
- package/src/ngb/router/queryParams.ts +129 -0
- package/src/ngb/router/queryState.ts +121 -0
- package/src/ngb/router/routeAliases.ts +66 -0
- package/src/ngb/router/routeParams.ts +15 -0
- package/src/ngb/router/shareLink.ts +39 -0
- package/src/ngb/security/NgbAccessAuditPanel.vue +32 -0
- package/src/ngb/security/NgbAccessDeniedState.vue +27 -0
- package/src/ngb/security/NgbEffectiveAccessPanel.vue +60 -0
- package/src/ngb/security/NgbPermissionMatrix.vue +160 -0
- package/src/ngb/security/NgbRoleEditorPage.vue +319 -0
- package/src/ngb/security/NgbRolesPage.vue +125 -0
- package/src/ngb/security/NgbUserEditorPage.vue +615 -0
- package/src/ngb/security/NgbUsersPage.vue +125 -0
- package/src/ngb/security/api.ts +91 -0
- package/src/ngb/security/audit.ts +21 -0
- package/src/ngb/security/permissions.ts +65 -0
- package/src/ngb/security/types.ts +157 -0
- package/src/ngb/security/useAccessStore.ts +69 -0
- package/src/ngb/site/NgbDashboardAsOfToolbar.vue +100 -0
- package/src/ngb/site/NgbDashboardStatusBanner.vue +91 -0
- package/src/ngb/site/NgbLogo.vue +44 -0
- package/src/ngb/site/NgbPageHeader.vue +49 -0
- package/src/ngb/site/NgbSiteShell.vue +286 -0
- package/src/ngb/site/NgbSiteSidebar.vue +270 -0
- package/src/ngb/site/NgbTopBar.vue +425 -0
- package/src/ngb/site/NgbTrendChart.vue +224 -0
- package/src/ngb/site/dashboardData.ts +324 -0
- package/src/ngb/site/mainMenuStore.ts +49 -0
- package/src/ngb/site/types.ts +31 -0
- package/src/ngb/site/useDashboardPageState.ts +98 -0
- package/src/ngb/site/useTheme.ts +95 -0
- package/src/ngb/utils/clone.ts +43 -0
- package/src/ngb/utils/dateValues.ts +107 -0
- package/src/ngb/utils/errorMessage.ts +14 -0
- package/src/ngb/utils/guid.ts +21 -0
- package/src/ngb/utils/stableValue.ts +21 -0
- package/src/ngb/utils/storage.ts +135 -0
- package/src/styles/tailwind.css +165 -0
- package/src/vite/publicAssets.ts +104 -0
- package/vite-public-assets.js +68 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="grid grid-cols-1 items-start gap-3" :class="dense ? 'sm:grid-cols-[180px_minmax(0,1fr)]' : 'sm:grid-cols-[220px_minmax(0,1fr)]'">
|
|
3
|
+
<div class="pt-0 sm:pt-2">
|
|
4
|
+
<div class="text-sm font-medium text-ngb-text">{{ label }}</div>
|
|
5
|
+
<div v-if="hint" class="text-xs text-ngb-muted mt-1">{{ hint }}</div>
|
|
6
|
+
</div>
|
|
7
|
+
<div class="min-w-0">
|
|
8
|
+
<slot />
|
|
9
|
+
<div v-if="error" class="text-xs text-ngb-danger mt-1">{{ error }}</div>
|
|
10
|
+
</div>
|
|
11
|
+
</div>
|
|
12
|
+
</template>
|
|
13
|
+
|
|
14
|
+
<script setup lang="ts">
|
|
15
|
+
defineProps<{
|
|
16
|
+
label: string;
|
|
17
|
+
hint?: string;
|
|
18
|
+
error?: string;
|
|
19
|
+
dense?: boolean;
|
|
20
|
+
}>();
|
|
21
|
+
</script>
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<section class="ngb-card p-4">
|
|
3
|
+
<div v-if="title" class="text-sm font-semibold text-ngb-text">{{ title }}</div>
|
|
4
|
+
<div v-if="description" class="text-xs text-ngb-muted mt-1">{{ description }}</div>
|
|
5
|
+
<div class="mt-4 space-y-3">
|
|
6
|
+
<slot />
|
|
7
|
+
</div>
|
|
8
|
+
</section>
|
|
9
|
+
</template>
|
|
10
|
+
|
|
11
|
+
<script setup lang="ts">
|
|
12
|
+
defineProps<{
|
|
13
|
+
title?: string;
|
|
14
|
+
description?: string;
|
|
15
|
+
}>();
|
|
16
|
+
</script>
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div
|
|
3
|
+
v-if="messages?.length"
|
|
4
|
+
role="alert"
|
|
5
|
+
aria-live="assertive"
|
|
6
|
+
aria-atomic="true"
|
|
7
|
+
class="rounded-[var(--ngb-radius)] border border-ngb-border bg-[#FFFBEB] dark:bg-[rgba(251,191,36,.10)] dark:border-[rgba(251,191,36,.25)] px-4 py-3"
|
|
8
|
+
>
|
|
9
|
+
<div class="text-sm font-semibold text-ngb-text">Please fix the following</div>
|
|
10
|
+
<ul class="mt-2 space-y-1 text-sm text-ngb-muted list-disc pl-5">
|
|
11
|
+
<li v-for="(m, idx) in messages" :key="idx">{{ m }}</li>
|
|
12
|
+
</ul>
|
|
13
|
+
</div>
|
|
14
|
+
</template>
|
|
15
|
+
|
|
16
|
+
<script setup lang="ts">
|
|
17
|
+
defineProps<{ messages: string[] }>();
|
|
18
|
+
</script>
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<aside class="w-full h-full bg-white border-r border-ngb-border">
|
|
3
|
+
<div class="p-3 border-b border-ngb-border">
|
|
4
|
+
<div class="text-xs font-semibold text-ngb-muted mb-2">Navigation</div>
|
|
5
|
+
<input
|
|
6
|
+
v-model="q"
|
|
7
|
+
class="h-9 w-full rounded-[var(--ngb-radius)] border border-ngb-border bg-white px-3 text-sm text-ngb-text placeholder:text-ngb-muted/70 ngb-focus"
|
|
8
|
+
placeholder="Search…"
|
|
9
|
+
aria-label="Search navigation"
|
|
10
|
+
/>
|
|
11
|
+
</div>
|
|
12
|
+
|
|
13
|
+
<div class="p-2 overflow-auto h-[calc(100%-61px)]" role="tree" aria-label="Navigation">
|
|
14
|
+
<NgbTreeNode
|
|
15
|
+
v-for="n in visibleNodes"
|
|
16
|
+
:key="n.id"
|
|
17
|
+
:node="n"
|
|
18
|
+
:level="0"
|
|
19
|
+
:expanded="expanded"
|
|
20
|
+
:selectedId="modelValue ?? null"
|
|
21
|
+
@toggle="toggle"
|
|
22
|
+
@select="select"
|
|
23
|
+
/>
|
|
24
|
+
</div>
|
|
25
|
+
</aside>
|
|
26
|
+
</template>
|
|
27
|
+
|
|
28
|
+
<script setup lang="ts">
|
|
29
|
+
import { computed, ref } from 'vue';
|
|
30
|
+
import NgbTreeNode, { type NavNode } from './NgbTreeNode.vue';
|
|
31
|
+
|
|
32
|
+
const props = defineProps<{
|
|
33
|
+
nodes: NavNode[];
|
|
34
|
+
modelValue?: string | null;
|
|
35
|
+
}>();
|
|
36
|
+
|
|
37
|
+
const emit = defineEmits<{
|
|
38
|
+
(e: 'update:modelValue', v: string | null): void;
|
|
39
|
+
(e: 'select', node: NavNode): void;
|
|
40
|
+
}>();
|
|
41
|
+
|
|
42
|
+
const q = ref('');
|
|
43
|
+
const expanded = ref<Set<string>>(new Set(['documents', 'catalogs', 'reports']));
|
|
44
|
+
|
|
45
|
+
function toggle(id: string) {
|
|
46
|
+
const s = new Set(expanded.value);
|
|
47
|
+
if (s.has(id)) s.delete(id);
|
|
48
|
+
else s.add(id);
|
|
49
|
+
expanded.value = s;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function select(node: NavNode) {
|
|
53
|
+
emit('update:modelValue', node.id);
|
|
54
|
+
emit('select', node);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function matchNode(node: NavNode, query: string): boolean {
|
|
58
|
+
const hit = node.label.toLowerCase().includes(query);
|
|
59
|
+
const childHit = (node.children ?? []).some(c => matchNode(c, query));
|
|
60
|
+
return hit || childHit;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const visibleNodes = computed(() => {
|
|
64
|
+
const query = q.value.trim().toLowerCase();
|
|
65
|
+
if (!query) return props.nodes;
|
|
66
|
+
return props.nodes.filter(n => matchNode(n, query));
|
|
67
|
+
});
|
|
68
|
+
</script>
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="select-none">
|
|
3
|
+
<div
|
|
4
|
+
role="treeitem"
|
|
5
|
+
tabindex="0"
|
|
6
|
+
:aria-level="level + 1"
|
|
7
|
+
:aria-selected="selectedId === node.id"
|
|
8
|
+
:aria-expanded="hasChildren ? isExpanded : undefined"
|
|
9
|
+
class="h-9 rounded-[var(--ngb-radius)] flex items-center gap-2 px-2 text-sm cursor-pointer"
|
|
10
|
+
:class="selectedId === node.id ? 'bg-[#F1F5F9] border border-[#E2E8F0]' : 'hover:bg-[#F8FAFC]'"
|
|
11
|
+
:style="{ paddingLeft: `${8 + level * 14}px` }"
|
|
12
|
+
@click="$emit('select', node)"
|
|
13
|
+
@keydown="onRowKeyDown"
|
|
14
|
+
>
|
|
15
|
+
<button
|
|
16
|
+
v-if="hasChildren"
|
|
17
|
+
type="button"
|
|
18
|
+
class="w-6 h-6 inline-flex items-center justify-center rounded hover:bg-[#F1F5F9] text-ngb-muted"
|
|
19
|
+
@click.stop="$emit('toggle', node.id)"
|
|
20
|
+
aria-label="Toggle"
|
|
21
|
+
>
|
|
22
|
+
{{ isExpanded ? '▾' : '▸' }}
|
|
23
|
+
</button>
|
|
24
|
+
<span v-else class="w-6 h-6 inline-flex items-center justify-center text-ngb-muted">•</span>
|
|
25
|
+
|
|
26
|
+
<span class="truncate flex-1 text-ngb-text">{{ node.label }}</span>
|
|
27
|
+
|
|
28
|
+
<span
|
|
29
|
+
v-if="node.badge"
|
|
30
|
+
class="text-[11px] font-semibold px-2 py-0.5 rounded-full border border-ngb-border text-ngb-muted"
|
|
31
|
+
>
|
|
32
|
+
{{ node.badge }}
|
|
33
|
+
</span>
|
|
34
|
+
</div>
|
|
35
|
+
|
|
36
|
+
<div v-if="hasChildren && isExpanded" class="mt-1" role="group">
|
|
37
|
+
<NgbTreeNode
|
|
38
|
+
v-for="c in node.children"
|
|
39
|
+
:key="c.id"
|
|
40
|
+
:node="c"
|
|
41
|
+
:level="level + 1"
|
|
42
|
+
:expanded="expanded"
|
|
43
|
+
:selectedId="selectedId"
|
|
44
|
+
@toggle="$emit('toggle', $event)"
|
|
45
|
+
@select="$emit('select', $event)"
|
|
46
|
+
/>
|
|
47
|
+
</div>
|
|
48
|
+
</div>
|
|
49
|
+
</template>
|
|
50
|
+
|
|
51
|
+
<script setup lang="ts">
|
|
52
|
+
import { computed } from 'vue';
|
|
53
|
+
|
|
54
|
+
export type NavNode = {
|
|
55
|
+
id: string;
|
|
56
|
+
label: string;
|
|
57
|
+
children?: NavNode[];
|
|
58
|
+
badge?: string;
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
const props = defineProps<{
|
|
62
|
+
node: NavNode;
|
|
63
|
+
level: number;
|
|
64
|
+
expanded: Set<string>;
|
|
65
|
+
selectedId: string | null;
|
|
66
|
+
}>();
|
|
67
|
+
|
|
68
|
+
const emit = defineEmits<{
|
|
69
|
+
(e: 'toggle', id: string): void;
|
|
70
|
+
(e: 'select', node: NavNode): void;
|
|
71
|
+
}>();
|
|
72
|
+
|
|
73
|
+
const hasChildren = computed(() => !!(props.node.children && props.node.children.length));
|
|
74
|
+
const isExpanded = computed(() => props.expanded.has(props.node.id));
|
|
75
|
+
|
|
76
|
+
function onRowKeyDown(event: KeyboardEvent) {
|
|
77
|
+
if (event.key === 'Enter' || event.key === ' ' || event.key === 'Spacebar') {
|
|
78
|
+
event.preventDefault();
|
|
79
|
+
emit('select', props.node);
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (event.key === 'ArrowRight' && hasChildren.value && !isExpanded.value) {
|
|
84
|
+
event.preventDefault();
|
|
85
|
+
emit('toggle', props.node.id);
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
if (event.key === 'ArrowLeft' && hasChildren.value && isExpanded.value) {
|
|
90
|
+
event.preventDefault();
|
|
91
|
+
emit('toggle', props.node.id);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
</script>
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="relative" ref="root">
|
|
3
|
+
<NgbButton variant="ghost" @click="open = !open">Columns ▾</NgbButton>
|
|
4
|
+
<div v-if="open" class="absolute right-0 mt-2 w-72 rounded-[var(--ngb-radius)] border border-ngb-border bg-ngb-card shadow-card p-2 z-10">
|
|
5
|
+
<div class="px-2 py-2 text-sm font-semibold text-ngb-text">Columns</div>
|
|
6
|
+
<div class="max-h-[320px] overflow-auto px-2 pb-2">
|
|
7
|
+
<label v-for="c in columns" :key="c.id" class="flex items-center gap-2 py-1.5 text-sm">
|
|
8
|
+
<input type="checkbox" :checked="visibleSet.has(c.id)" @change="toggle(c.id)" />
|
|
9
|
+
<span class="flex-1">{{ c.label }}</span>
|
|
10
|
+
</label>
|
|
11
|
+
</div>
|
|
12
|
+
<div class="px-2 pt-2 border-t border-ngb-border flex items-center justify-between gap-2">
|
|
13
|
+
<button class="text-sm text-ngb-muted hover:text-ngb-text" @click="reset">Reset</button>
|
|
14
|
+
<div class="flex items-center gap-2">
|
|
15
|
+
<NgbButton variant="secondary" @click="open = false">Close</NgbButton>
|
|
16
|
+
</div>
|
|
17
|
+
</div>
|
|
18
|
+
</div>
|
|
19
|
+
</div>
|
|
20
|
+
</template>
|
|
21
|
+
|
|
22
|
+
<script setup lang="ts">
|
|
23
|
+
import { computed, onBeforeUnmount, onMounted, ref } from 'vue';
|
|
24
|
+
import NgbButton from '../../primitives/NgbButton.vue';
|
|
25
|
+
import { loadJson, saveJson } from '../../utils/storage';
|
|
26
|
+
|
|
27
|
+
export type ColumnDef = { id: string; label: string; };
|
|
28
|
+
type PersistedColumnChooserState = string[] | {
|
|
29
|
+
visible?: string[];
|
|
30
|
+
order?: string[];
|
|
31
|
+
widths?: Record<string, number>;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
const props = defineProps<{
|
|
35
|
+
columns: ColumnDef[];
|
|
36
|
+
modelValue: string[];
|
|
37
|
+
storageKey?: string;
|
|
38
|
+
}>();
|
|
39
|
+
|
|
40
|
+
const emit = defineEmits<{
|
|
41
|
+
(e: 'update:modelValue', value: string[]): void;
|
|
42
|
+
}>();
|
|
43
|
+
|
|
44
|
+
const open = ref(false);
|
|
45
|
+
const root = ref<HTMLElement | null>(null);
|
|
46
|
+
|
|
47
|
+
const visibleSet = computed(() => new Set(props.modelValue));
|
|
48
|
+
|
|
49
|
+
function readStoredVisibleColumns(storageKey: string): string[] | null {
|
|
50
|
+
const stored = loadJson<PersistedColumnChooserState | null>(storageKey, null);
|
|
51
|
+
if (!stored) return null;
|
|
52
|
+
if (Array.isArray(stored)) return [...stored];
|
|
53
|
+
return Array.isArray(stored.visible) ? [...stored.visible] : null;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function saveStoredVisibleColumns(storageKey: string, visible: string[]) {
|
|
57
|
+
const stored = loadJson<PersistedColumnChooserState | null>(storageKey, null);
|
|
58
|
+
if (stored && !Array.isArray(stored)) {
|
|
59
|
+
saveJson(storageKey, {
|
|
60
|
+
...stored,
|
|
61
|
+
visible,
|
|
62
|
+
});
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
saveJson(storageKey, {
|
|
67
|
+
visible,
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function toggle(id: string) {
|
|
72
|
+
const next = new Set(props.modelValue);
|
|
73
|
+
if (next.has(id)) next.delete(id);
|
|
74
|
+
else next.add(id);
|
|
75
|
+
const list = props.columns.map(c => c.id).filter(cid => next.has(cid));
|
|
76
|
+
emit('update:modelValue', list);
|
|
77
|
+
if (props.storageKey) saveStoredVisibleColumns(props.storageKey, list);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function reset() {
|
|
81
|
+
const list = props.columns.map(c => c.id);
|
|
82
|
+
emit('update:modelValue', list);
|
|
83
|
+
if (props.storageKey) saveStoredVisibleColumns(props.storageKey, list);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function onDocClick(e: MouseEvent) {
|
|
87
|
+
if (!open.value) return;
|
|
88
|
+
const t = e.target as Node;
|
|
89
|
+
if (!root.value) return;
|
|
90
|
+
if (!root.value.contains(t)) open.value = false;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
onMounted(() => {
|
|
94
|
+
document.addEventListener('mousedown', onDocClick);
|
|
95
|
+
if (props.storageKey) {
|
|
96
|
+
const stored = readStoredVisibleColumns(props.storageKey);
|
|
97
|
+
if (stored) emit('update:modelValue', stored);
|
|
98
|
+
}
|
|
99
|
+
});
|
|
100
|
+
onBeforeUnmount(() => document.removeEventListener('mousedown', onDocClick));
|
|
101
|
+
</script>
|