@olenbetong/appframe-ds 0.7.0 → 0.9.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/CHANGELOG.md +175 -0
- package/package.json +52 -8
- package/scripts/copyAssets.mjs +13 -0
- package/src/AfLookup/LookupCombobox.css +1 -1
- package/src/AfLookup/LookupCombobox.tsx +3 -1
- package/src/autocomplete/Autocomplete.css +63 -42
- package/src/autocomplete/Autocomplete.tsx +2 -0
- package/src/autocomplete/AutocompleteFrame.tsx +21 -2
- package/src/autocomplete/Combobox.tsx +15 -2
- package/src/autocomplete/types.ts +4 -0
- package/src/container/Container.tsx +49 -0
- package/src/container/index.ts +1 -0
- package/src/filter/ChipListInput.tsx +79 -0
- package/src/filter/FieldFilterPanel.css +127 -0
- package/src/filter/FieldFilterPanel.tsx +309 -0
- package/src/filter/FilterBuilder.css +152 -0
- package/src/filter/FilterBuilder.test.tsx +153 -0
- package/src/filter/FilterBuilder.tsx +435 -0
- package/src/filter/FilterEditor.css +231 -0
- package/src/filter/FilterEditor.test.tsx +259 -0
- package/src/filter/FilterEditor.tsx +74 -0
- package/src/filter/FilterGroupEditor.tsx +160 -0
- package/src/filter/FilterNameDialog.css +22 -0
- package/src/filter/FilterNameDialog.tsx +81 -0
- package/src/filter/FilterRow.tsx +117 -0
- package/src/filter/FilterShareDialog.css +28 -0
- package/src/filter/FilterShareDialog.tsx +201 -0
- package/src/filter/FilterStringField.tsx +77 -0
- package/src/filter/FilterValueEditor.tsx +220 -0
- package/src/filter/SavedFilterTree.css +107 -0
- package/src/filter/SavedFilterTree.test.tsx +94 -0
- package/src/filter/SavedFilterTree.tsx +222 -0
- package/src/filter/fieldFilter.test.ts +158 -0
- package/src/filter/fieldFilter.ts +139 -0
- package/src/filter/index.ts +27 -0
- package/src/filter/types.ts +38 -0
- package/src/filter/useDistinctValues.test.ts +102 -0
- package/src/filter/useDistinctValues.ts +230 -0
- package/src/global.d.ts +11 -0
- package/src/grid/AfGridColumnsPanel.tsx +118 -0
- package/src/grid/AfGridContext.tsx +51 -0
- package/src/grid/AfGridError.tsx +46 -0
- package/src/grid/AfHeaderFilterCell.tsx +151 -0
- package/src/grid/AfHeaderFilterPanel.tsx +111 -0
- package/src/grid/Toolbar.tsx +233 -0
- package/src/grid/editing.ts +90 -0
- package/src/grid/filter.ts +78 -0
- package/src/grid/formatters.ts +48 -0
- package/src/grid/index.css +187 -0
- package/src/grid/index.tsx +376 -0
- package/src/grid/license.ts +48 -0
- package/src/grid/localization.ts +369 -0
- package/src/grid/slots/index.tsx +275 -0
- package/src/grid/slots/slots.css +53 -0
- package/src/grid/theme.tsx +185 -0
- package/src/grid/useAfColumns.tsx +313 -0
- package/src/grid/useAfCurrentIndex.ts +55 -0
- package/src/grid/useAfData.ts +20 -0
- package/src/grid/useAfFilter.ts +160 -0
- package/src/grid/useAfFilterFields.ts +55 -0
- package/src/grid/useAfGridApi.ts +66 -0
- package/src/grid/useAfKeyBindings.ts +36 -0
- package/src/grid/useAfPagination.ts +53 -0
- package/src/grid/useAfPersistedState.ts +222 -0
- package/src/grid/useAfRowEditModel.ts +137 -0
- package/src/grid/useAfSortModel.ts +72 -0
- package/src/index.ts +2 -0
- package/src/input/InputAdornments.tsx +6 -1
- package/src/paper/Paper.tsx +2 -0
- package/src/report-downloader/ReportDownloader.css +67 -0
- package/src/report-downloader/components/StatusItem.tsx +86 -0
- package/src/report-downloader/components/StatusList.tsx +28 -0
- package/src/report-downloader/index.ts +41 -0
- package/src/report-downloader/status.tsx +92 -0
- package/src/test/setup.ts +8 -0
- package/vitest.config.ts +10 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,180 @@
|
|
|
1
1
|
# @olenbetong/appframe-ds
|
|
2
2
|
|
|
3
|
+
## 0.9.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- cfc94c7: Rework the `AfGrid` header filter, and stop the toolbar refresh button from shifting while loading.
|
|
8
|
+
|
|
9
|
+
The header filters and the toolbar's filter builder now edit **one** filter tree in a single named
|
|
10
|
+
filter slot keyed on the grid `id`, instead of writing two slots that could not see each other. MUI's
|
|
11
|
+
`GridFilterModel` is no longer used as a transport — it cannot represent nested groups or the full
|
|
12
|
+
Appframe operator set. `useAfFilterModel` is replaced by `useAfFilter`, which exposes the tree plus
|
|
13
|
+
per-field helpers.
|
|
14
|
+
|
|
15
|
+
Each header filter cell is now an unlabelled input with a funnel button:
|
|
16
|
+
- one criterion taking a single value is edited straight from the input, debounced,
|
|
17
|
+
- anything else shows a read-only summary such as `In list (3)`, with a clear button,
|
|
18
|
+
- the funnel opens a per-column panel with a row per criterion, a `+ Criteria` button, and — for
|
|
19
|
+
string, number and uniqueidentifier columns — a searchable list of the column's distinct values
|
|
20
|
+
with row counts, checked to build an `in list` criterion.
|
|
21
|
+
|
|
22
|
+
The distinct list respects every other active filter but ignores the column's own criteria, so
|
|
23
|
+
unchecking a value does not make the remaining values disappear. Counts are best effort: views that
|
|
24
|
+
cannot be grouped fall back to a plain distinct list. Panel edits are staged and only applied on
|
|
25
|
+
`Apply`, so a half-built criterion never reaches the server.
|
|
26
|
+
|
|
27
|
+
`GridToolbarRefreshButton` now hides its icon while loading so the spinner replaces it instead of
|
|
28
|
+
being added next to it, and the columns panel has spacing between its search field and the column
|
|
29
|
+
list. `InputAdornments` accepts a `ref`, which is forwarded to the input element.
|
|
30
|
+
|
|
31
|
+
The panel body is exported on its own as **`FieldFilterPanel`**, a controlled component with no
|
|
32
|
+
dependency on the grid, so a single field can be filtered anywhere — a report parameter, a sidebar
|
|
33
|
+
facet, a mobile filter sheet. Its distinct value query is exported too, as `useDistinctValues`.
|
|
34
|
+
|
|
35
|
+
The header filter input no longer drives MUI's header filter edit mode from `onFocus`/`onBlur`. The
|
|
36
|
+
header filter cell refocuses itself whenever that state changes, so any mousedown leaving the input
|
|
37
|
+
pulled focus straight back in — inputs in the filter panel could not be focused, and starting a
|
|
38
|
+
column drag from a header cancelled the drag. Nothing is lost: MUI focuses the cell's focusable
|
|
39
|
+
element anyway, so typing and keyboard navigation still work.
|
|
40
|
+
|
|
41
|
+
- cfc94c7: Add `@olenbetong/appframe-ds/grid`, a Designsystemet flavoured `AfGrid`.
|
|
42
|
+
|
|
43
|
+
The grid keeps MUI X Data Grid Pro as its engine — Designsystemet has no data grid — but replaces
|
|
44
|
+
the chrome:
|
|
45
|
+
- the base slots (checkbox, button, icon button, text field, select, switch, tooltip, chip, badge,
|
|
46
|
+
progress, skeleton, divider) render Designsystemet components,
|
|
47
|
+
- the toolbar and the column panel are rebuilt with Designsystemet components and Aksel icons,
|
|
48
|
+
- the filter panel is replaced by `FilterBuilder` in dialog mode,
|
|
49
|
+
- the MUI theme is derived from the `--ds-color-*` tokens and follows `data-color-scheme`, so the
|
|
50
|
+
grid works in light and dark mode without an app-level `ThemeProvider`,
|
|
51
|
+
- the MUI X license is registered by the grid itself, from a build-time injected
|
|
52
|
+
`__MUI_X_LICENSE_KEY__` (supplied by the `appframe()` Vite plugin) or
|
|
53
|
+
`import.meta.env.VITE_MUI_X_LICENSE_KEY`.
|
|
54
|
+
|
|
55
|
+
The API matches the `@olenbetong/appframe-mui` `AfGrid`, so migrating is an import path change plus
|
|
56
|
+
an optional `filterViewName` for saved filters. Layout persistence uses the same store, so users
|
|
57
|
+
keep their saved column layouts.
|
|
58
|
+
|
|
59
|
+
The filter builder (`FilterBuilder`, `FilterEditor`, `SavedFilterTree`, …) moved here from
|
|
60
|
+
`@olenbetong/synergi-react` and is exported from the package root.
|
|
61
|
+
|
|
62
|
+
`ReportDownloader` (`openDataObjectReportWithProgress`) is available from
|
|
63
|
+
`@olenbetong/appframe-ds/report-downloader`, built with Designsystemet components.
|
|
64
|
+
|
|
65
|
+
### Patch Changes
|
|
66
|
+
|
|
67
|
+
- Updated dependencies [62a0ad5]
|
|
68
|
+
- Updated dependencies [5155cdb]
|
|
69
|
+
- @olenbetong/appframe-data@1.6.1
|
|
70
|
+
- @olenbetong/appframe-core@2.13.0
|
|
71
|
+
- @olenbetong/appframe-react@2.1.1
|
|
72
|
+
|
|
73
|
+
## 0.8.0
|
|
74
|
+
|
|
75
|
+
### Minor Changes
|
|
76
|
+
|
|
77
|
+
- b50190d: Add a `Container` component: a centered, max-width content wrapper equivalent to MUI `Container`. It is a thin alias over `Page.Block` with gutters enabled by default and a MUI-familiar `maxWidth` prop (`xs` | `sm` | `md` | `lg` | `xl` | `false`).
|
|
78
|
+
- 27ef468: Add `FilterEditor`, the criteria tree of the web filter builder.
|
|
79
|
+
|
|
80
|
+
`FilterEditor` renders a nested criteria tree, matching the advanced filter
|
|
81
|
+
editor in the Windows client: groups can be nested to any depth and each group
|
|
82
|
+
chooses whether its items are combined with `and` or `or`. An empty filter is a
|
|
83
|
+
single `and` group. Every group ends with
|
|
84
|
+
a blank row, so a condition is added just by picking a field. Value editors are
|
|
85
|
+
chosen from the field type and the operator's arity, and cover text, number and
|
|
86
|
+
date inputs, ranges, value lists and relative dates such as "Today" or "Current
|
|
87
|
+
user". Set `showFilterString` to show the equivalent filter string, which can
|
|
88
|
+
also be edited and parsed back into the tree. A group's conditions are indented
|
|
89
|
+
and boxed below its header so it is clear which group they belong to. On narrow
|
|
90
|
+
viewports, where the parts of a condition wrap onto several lines, conditions are
|
|
91
|
+
separated by extra spacing and a rule, and the group buttons are reduced to
|
|
92
|
+
icons so they no longer overflow.
|
|
93
|
+
|
|
94
|
+
`@olenbetong/appframe-core` gained path-based filter tree helpers —
|
|
95
|
+
`getNodeAtPath`, `updateNodeAtPath`, `insertNodeAtPath`, `removeNodeAtPath`,
|
|
96
|
+
`setGroupMode` and `addGroupAtPath` — replacing the group/item index helpers
|
|
97
|
+
`addExpression`, `updateExpression`, `removeExpression`, `addGroup` and
|
|
98
|
+
`removeGroup`, which only worked on the flat standard layout. `pruneFilter` now
|
|
99
|
+
prunes nested groups, `toFilterTree` normalizes a filter without flattening it,
|
|
100
|
+
and `isStandardLayout` reports whether a filter fits the simplified layout the
|
|
101
|
+
Windows flow editor can render.
|
|
102
|
+
|
|
103
|
+
`Combobox` in `@olenbetong/appframe-ds` gained two related fixes: it now accepts
|
|
104
|
+
`aria-label` for use without a visible label, and the input shows the selected
|
|
105
|
+
option's label on mount and whenever the selection is changed from the outside.
|
|
106
|
+
`Combobox` also no longer renders text one step smaller than a `Textfield` or
|
|
107
|
+
`Select` of the same `data-size`. Designsystemet inputs inherit their font size
|
|
108
|
+
when `data-size` is set, and `data-size` rescales `--ds-font-size-*`, so
|
|
109
|
+
referencing the type scale at all made the text smaller — `Combobox` now
|
|
110
|
+
inherits its font size the same way `Textfield` does, and has a minimum height
|
|
111
|
+
matching `--dsc-input-size` so it lines up exactly with an input beside it.
|
|
112
|
+
|
|
113
|
+
`Combobox` options were also unreadable in dark mode: the hovered and selected
|
|
114
|
+
states referenced `--ds-color-accent-*`, a colour set that does not exist in
|
|
115
|
+
these themes, so every option fell back to a hard-coded light background while
|
|
116
|
+
the text stayed light. The option states now use the contextual `--ds-color-*`
|
|
117
|
+
aliases, and all hard-coded colour fallbacks — which only ever encoded the light
|
|
118
|
+
theme — have been removed from `Combobox` and `AfLookup`.
|
|
119
|
+
|
|
120
|
+
### Patch Changes
|
|
121
|
+
|
|
122
|
+
- b50190d: `Paper` now accepts a `ref`, which is forwarded to the rendered element.
|
|
123
|
+
- 3ff7c84: Depend on the workspace versions of `@olenbetong/appframe-core`, `@olenbetong/appframe-data` and `@olenbetong/appframe-react` instead of the published versions from the catalog, so the package is built and tested against the current source like the other workspace packages.
|
|
124
|
+
- a0df74a: Add the report metadata model, `useReport` and `ReportParameters`.
|
|
125
|
+
|
|
126
|
+
`@olenbetong/appframe-core` gained a pure report model: `Report`,
|
|
127
|
+
`ReportParameter`, `ReportRecordSource` and their raw record counterparts, plus
|
|
128
|
+
`toReport`, `getReportFormats`, `parseRecordSource` and the parameter helpers
|
|
129
|
+
`toReportParameters`, `coerceParameterValue`, `getDefaultParameterValues`,
|
|
130
|
+
`serializeParameters`, `parseParameters` and `restoreParameterValues`. The
|
|
131
|
+
parameters XML is the same `<Parameters Version="1">` document the Windows
|
|
132
|
+
launcher writes, so a filter saved in either client restores in the other. A
|
|
133
|
+
parameter's editor is derived the way the Windows launcher derives it: a lookup
|
|
134
|
+
wins over everything, a fixed item list comes next, and otherwise the data type
|
|
135
|
+
decides. `FormatCSV` enables XLSX and XLS as well, matching the single
|
|
136
|
+
DevExpress export the flag stands for.
|
|
137
|
+
|
|
138
|
+
`@olenbetong/appframe-react` gained `useReport`, which loads a report from
|
|
139
|
+
`sviw_WinClient_MyReports` — so a report that does not come back is one the user
|
|
140
|
+
may not run — merges the `HasTitle`/`HasHeader` bands from
|
|
141
|
+
`sviw_WinClient_ReportsWeb` and the parameters from
|
|
142
|
+
`sviw_WinClient_ReportsParameters`. It returns the filter the report is designed
|
|
143
|
+
with, overridable through an `initialFilter` option the way opening a report
|
|
144
|
+
programmatically overrides it in the Windows client, and the literal parameter
|
|
145
|
+
defaults. Defaults naming a stored procedure are left unset, to be resolved
|
|
146
|
+
server-side. `useParameterOptions` loads the options for a lookup parameter from
|
|
147
|
+
its `afRecordSource`.
|
|
148
|
+
|
|
149
|
+
`@olenbetong/synergi-react` gained `ReportParameters`, the parameter form of the
|
|
150
|
+
report launcher. It renders nothing when a report has no parameters, so the
|
|
151
|
+
launcher can include it unconditionally, and gives each parameter the editor its
|
|
152
|
+
metadata asks for: a lookup combobox, a select, a checkbox, or a text, number or
|
|
153
|
+
date input. Values are coerced to the parameter's type as they are entered.
|
|
154
|
+
|
|
155
|
+
`Combobox` in `@olenbetong/appframe-ds` had no spacing between its label and its
|
|
156
|
+
control: the root is not a `.ds-field`, which is what normally spaces a label,
|
|
157
|
+
description and input apart, so it now does that spacing itself and lines up
|
|
158
|
+
with a `Textfield` or `Select` beside it.
|
|
159
|
+
|
|
160
|
+
- 1bb10de: `Autocomplete` and `Combobox` now open their option list inside the dialog they
|
|
161
|
+
are used in. The list is portalled, and a modal dialog is painted in the top
|
|
162
|
+
layer, so a list portalled to the body ended up behind the dialog backdrop and
|
|
163
|
+
appeared not to open at all.
|
|
164
|
+
- Updated dependencies [448c378]
|
|
165
|
+
- Updated dependencies [54e5344]
|
|
166
|
+
- Updated dependencies [448c378]
|
|
167
|
+
- Updated dependencies [a0df74a]
|
|
168
|
+
- Updated dependencies [7a54755]
|
|
169
|
+
- Updated dependencies [3954bc9]
|
|
170
|
+
- Updated dependencies [0a23d5b]
|
|
171
|
+
- Updated dependencies [3ff7c84]
|
|
172
|
+
- Updated dependencies [ace635e]
|
|
173
|
+
- Updated dependencies [27ef468]
|
|
174
|
+
- @olenbetong/appframe-react@2.1.0
|
|
175
|
+
- @olenbetong/appframe-core@2.12.0
|
|
176
|
+
- @olenbetong/appframe-data@1.6.0
|
|
177
|
+
|
|
3
178
|
## 0.7.0
|
|
4
179
|
|
|
5
180
|
### Minor Changes
|
package/package.json
CHANGED
|
@@ -1,12 +1,21 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@olenbetong/appframe-ds",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.0",
|
|
4
4
|
"description": "Components that bind Designsystemet components to Appframe data objects",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"types": "./es/index.d.ts",
|
|
7
7
|
"exports": {
|
|
8
8
|
".": {
|
|
9
|
+
"types": "./es/index.d.ts",
|
|
9
10
|
"import": "./es/index.js"
|
|
11
|
+
},
|
|
12
|
+
"./grid": {
|
|
13
|
+
"types": "./es/grid/index.d.ts",
|
|
14
|
+
"import": "./es/grid/index.js"
|
|
15
|
+
},
|
|
16
|
+
"./report-downloader": {
|
|
17
|
+
"types": "./es/report-downloader/index.d.ts",
|
|
18
|
+
"import": "./es/report-downloader/index.js"
|
|
10
19
|
}
|
|
11
20
|
},
|
|
12
21
|
"sideEffects": [
|
|
@@ -16,29 +25,63 @@
|
|
|
16
25
|
"license": "MIT",
|
|
17
26
|
"peerDependencies": {
|
|
18
27
|
"@digdir/designsystemet-react": ">=1.18.0",
|
|
28
|
+
"@emotion/react": ">=11.14.0",
|
|
29
|
+
"@emotion/styled": ">=11.14.1",
|
|
30
|
+
"@mui/material": ">=9.0.0",
|
|
31
|
+
"@mui/x-data-grid-pro": ">=8.0.0 <9.0.0",
|
|
32
|
+
"@mui/x-license": ">=8.0.0 <9.0.0",
|
|
19
33
|
"@navikt/aksel-icons": ">=8.16.1",
|
|
20
34
|
"react": ">=19.2.8",
|
|
21
35
|
"react-dom": ">=19.2.8",
|
|
22
36
|
"react-error-boundary": ">=6.1.2",
|
|
23
37
|
"react-router": ">=8.3.0"
|
|
24
38
|
},
|
|
39
|
+
"peerDependenciesMeta": {
|
|
40
|
+
"@emotion/react": {
|
|
41
|
+
"optional": true
|
|
42
|
+
},
|
|
43
|
+
"@emotion/styled": {
|
|
44
|
+
"optional": true
|
|
45
|
+
},
|
|
46
|
+
"@mui/material": {
|
|
47
|
+
"optional": true
|
|
48
|
+
},
|
|
49
|
+
"@mui/x-data-grid-pro": {
|
|
50
|
+
"optional": true
|
|
51
|
+
},
|
|
52
|
+
"@mui/x-license": {
|
|
53
|
+
"optional": true
|
|
54
|
+
}
|
|
55
|
+
},
|
|
25
56
|
"dependencies": {
|
|
26
|
-
"@atlaskit/pragmatic-drag-and-drop": "2.0.
|
|
57
|
+
"@atlaskit/pragmatic-drag-and-drop": "2.0.2",
|
|
27
58
|
"@atlaskit/pragmatic-drag-and-drop-hitbox": "2.0.0",
|
|
28
59
|
"@base-ui/react": "1.7.0",
|
|
29
|
-
"@olenbetong/appframe-core": "2.11.10",
|
|
30
|
-
"@olenbetong/appframe-data": "1.5.0",
|
|
31
|
-
"@olenbetong/appframe-react": "2.0.0",
|
|
32
60
|
"clsx": "^2.1.1",
|
|
61
|
+
"localforage": "^1.10.0",
|
|
62
|
+
"mitt": "^3.0.1",
|
|
33
63
|
"react-virtualized-auto-sizer": "^1.0.26",
|
|
34
|
-
"react-window": "^1.8.11"
|
|
64
|
+
"react-window": "^1.8.11",
|
|
65
|
+
"@olenbetong/appframe-core": "2.13.0",
|
|
66
|
+
"@olenbetong/appframe-react": "2.1.1",
|
|
67
|
+
"@olenbetong/appframe-data": "1.6.1"
|
|
35
68
|
},
|
|
36
69
|
"devDependencies": {
|
|
37
70
|
"@digdir/designsystemet-react": "1.18.0",
|
|
71
|
+
"@emotion/react": "^11.14.0",
|
|
72
|
+
"@emotion/styled": "^11.14.1",
|
|
73
|
+
"@mui/material": "9.3.1",
|
|
74
|
+
"@mui/x-data-grid-pro": "8.29.2",
|
|
75
|
+
"@mui/x-license": "8.29.2",
|
|
38
76
|
"@navikt/aksel-icons": "8.16.1",
|
|
77
|
+
"@testing-library/dom": "^10.4.1",
|
|
78
|
+
"@testing-library/jest-dom": "^7.0.1",
|
|
79
|
+
"@testing-library/react": "16.3.2",
|
|
80
|
+
"@testing-library/user-event": "^14.6.3",
|
|
39
81
|
"@types/react": "19.2.18",
|
|
40
82
|
"@types/react-dom": "19.2.4",
|
|
41
83
|
"@types/react-window": "^1.8.8",
|
|
84
|
+
"happy-dom": "^20.11.1",
|
|
42
85
|
"react": "19.2.8",
|
|
43
86
|
"react-dom": "19.2.8",
|
|
44
87
|
"react-error-boundary": "6.1.2",
|
|
@@ -46,8 +89,9 @@
|
|
|
46
89
|
"typescript": "7.0.2"
|
|
47
90
|
},
|
|
48
91
|
"scripts": {
|
|
49
|
-
"build": "pnpm run build:tsc && pnpm run build:css",
|
|
92
|
+
"build": "pnpm run build:tsc && pnpm test && pnpm run build:css",
|
|
50
93
|
"build:tsc": "node --no-warnings ../../scripts/clean.ts ./es tsconfig.build.tsbuildinfo && tsc -p tsconfig.build.json",
|
|
51
|
-
"build:css": "node scripts/copyAssets.mjs"
|
|
94
|
+
"build:css": "node scripts/copyAssets.mjs",
|
|
95
|
+
"test": "vitest run"
|
|
52
96
|
}
|
|
53
97
|
}
|
package/scripts/copyAssets.mjs
CHANGED
|
@@ -22,6 +22,15 @@ const files = [
|
|
|
22
22
|
["src/sortable/DropIndicator.css", "es/sortable/DropIndicator.css"],
|
|
23
23
|
["src/sortable/SortableItemList.css", "es/sortable/SortableItemList.css"],
|
|
24
24
|
["src/autocomplete/Autocomplete.css", "es/autocomplete/Autocomplete.css"],
|
|
25
|
+
["src/filter/FilterBuilder.css", "es/filter/FilterBuilder.css"],
|
|
26
|
+
["src/filter/FieldFilterPanel.css", "es/filter/FieldFilterPanel.css"],
|
|
27
|
+
["src/filter/FilterEditor.css", "es/filter/FilterEditor.css"],
|
|
28
|
+
["src/filter/FilterNameDialog.css", "es/filter/FilterNameDialog.css"],
|
|
29
|
+
["src/filter/FilterShareDialog.css", "es/filter/FilterShareDialog.css"],
|
|
30
|
+
["src/filter/SavedFilterTree.css", "es/filter/SavedFilterTree.css"],
|
|
31
|
+
["src/grid/index.css", "es/grid/index.css"],
|
|
32
|
+
["src/grid/slots/slots.css", "es/grid/slots/slots.css"],
|
|
33
|
+
["src/report-downloader/ReportDownloader.css", "es/report-downloader/ReportDownloader.css"],
|
|
25
34
|
];
|
|
26
35
|
|
|
27
36
|
mkdirSync("es/layout", { recursive: true });
|
|
@@ -32,6 +41,10 @@ mkdirSync("es/paper", { recursive: true });
|
|
|
32
41
|
mkdirSync("es/progress", { recursive: true });
|
|
33
42
|
mkdirSync("es/sortable", { recursive: true });
|
|
34
43
|
mkdirSync("es/autocomplete", { recursive: true });
|
|
44
|
+
mkdirSync("es/filter", { recursive: true });
|
|
45
|
+
mkdirSync("es/grid", { recursive: true });
|
|
46
|
+
mkdirSync("es/grid/slots", { recursive: true });
|
|
47
|
+
mkdirSync("es/report-downloader", { recursive: true });
|
|
35
48
|
|
|
36
49
|
for (let [src, dest] of files) {
|
|
37
50
|
cpSync(src, dest);
|
|
@@ -39,7 +39,9 @@ export interface LookupComboboxGridProps<T extends Record<string, unknown>> exte
|
|
|
39
39
|
columns: LookupColumn<T>[];
|
|
40
40
|
}
|
|
41
41
|
|
|
42
|
-
export type LookupComboboxProps<T extends Record<string, unknown>> =
|
|
42
|
+
export type LookupComboboxProps<T extends Record<string, unknown>> =
|
|
43
|
+
| LookupComboboxListProps<T>
|
|
44
|
+
| LookupComboboxGridProps<T>;
|
|
43
45
|
|
|
44
46
|
export function LookupCombobox<T extends Record<string, unknown>>(props: LookupComboboxProps<T>) {
|
|
45
47
|
let {
|
|
@@ -1,34 +1,48 @@
|
|
|
1
1
|
.ObAutocomplete-root {
|
|
2
2
|
--obAutocomplete-padding: 0.25rem;
|
|
3
3
|
--obAutocomplete-gap: 0.25rem;
|
|
4
|
-
--obAutocomplete-fontSize: var(--ds-font-size-3, 1rem);
|
|
5
4
|
|
|
6
5
|
display: inline-grid;
|
|
7
6
|
}
|
|
8
7
|
|
|
8
|
+
/*
|
|
9
|
+
* The root is not a `.ds-field`, so it has to space its label, description,
|
|
10
|
+
* control and validation message itself. Mirrors `.ds-field > * + *`, including
|
|
11
|
+
* the description sitting tight under the label.
|
|
12
|
+
*/
|
|
13
|
+
.ObAutocomplete-root > * + * {
|
|
14
|
+
margin-block-start: var(--ds-size-2);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
.ObAutocomplete-root > [data-field="description"] {
|
|
18
|
+
margin-block-start: 0;
|
|
19
|
+
}
|
|
20
|
+
|
|
9
21
|
.ObAutocomplete-root[data-full-width="true"] {
|
|
10
22
|
display: grid;
|
|
11
23
|
inline-size: 100%;
|
|
12
24
|
}
|
|
13
25
|
|
|
26
|
+
/*
|
|
27
|
+
* Font size is deliberately never set: `ds-input` inherits its font size when a
|
|
28
|
+
* `data-size` attribute is present, so referencing `--ds-font-size-*` here — which
|
|
29
|
+
* `data-size` rescales — would render the text smaller than a `Textfield` or
|
|
30
|
+
* `Select` of the same size. Only the padding varies per size.
|
|
31
|
+
*/
|
|
14
32
|
.ObAutocomplete-root[data-size="xs"] {
|
|
15
33
|
--obAutocomplete-padding: 0.125rem;
|
|
16
|
-
--obAutocomplete-fontSize: var(--ds-font-size-1, 0.75rem);
|
|
17
34
|
}
|
|
18
35
|
|
|
19
36
|
.ObAutocomplete-root[data-size="sm"] {
|
|
20
37
|
--obAutocomplete-padding: 0.25rem;
|
|
21
|
-
--obAutocomplete-fontSize: var(--ds-font-size-2, 0.875rem);
|
|
22
38
|
}
|
|
23
39
|
|
|
24
40
|
.ObAutocomplete-root[data-size="md"] {
|
|
25
41
|
--obAutocomplete-padding: 0.25rem;
|
|
26
|
-
--obAutocomplete-fontSize: var(--ds-font-size-3, 1rem);
|
|
27
42
|
}
|
|
28
43
|
|
|
29
44
|
.ObAutocomplete-root[data-size="lg"] {
|
|
30
45
|
--obAutocomplete-padding: 0.375rem;
|
|
31
|
-
--obAutocomplete-fontSize: var(--ds-font-size-4, 1.125rem);
|
|
32
46
|
}
|
|
33
47
|
|
|
34
48
|
/*
|
|
@@ -40,34 +54,36 @@
|
|
|
40
54
|
.ObAutocomplete-control {
|
|
41
55
|
display: flex;
|
|
42
56
|
flex-direction: column;
|
|
57
|
+
justify-content: center;
|
|
43
58
|
box-sizing: border-box;
|
|
44
59
|
gap: var(--obAutocomplete-gap);
|
|
45
60
|
padding: var(--obAutocomplete-padding);
|
|
46
61
|
inline-size: 100%;
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
border
|
|
62
|
+
/* Matches `--dsc-input-size`, so the control lines up with a Textfield. */
|
|
63
|
+
min-block-size: var(--ds-size-12);
|
|
64
|
+
color: var(--ds-color-neutral-text-default);
|
|
65
|
+
background-color: var(--ds-color-neutral-surface-default);
|
|
66
|
+
border: var(--ds-border-width-default) solid var(--ds-color-neutral-border-default);
|
|
67
|
+
border-radius: var(--ds-border-radius-md);
|
|
52
68
|
}
|
|
53
69
|
|
|
54
70
|
.ObAutocomplete-control:hover:not(:focus-within) {
|
|
55
|
-
border-color: var(--ds-color-neutral-border-strong
|
|
71
|
+
border-color: var(--ds-color-neutral-border-strong);
|
|
56
72
|
}
|
|
57
73
|
|
|
58
74
|
.ObAutocomplete-control:has(.ObAutocomplete-input:focus-visible) {
|
|
59
75
|
box-shadow: var(--dsc-focus-boxShadow);
|
|
60
76
|
outline: var(--dsc-focus-outline);
|
|
61
|
-
outline-offset: var(--ds-border-width-focus
|
|
77
|
+
outline-offset: var(--ds-border-width-focus);
|
|
62
78
|
}
|
|
63
79
|
|
|
64
80
|
.ObAutocomplete-control[data-invalid="true"] {
|
|
65
|
-
border-color: var(--ds-color-danger-border-default
|
|
81
|
+
border-color: var(--ds-color-danger-border-default);
|
|
66
82
|
}
|
|
67
83
|
|
|
68
84
|
.ObAutocomplete-control[data-disabled="true"] {
|
|
69
85
|
cursor: not-allowed;
|
|
70
|
-
opacity: var(--ds-disabled
|
|
86
|
+
opacity: var(--ds-opacity-disabled);
|
|
71
87
|
}
|
|
72
88
|
|
|
73
89
|
.ObAutocomplete-chips {
|
|
@@ -88,7 +104,7 @@
|
|
|
88
104
|
.ObAutocomplete-input {
|
|
89
105
|
flex: 1;
|
|
90
106
|
min-inline-size: 0;
|
|
91
|
-
padding: var(--ds-size-1
|
|
107
|
+
padding: var(--ds-size-1);
|
|
92
108
|
font: inherit;
|
|
93
109
|
color: inherit;
|
|
94
110
|
background: transparent;
|
|
@@ -105,17 +121,16 @@
|
|
|
105
121
|
align-items: center;
|
|
106
122
|
justify-content: center;
|
|
107
123
|
flex: none;
|
|
108
|
-
padding: var(--ds-size-1
|
|
109
|
-
font-size: var(--obAutocomplete-fontSize);
|
|
124
|
+
padding: var(--ds-size-1);
|
|
110
125
|
color: inherit;
|
|
111
126
|
background: transparent;
|
|
112
127
|
border: none;
|
|
113
|
-
border-radius: var(--ds-border-radius-sm
|
|
128
|
+
border-radius: var(--ds-border-radius-sm);
|
|
114
129
|
cursor: pointer;
|
|
115
130
|
}
|
|
116
131
|
|
|
117
132
|
.ObAutocomplete-button:hover:not(:disabled) {
|
|
118
|
-
background-color: var(--ds-color-neutral-surface-hover
|
|
133
|
+
background-color: var(--ds-color-neutral-surface-hover);
|
|
119
134
|
}
|
|
120
135
|
|
|
121
136
|
.ObAutocomplete-button:disabled {
|
|
@@ -134,7 +149,7 @@
|
|
|
134
149
|
flex: none;
|
|
135
150
|
display: inline-flex;
|
|
136
151
|
align-items: center;
|
|
137
|
-
padding-inline: var(--ds-size-1
|
|
152
|
+
padding-inline: var(--ds-size-1);
|
|
138
153
|
}
|
|
139
154
|
|
|
140
155
|
.ObAutocomplete-positioner {
|
|
@@ -149,13 +164,12 @@
|
|
|
149
164
|
max-block-size: min(24rem, var(--available-height));
|
|
150
165
|
overflow-y: auto;
|
|
151
166
|
overscroll-behavior: contain;
|
|
152
|
-
padding: var(--ds-size-1
|
|
153
|
-
|
|
154
|
-
color: var(--ds-color-neutral-
|
|
155
|
-
|
|
156
|
-
border: var(--ds-border-
|
|
157
|
-
|
|
158
|
-
box-shadow: var(--ds-shadow-md, 0 2px 8px 0 rgb(0 0 0 / 0.12));
|
|
167
|
+
padding: var(--ds-size-1);
|
|
168
|
+
color: var(--ds-color-neutral-text-default);
|
|
169
|
+
background-color: var(--ds-color-neutral-surface-default);
|
|
170
|
+
border: var(--ds-border-width-default) solid var(--ds-color-neutral-border-subtle);
|
|
171
|
+
border-radius: var(--ds-border-radius-md);
|
|
172
|
+
box-shadow: var(--ds-shadow-md);
|
|
159
173
|
transform-origin: var(--transform-origin);
|
|
160
174
|
transition:
|
|
161
175
|
opacity 0.12s ease-out,
|
|
@@ -176,30 +190,37 @@
|
|
|
176
190
|
.ObAutocomplete-item {
|
|
177
191
|
display: flex;
|
|
178
192
|
align-items: center;
|
|
179
|
-
gap: var(--ds-size-2
|
|
180
|
-
padding: var(--ds-size-2
|
|
181
|
-
border-radius: var(--ds-border-radius-sm
|
|
193
|
+
gap: var(--ds-size-2);
|
|
194
|
+
padding: var(--ds-size-2);
|
|
195
|
+
border-radius: var(--ds-border-radius-sm);
|
|
182
196
|
cursor: default;
|
|
183
197
|
user-select: none;
|
|
184
|
-
scroll-margin-block: var(--ds-size-1
|
|
198
|
+
scroll-margin-block: var(--ds-size-1);
|
|
185
199
|
}
|
|
186
200
|
|
|
201
|
+
/*
|
|
202
|
+
* The `accent` colour set does not exist in every Designsystemet theme, so the
|
|
203
|
+
* option states use the contextual `--ds-color-*` aliases — the same tokens
|
|
204
|
+
* Designsystemet's own Suggestion list uses.
|
|
205
|
+
*/
|
|
187
206
|
.ObAutocomplete-item[data-highlighted] {
|
|
188
|
-
|
|
207
|
+
color: var(--ds-color-text-default);
|
|
208
|
+
background-color: var(--ds-color-surface-hover);
|
|
189
209
|
}
|
|
190
210
|
|
|
191
211
|
.ObAutocomplete-item[data-selected] {
|
|
192
|
-
font-weight: var(--ds-font-weight-medium
|
|
193
|
-
|
|
212
|
+
font-weight: var(--ds-font-weight-medium);
|
|
213
|
+
color: var(--ds-color-text-default);
|
|
214
|
+
background-color: var(--ds-color-surface-tinted);
|
|
194
215
|
}
|
|
195
216
|
|
|
196
217
|
.ObAutocomplete-item[data-selected][data-highlighted] {
|
|
197
|
-
background-color: var(--ds-color-
|
|
218
|
+
background-color: var(--ds-color-surface-active);
|
|
198
219
|
}
|
|
199
220
|
|
|
200
221
|
.ObAutocomplete-item[data-disabled] {
|
|
201
222
|
cursor: not-allowed;
|
|
202
|
-
opacity: var(--ds-disabled
|
|
223
|
+
opacity: var(--ds-opacity-disabled);
|
|
203
224
|
}
|
|
204
225
|
|
|
205
226
|
.ObAutocomplete-itemIndicator {
|
|
@@ -209,14 +230,14 @@
|
|
|
209
230
|
}
|
|
210
231
|
|
|
211
232
|
.ObAutocomplete-groupLabel {
|
|
212
|
-
padding: var(--ds-size-2
|
|
213
|
-
font-size: var(--ds-font-size-1
|
|
214
|
-
font-weight: var(--ds-font-weight-semibold
|
|
215
|
-
color: var(--ds-color-neutral-text-subtle
|
|
233
|
+
padding: var(--ds-size-2);
|
|
234
|
+
font-size: var(--ds-font-size-1);
|
|
235
|
+
font-weight: var(--ds-font-weight-semibold);
|
|
236
|
+
color: var(--ds-color-neutral-text-subtle);
|
|
216
237
|
text-transform: uppercase;
|
|
217
238
|
}
|
|
218
239
|
|
|
219
240
|
.ObAutocomplete-empty:not(:empty) {
|
|
220
|
-
padding: var(--ds-size-2
|
|
221
|
-
color: var(--ds-color-neutral-text-subtle
|
|
241
|
+
padding: var(--ds-size-2);
|
|
242
|
+
color: var(--ds-color-neutral-text-subtle);
|
|
222
243
|
}
|
|
@@ -117,6 +117,7 @@ export function Autocomplete<
|
|
|
117
117
|
disableClearable,
|
|
118
118
|
placeholder,
|
|
119
119
|
inputRef,
|
|
120
|
+
"aria-label": ariaLabel,
|
|
120
121
|
"data-size": size,
|
|
121
122
|
"data-color": color,
|
|
122
123
|
} = props;
|
|
@@ -294,6 +295,7 @@ export function Autocomplete<
|
|
|
294
295
|
loading={loading}
|
|
295
296
|
disableClearable={disableClearable}
|
|
296
297
|
inputRef={inputRef}
|
|
298
|
+
aria-label={ariaLabel}
|
|
297
299
|
chips={chips}
|
|
298
300
|
trigger={
|
|
299
301
|
<BaseAutocomplete.Trigger
|
|
@@ -3,7 +3,7 @@ import { FieldDescription, Label, Spinner, ValidationMessage } from "@digdir/des
|
|
|
3
3
|
import { getLocalizedString } from "@olenbetong/appframe-core";
|
|
4
4
|
import { XMarkIcon } from "@navikt/aksel-icons";
|
|
5
5
|
import clsx from "clsx";
|
|
6
|
-
import type
|
|
6
|
+
import { type ReactNode, type Ref, useEffect, useRef, useState } from "react";
|
|
7
7
|
|
|
8
8
|
import type { AutocompleteSize } from "./types.js";
|
|
9
9
|
|
|
@@ -26,6 +26,10 @@ export interface AutocompleteFrameProps {
|
|
|
26
26
|
loading?: boolean;
|
|
27
27
|
disableClearable?: boolean;
|
|
28
28
|
inputRef?: Ref<HTMLInputElement>;
|
|
29
|
+
/**
|
|
30
|
+
* Accessible name for the input. Use when the field has no visible label.
|
|
31
|
+
*/
|
|
32
|
+
"aria-label"?: string;
|
|
29
33
|
/**
|
|
30
34
|
* Chips for the selected values, rendered above the input.
|
|
31
35
|
*/
|
|
@@ -75,6 +79,7 @@ export function AutocompleteFrame({
|
|
|
75
79
|
loading,
|
|
76
80
|
disableClearable,
|
|
77
81
|
inputRef,
|
|
82
|
+
"aria-label": ariaLabel,
|
|
78
83
|
chips,
|
|
79
84
|
trigger,
|
|
80
85
|
emptyContent,
|
|
@@ -82,10 +87,23 @@ export function AutocompleteFrame({
|
|
|
82
87
|
onInputKeyDown,
|
|
83
88
|
onInputBlur,
|
|
84
89
|
}: AutocompleteFrameProps) {
|
|
90
|
+
let rootRef = useRef<HTMLDivElement>(null);
|
|
91
|
+
let [container, setContainer] = useState<HTMLElement | null>(null);
|
|
92
|
+
|
|
93
|
+
/*
|
|
94
|
+
* A modal dialog is painted in the top layer, above everything else in the
|
|
95
|
+
* document, so a popup portalled to the body ends up behind its backdrop and
|
|
96
|
+
* is invisible. Portal into the dialog itself when there is one.
|
|
97
|
+
*/
|
|
98
|
+
useEffect(() => {
|
|
99
|
+
setContainer(rootRef.current?.closest("dialog") ?? null);
|
|
100
|
+
}, []);
|
|
101
|
+
|
|
85
102
|
return (
|
|
86
103
|
// Designsystemet's `Field` is a custom element that expects to own the
|
|
87
104
|
// layout, so the root here is a plain grid instead.
|
|
88
105
|
<div
|
|
106
|
+
ref={rootRef}
|
|
89
107
|
className={clsx("ObAutocomplete-root", className)}
|
|
90
108
|
style={style}
|
|
91
109
|
data-size={size}
|
|
@@ -111,6 +129,7 @@ export function AutocompleteFrame({
|
|
|
111
129
|
ref={inputRef}
|
|
112
130
|
className="ObAutocomplete-input"
|
|
113
131
|
placeholder={placeholder}
|
|
132
|
+
aria-label={ariaLabel}
|
|
114
133
|
disabled={disabled}
|
|
115
134
|
readOnly={readOnly}
|
|
116
135
|
required={required}
|
|
@@ -136,7 +155,7 @@ export function AutocompleteFrame({
|
|
|
136
155
|
</BaseCombobox.InputGroup>
|
|
137
156
|
</div>
|
|
138
157
|
{error && <ValidationMessage data-size={size}>{error}</ValidationMessage>}
|
|
139
|
-
<BaseCombobox.Portal>
|
|
158
|
+
<BaseCombobox.Portal container={container ?? undefined}>
|
|
140
159
|
<BaseCombobox.Positioner className="ObAutocomplete-positioner" sideOffset={4}>
|
|
141
160
|
<BaseCombobox.Popup className={clsx("ObAutocomplete-popup", popupClassName)} data-size={size}>
|
|
142
161
|
<BaseCombobox.Empty className="ObAutocomplete-empty">{emptyContent}</BaseCombobox.Empty>
|