@olenbetong/appframe-ds 1.9.0 → 1.9.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +12 -0
- package/package.json +1 -1
- package/scripts/copyAssets.mjs +27 -27
- package/src/AfLookup/Lookup.css +7 -0
- package/src/AfLookup/Lookup.tsx +29 -2
- package/src/AfLookup/LookupEdit.tsx +2 -1
- package/src/AfLookup/LookupGrid.tsx +2 -2
- package/src/grid/GridEditLookup.tsx +3 -2
- package/src/grid/index.css +27 -5
- package/src/grid/useAfSizeMode.ts +5 -4
- package/tsconfig.build.json +9 -9
- package/tsconfig.json +8 -8
- package/vitest.config.ts +10 -10
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# @olenbetong/appframe-ds
|
|
2
2
|
|
|
3
|
+
## 1.9.2
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 2f1516a: AfGrid and the lookup grid take their row height from `--ds-size-11` instead of `--ds-size-10`, so the text no longer crowds the row (about 34px at sm, 28px at xs). AfGrid's text is one step below the size mode's body text, as MUI's compact grid was, except at xs, and inputs in the grid follow it.
|
|
8
|
+
|
|
9
|
+
## 1.9.1
|
|
10
|
+
|
|
11
|
+
### Patch Changes
|
|
12
|
+
|
|
13
|
+
- 0f57479: Lookups: the dialog lines up with the input instead of the label beside or above it, grows upwards when it is placed above the field, and follows the field's size mode also when opened with ArrowDown or F4. `GridEditLookup` takes `editable`, so a value can be typed into the cell.
|
|
14
|
+
|
|
3
15
|
## 1.9.0
|
|
4
16
|
|
|
5
17
|
### Minor Changes
|
package/package.json
CHANGED
package/scripts/copyAssets.mjs
CHANGED
|
@@ -1,27 +1,27 @@
|
|
|
1
|
-
/*
|
|
2
|
-
* tsc emits the JavaScript into es/ but not the stylesheets the components import next to
|
|
3
|
-
* themselves. Copy every .css under src/ to the same path under es/, so a new component's
|
|
4
|
-
* stylesheet is published without anyone remembering a list.
|
|
5
|
-
*/
|
|
6
|
-
import { cpSync, mkdirSync, readdirSync } from "node:fs";
|
|
7
|
-
import { dirname, join, relative } from "node:path";
|
|
8
|
-
|
|
9
|
-
function* cssFiles(dir) {
|
|
10
|
-
for (let entry of readdirSync(dir, { withFileTypes: true })) {
|
|
11
|
-
let path = join(dir, entry.name);
|
|
12
|
-
if (entry.isDirectory()) {
|
|
13
|
-
yield* cssFiles(path);
|
|
14
|
-
} else if (entry.name.endsWith(".css")) {
|
|
15
|
-
yield path;
|
|
16
|
-
}
|
|
17
|
-
}
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
let count = 0;
|
|
21
|
-
for (let src of cssFiles("src")) {
|
|
22
|
-
let dest = join("es", relative("src", src));
|
|
23
|
-
mkdirSync(dirname(dest), { recursive: true });
|
|
24
|
-
cpSync(src, dest);
|
|
25
|
-
count++;
|
|
26
|
-
}
|
|
27
|
-
console.log(`Copied ${count} stylesheets from src/ to es/`);
|
|
1
|
+
/*
|
|
2
|
+
* tsc emits the JavaScript into es/ but not the stylesheets the components import next to
|
|
3
|
+
* themselves. Copy every .css under src/ to the same path under es/, so a new component's
|
|
4
|
+
* stylesheet is published without anyone remembering a list.
|
|
5
|
+
*/
|
|
6
|
+
import { cpSync, mkdirSync, readdirSync } from "node:fs";
|
|
7
|
+
import { dirname, join, relative } from "node:path";
|
|
8
|
+
|
|
9
|
+
function* cssFiles(dir) {
|
|
10
|
+
for (let entry of readdirSync(dir, { withFileTypes: true })) {
|
|
11
|
+
let path = join(dir, entry.name);
|
|
12
|
+
if (entry.isDirectory()) {
|
|
13
|
+
yield* cssFiles(path);
|
|
14
|
+
} else if (entry.name.endsWith(".css")) {
|
|
15
|
+
yield path;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
let count = 0;
|
|
21
|
+
for (let src of cssFiles("src")) {
|
|
22
|
+
let dest = join("es", relative("src", src));
|
|
23
|
+
mkdirSync(dirname(dest), { recursive: true });
|
|
24
|
+
cpSync(src, dest);
|
|
25
|
+
count++;
|
|
26
|
+
}
|
|
27
|
+
console.log(`Copied ${count} stylesheets from src/ to es/`);
|
package/src/AfLookup/Lookup.css
CHANGED
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
|
|
4
4
|
.ds-input {
|
|
5
5
|
padding-inline-end: var(--ObLookup-buttons-width, var(--ds-size-12, 3rem));
|
|
6
|
+
/* Set by AfLookup and AfLookupEdit; the dialog lines up with the input, not its label. */
|
|
7
|
+
anchor-name: var(--ObLookup-anchor);
|
|
6
8
|
}
|
|
7
9
|
}
|
|
8
10
|
|
|
@@ -144,6 +146,11 @@
|
|
|
144
146
|
}
|
|
145
147
|
}
|
|
146
148
|
|
|
149
|
+
/* Placed above the field (see useLookup): grow upwards from it. */
|
|
150
|
+
&[data-above] {
|
|
151
|
+
transform-origin: bottom left;
|
|
152
|
+
}
|
|
153
|
+
|
|
147
154
|
&[data-anchor="manual"] {
|
|
148
155
|
inset: unset;
|
|
149
156
|
position: fixed;
|
package/src/AfLookup/Lookup.tsx
CHANGED
|
@@ -78,12 +78,38 @@ export function useLookup<T extends Record<string, unknown>>({
|
|
|
78
78
|
// mounts with the search value already set.
|
|
79
79
|
setInitialSearchValue(searchValue);
|
|
80
80
|
openDialog();
|
|
81
|
+
setGrowDirection();
|
|
82
|
+
// A manually anchored dialog (in a virtualized grid) is placed in the next frame.
|
|
83
|
+
requestAnimationFrame(setGrowDirection);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// When there is no room below the field, the dialog is placed above it; it then grows upwards
|
|
87
|
+
// from the field instead of down towards it. Its bottom edge tells, whatever the opening scale.
|
|
88
|
+
function setGrowDirection() {
|
|
89
|
+
let $dialog = dialogRef.current;
|
|
90
|
+
let anchor = document.getElementById(inputId) ?? getAnchorElement?.();
|
|
91
|
+
if (!$dialog || !anchor) return;
|
|
92
|
+
|
|
93
|
+
let above = $dialog.getBoundingClientRect().bottom <= anchor.getBoundingClientRect().top + 1;
|
|
94
|
+
$dialog.toggleAttribute("data-above", above);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// useLookupState opens the dialog on ArrowDown and F4 itself, which would skip the size mode and
|
|
98
|
+
// the grow direction above; every way of opening it goes through handleOpenDialog instead.
|
|
99
|
+
function handleKeyDown(event: React.KeyboardEvent<HTMLElement>) {
|
|
100
|
+
if (event.key === "ArrowDown" || event.key === "F4") {
|
|
101
|
+
event.preventDefault();
|
|
102
|
+
event.stopPropagation();
|
|
103
|
+
handleOpenDialog();
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
onKeyDown(event);
|
|
81
107
|
}
|
|
82
108
|
|
|
83
109
|
return {
|
|
84
110
|
open,
|
|
85
111
|
openDialog: handleOpenDialog,
|
|
86
|
-
onKeyDown,
|
|
112
|
+
onKeyDown: handleKeyDown,
|
|
87
113
|
onChange: handleChange,
|
|
88
114
|
dialogRef,
|
|
89
115
|
anchorName,
|
|
@@ -135,7 +161,8 @@ export function AfLookup<T extends Record<string, unknown>>({
|
|
|
135
161
|
}
|
|
136
162
|
|
|
137
163
|
return (
|
|
138
|
-
|
|
164
|
+
// The dialog anchors to the input itself (see Lookup.css), not to the label beside or above it.
|
|
165
|
+
<div style={{ "--ObLookup-anchor": anchorName } as React.CSSProperties}>
|
|
139
166
|
<div
|
|
140
167
|
className="ObLookup-inputWrap"
|
|
141
168
|
style={
|
|
@@ -73,7 +73,8 @@ export function AfLookupEdit<T extends Record<string, unknown>>({
|
|
|
73
73
|
let hasClear = !!(nullable && value && onChange);
|
|
74
74
|
|
|
75
75
|
return (
|
|
76
|
-
|
|
76
|
+
// The dialog anchors to the input itself (see Lookup.css), not to the label beside or above it.
|
|
77
|
+
<div style={{ "--ObLookup-anchor": anchorName } as React.CSSProperties}>
|
|
77
78
|
<div
|
|
78
79
|
className="ObLookup-inputWrap"
|
|
79
80
|
style={
|
|
@@ -9,7 +9,7 @@ import { List, type RowComponentProps, useListRef } from "react-window";
|
|
|
9
9
|
import { getLocalizedString } from "../i18n.js";
|
|
10
10
|
import { useDsSize } from "../theme/sizeMode.js";
|
|
11
11
|
|
|
12
|
-
/** The row and header height until `--ds-size-
|
|
12
|
+
/** The row and header height until `--ds-size-11` is measured, which follows the size mode like AfGrid's rows. */
|
|
13
13
|
const ROW_HEIGHT = 40;
|
|
14
14
|
/** Narrowest a column can be made, and what a flexible column counts for in the minimum width */
|
|
15
15
|
const MIN_COLUMN_WIDTH = 80;
|
|
@@ -192,7 +192,7 @@ export function LookupGrid<T extends Record<string, unknown>>({
|
|
|
192
192
|
let listRef = useListRef(null);
|
|
193
193
|
let headerRowRef = useRef<HTMLDivElement>(null);
|
|
194
194
|
let rootRef = useRef<HTMLDivElement>(null);
|
|
195
|
-
let rowHeight = useDsSize(rootRef,
|
|
195
|
+
let rowHeight = useDsSize(rootRef, 11, ROW_HEIGHT);
|
|
196
196
|
let [resizedWidths, setResizedWidths] = useState<Partial<Record<string, number>>>({});
|
|
197
197
|
let [resizing, setResizing] = useState<{ field: string; startWidth: number } | null>(null);
|
|
198
198
|
let [listInnerWidth, setListInnerWidth] = useState<number>();
|
|
@@ -22,7 +22,8 @@ export type GridEditLookupProps<
|
|
|
22
22
|
|
|
23
23
|
/**
|
|
24
24
|
* A lookup as a grid cell editor: a read-only input with a search button that opens the
|
|
25
|
-
* lookup dialog anchored to the cell, and a clear button when the field is nullable.
|
|
25
|
+
* lookup dialog anchored to the cell, and a clear button when the field is nullable. With
|
|
26
|
+
* `editable` the value can be typed into the cell as well, such as a username. Give it
|
|
26
27
|
* to a column's `renderEditCell`. Equivalent of `GridEditLookup` in `@olenbetong/appframe-mui`.
|
|
27
28
|
*/
|
|
28
29
|
export function GridEditLookup<
|
|
@@ -77,7 +78,7 @@ export function GridEditLookup<
|
|
|
77
78
|
slotProps={{
|
|
78
79
|
root: {
|
|
79
80
|
// @ts-expect-error: typed as HTMLElement, but it is the input
|
|
80
|
-
readOnly:
|
|
81
|
+
readOnly: !props.editable,
|
|
81
82
|
},
|
|
82
83
|
}}
|
|
83
84
|
onKeyDown={onKeyDown}
|
package/src/grid/index.css
CHANGED
|
@@ -4,11 +4,33 @@
|
|
|
4
4
|
|
|
5
5
|
/*
|
|
6
6
|
* The grid follows the Designsystemet size mode it sits in instead of MUI's density and type
|
|
7
|
-
* scale
|
|
8
|
-
*
|
|
7
|
+
* scale, and its row and header heights come from the same mode (`useAfSizeMode`). Its text is
|
|
8
|
+
* one step below the mode's body text - at sm that is MUI's compact grid text - except at xs,
|
|
9
|
+
* where the body text is already that small. The size is picked on the element that sets the
|
|
10
|
+
* mode, so the closest one wins; the components that use `data-size` for a variant of their own
|
|
11
|
+
* are left out, as in `getSizeMode`. Without a size mode the grid inherits the page's text.
|
|
9
12
|
*/
|
|
13
|
+
[data-size]:not(.ds-heading, .ds-paragraph, .ds-spinner, .ds-avatar) {
|
|
14
|
+
--AfGrid-font-size: var(--ds-body-sm-font-size);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
[data-size="xs"]:not(.ds-heading, .ds-paragraph, .ds-spinner, .ds-avatar) {
|
|
18
|
+
--AfGrid-font-size: var(--ds-body-md-font-size);
|
|
19
|
+
}
|
|
20
|
+
|
|
10
21
|
.AfGrid-root .MuiDataGrid-root {
|
|
11
|
-
font-size: inherit;
|
|
22
|
+
font-size: var(--AfGrid-font-size, inherit);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/*
|
|
26
|
+
* Inputs in the grid - cell editors, header filters - take the grid's text size rather than the
|
|
27
|
+
* mode's body text. Only where there is a mouse, so a touch device keeps Designsystemet's floor
|
|
28
|
+
* that stops iOS zooming in on a focused field (see the site's xs rule).
|
|
29
|
+
*/
|
|
30
|
+
@media (hover: hover) and (pointer: fine) {
|
|
31
|
+
.AfGrid-root .MuiDataGrid-root .ds-input {
|
|
32
|
+
--dsc-input-font-size: 1em;
|
|
33
|
+
}
|
|
12
34
|
}
|
|
13
35
|
|
|
14
36
|
/*
|
|
@@ -30,13 +52,13 @@
|
|
|
30
52
|
|
|
31
53
|
/*
|
|
32
54
|
* Controls in a data cell - editors, lookup buttons, the row's action buttons - fit the row, which
|
|
33
|
-
* is `--ds-size-
|
|
55
|
+
* is `--ds-size-11` tall, rather than taking Designsystemet's `--ds-size-12` control height, which
|
|
34
56
|
* would overflow it at every size. The custom properties are set on the components themselves,
|
|
35
57
|
* so they are overridden there and not on the cell.
|
|
36
58
|
*/
|
|
37
59
|
.AfGrid-root .MuiDataGrid-cell {
|
|
38
60
|
.ds-input {
|
|
39
|
-
--dsc-input-size: var(--ds-size-
|
|
61
|
+
--dsc-input-size: var(--ds-size-11, 2.25rem);
|
|
40
62
|
--dsc-input-padding-block: 0;
|
|
41
63
|
}
|
|
42
64
|
|
|
@@ -7,13 +7,14 @@ const FALLBACK_HEIGHT = 36;
|
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
9
|
* Row and header heights from the Designsystemet size mode the grid sits in, in place of MUI's
|
|
10
|
-
* density: `--ds-size-
|
|
11
|
-
* site's 14px root
|
|
12
|
-
*
|
|
10
|
+
* density: `--ds-size-11`, which is about 28px at xs, 34px at sm, 39px at md and 45px at lg with
|
|
11
|
+
* the site's 14px root - close to Designsystemet's own table at each size. `--ds-size-10` left the
|
|
12
|
+
* text crowded in its row. The grid renders at the standard density, so these are the heights as
|
|
13
|
+
* they are. The text, toolbar and footer follow the same size mode through the stylesheet and by
|
|
13
14
|
* inheriting `data-size`.
|
|
14
15
|
*/
|
|
15
16
|
export function useAfSizeMode(rootRef: RefObject<HTMLElement | null>) {
|
|
16
|
-
let height = useDsSize(rootRef,
|
|
17
|
+
let height = useDsSize(rootRef, 11, FALLBACK_HEIGHT);
|
|
17
18
|
|
|
18
19
|
return { rowHeight: height, columnHeaderHeight: height };
|
|
19
20
|
}
|
package/tsconfig.build.json
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
{
|
|
2
|
-
"extends": "../tsconfig.build.base.json",
|
|
3
|
-
"compilerOptions": {
|
|
4
|
-
"rootDir": "./src",
|
|
5
|
-
"outDir": "./es"
|
|
6
|
-
},
|
|
7
|
-
"include": ["src"],
|
|
8
|
-
"exclude": ["es", "node_modules"]
|
|
9
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"extends": "../tsconfig.build.base.json",
|
|
3
|
+
"compilerOptions": {
|
|
4
|
+
"rootDir": "./src",
|
|
5
|
+
"outDir": "./es"
|
|
6
|
+
},
|
|
7
|
+
"include": ["src"],
|
|
8
|
+
"exclude": ["es", "node_modules"]
|
|
9
|
+
}
|
package/tsconfig.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
{
|
|
2
|
-
"extends": "../tsconfig.build.base.json",
|
|
3
|
-
"compilerOptions": {
|
|
4
|
-
"rootDir": "./src",
|
|
5
|
-
"outDir": "./es"
|
|
6
|
-
},
|
|
7
|
-
"include": ["src"]
|
|
8
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"extends": "../tsconfig.build.base.json",
|
|
3
|
+
"compilerOptions": {
|
|
4
|
+
"rootDir": "./src",
|
|
5
|
+
"outDir": "./es"
|
|
6
|
+
},
|
|
7
|
+
"include": ["src"]
|
|
8
|
+
}
|
package/vitest.config.ts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import { defineConfig } from "vitest/config";
|
|
2
|
-
|
|
3
|
-
export default defineConfig({
|
|
4
|
-
test: {
|
|
5
|
-
include: ["src/**/*.test.tsx", "src/**/*.test.ts"],
|
|
6
|
-
environment: "happy-dom",
|
|
7
|
-
setupFiles: ["src/test/setup.ts"],
|
|
8
|
-
env: { NODE_ENV: "test" },
|
|
9
|
-
},
|
|
10
|
-
});
|
|
1
|
+
import { defineConfig } from "vitest/config";
|
|
2
|
+
|
|
3
|
+
export default defineConfig({
|
|
4
|
+
test: {
|
|
5
|
+
include: ["src/**/*.test.tsx", "src/**/*.test.ts"],
|
|
6
|
+
environment: "happy-dom",
|
|
7
|
+
setupFiles: ["src/test/setup.ts"],
|
|
8
|
+
env: { NODE_ENV: "test" },
|
|
9
|
+
},
|
|
10
|
+
});
|