@antelopejs/dms 0.3.6 → 0.3.7
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 +17 -0
- package/frontend-vue/layers/dms-ui/app/components/grid/Grid.vue +10 -1
- package/frontend-vue/layers/dms-ui/app/components/grid/GridRow.vue +16 -6
- package/frontend-vue/layers/dms-ui/app/components/grid/columns.ts +29 -0
- package/frontend-vue/layers/dms-ui/app/components/grid/constants.ts +1 -0
- package/frontend-vue/tests/grid-responsive-columns.test.ts +105 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,22 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## v0.3.7
|
|
4
|
+
|
|
5
|
+
[compare changes](https://github.com/AntelopeJS/dms/compare/v0.3.6...v0.3.7)
|
|
6
|
+
|
|
7
|
+
### 🩹 Fixes
|
|
8
|
+
|
|
9
|
+
- **ui:** Let Grid drop columns instead of overflowing its container ([#26](https://github.com/AntelopeJS/dms/pull/26))
|
|
10
|
+
|
|
11
|
+
### 🏡 Chore
|
|
12
|
+
|
|
13
|
+
- Make playground orb setup reliable ([#23](https://github.com/AntelopeJS/dms/pull/23))
|
|
14
|
+
- **playground:** Use dms frontend 0.2.1 ([#24](https://github.com/AntelopeJS/dms/pull/24))
|
|
15
|
+
|
|
16
|
+
### ❤️ Contributors
|
|
17
|
+
|
|
18
|
+
- Antony Rizzitelli <rizzitelli.antony@pm.me>
|
|
19
|
+
|
|
3
20
|
## v0.3.6
|
|
4
21
|
|
|
5
22
|
[compare changes](https://github.com/AntelopeJS/dms/compare/v0.3.5...v0.3.6)
|
|
@@ -1,13 +1,16 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
import type { DefaultComponentProps } from "../../../../dms-core/app/types/component";
|
|
3
3
|
import { GRID_CONTEXT, type GridContext } from "./constants";
|
|
4
|
+
import { GRID_DEFAULT_MIN_COLUMN_WIDTH, gridColumnsTemplate } from "./columns";
|
|
4
5
|
|
|
5
6
|
interface GridProps extends DefaultComponentProps {
|
|
6
7
|
gap?: string;
|
|
8
|
+
minColumnWidth?: string;
|
|
7
9
|
}
|
|
8
10
|
|
|
9
11
|
const props = withDefaults(defineProps<GridProps>(), {
|
|
10
12
|
gap: "1rem",
|
|
13
|
+
minColumnWidth: GRID_DEFAULT_MIN_COLUMN_WIDTH,
|
|
11
14
|
});
|
|
12
15
|
|
|
13
16
|
const rowColumnCounts = ref<number[]>([]);
|
|
@@ -18,6 +21,7 @@ const maxColumns = computed(() => {
|
|
|
18
21
|
});
|
|
19
22
|
|
|
20
23
|
const gapRef = computed(() => props.gap);
|
|
24
|
+
const minColumnWidthRef = computed(() => props.minColumnWidth);
|
|
21
25
|
|
|
22
26
|
provide<GridContext>(GRID_CONTEXT, {
|
|
23
27
|
registerRowColumnCount: (columnCount: number) => {
|
|
@@ -25,11 +29,16 @@ provide<GridContext>(GRID_CONTEXT, {
|
|
|
25
29
|
},
|
|
26
30
|
maxColumns,
|
|
27
31
|
gap: gapRef,
|
|
32
|
+
minColumnWidth: minColumnWidthRef,
|
|
28
33
|
});
|
|
29
34
|
|
|
30
35
|
const gridStyle = computed(() => ({
|
|
31
36
|
display: "grid",
|
|
32
|
-
gridTemplateColumns:
|
|
37
|
+
gridTemplateColumns: gridColumnsTemplate(
|
|
38
|
+
maxColumns.value,
|
|
39
|
+
props.gap,
|
|
40
|
+
props.minColumnWidth,
|
|
41
|
+
),
|
|
33
42
|
gap: props.gap,
|
|
34
43
|
width: "100%",
|
|
35
44
|
}));
|
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
import type { DefaultComponentProps } from "../../../../dms-core/app/types/component";
|
|
3
3
|
import { GRID_CONTEXT, type GridContext } from "./constants";
|
|
4
|
+
import { GRID_DEFAULT_MIN_COLUMN_WIDTH, gridColumnsTemplate } from "./columns";
|
|
4
5
|
|
|
5
6
|
interface GridRowProps extends DefaultComponentProps {}
|
|
6
7
|
|
|
8
|
+
const DEFAULT_GAP = "1rem";
|
|
9
|
+
|
|
7
10
|
const props = defineProps<GridRowProps>();
|
|
8
11
|
|
|
9
12
|
const gridContext = inject<GridContext>(GRID_CONTEXT);
|
|
@@ -13,12 +16,19 @@ if (gridContext) {
|
|
|
13
16
|
gridContext.registerRowColumnCount(columnCount);
|
|
14
17
|
}
|
|
15
18
|
|
|
16
|
-
const rowStyle = computed(() =>
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
19
|
+
const rowStyle = computed(() => {
|
|
20
|
+
const gap = gridContext?.gap.value ?? DEFAULT_GAP;
|
|
21
|
+
return {
|
|
22
|
+
gridColumn: "1 / -1",
|
|
23
|
+
display: "grid",
|
|
24
|
+
gridTemplateColumns: gridColumnsTemplate(
|
|
25
|
+
gridContext?.maxColumns.value || 1,
|
|
26
|
+
gap,
|
|
27
|
+
gridContext?.minColumnWidth.value ?? GRID_DEFAULT_MIN_COLUMN_WIDTH,
|
|
28
|
+
),
|
|
29
|
+
gap,
|
|
30
|
+
};
|
|
31
|
+
});
|
|
22
32
|
</script>
|
|
23
33
|
|
|
24
34
|
<template>
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
const SINGLE_COLUMN = 1;
|
|
2
|
+
|
|
3
|
+
/** Track floor below which a column stops being usable, as a CSS length. */
|
|
4
|
+
export const GRID_DEFAULT_MIN_COLUMN_WIDTH = "240px";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* The column track template a `Grid` and every `GridRow` inside it share.
|
|
8
|
+
*
|
|
9
|
+
* `auto-fill` drops tracks as the container narrows, which is what makes the
|
|
10
|
+
* grid responsive without a breakpoint list. The track floor is the larger of
|
|
11
|
+
* `minColumnWidth` and the width one column would have at `maxColumns`: once
|
|
12
|
+
* the container is wide enough, that share exceeds `minColumnWidth` and leaves
|
|
13
|
+
* no room for an extra track, so `auto-fill` settles on exactly `maxColumns`
|
|
14
|
+
* and the wide layout stays what it was. The outer `min(100%, ...)` keeps a
|
|
15
|
+
* container narrower than `minColumnWidth` from overflowing it.
|
|
16
|
+
*
|
|
17
|
+
* Rows resolve this same template against the same width, so they keep lining
|
|
18
|
+
* up on one another — what `maxColumns` was introduced for — at every width
|
|
19
|
+
* rather than only at the widest.
|
|
20
|
+
*/
|
|
21
|
+
export function gridColumnsTemplate(
|
|
22
|
+
maxColumns: number,
|
|
23
|
+
gap: string,
|
|
24
|
+
minColumnWidth: string,
|
|
25
|
+
): string {
|
|
26
|
+
const columns = Math.max(maxColumns, SINGLE_COLUMN);
|
|
27
|
+
const columnShare = `(100% - ${columns - SINGLE_COLUMN} * ${gap}) / ${columns}`;
|
|
28
|
+
return `repeat(auto-fill, minmax(min(100%, max(${minColumnWidth}, ${columnShare})), 1fr))`;
|
|
29
|
+
}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
import { describe, expect, it } from "vitest";
|
|
3
|
+
import { gridColumnsTemplate } from "../layers/dms-ui/app/components/grid/columns";
|
|
4
|
+
|
|
5
|
+
const GAP = "1rem";
|
|
6
|
+
const MIN = "240px";
|
|
7
|
+
|
|
8
|
+
const sourceOf = (file: string) =>
|
|
9
|
+
readFileSync(
|
|
10
|
+
new URL(`../layers/dms-ui/app/components/grid/${file}`, import.meta.url),
|
|
11
|
+
"utf8",
|
|
12
|
+
);
|
|
13
|
+
|
|
14
|
+
// Guards the exact-fit case: at the width where the track floor is precisely
|
|
15
|
+
// one Nth of the container, binary floats land a hair under the integer.
|
|
16
|
+
const FIT_EPSILON = 1e-6;
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Resolves the template the way a browser would, in pixels, and reports how
|
|
20
|
+
* many tracks `auto-fill` ends up with for a given container width.
|
|
21
|
+
*/
|
|
22
|
+
function resolveColumnCount(
|
|
23
|
+
template: string,
|
|
24
|
+
containerWidth: number,
|
|
25
|
+
gapPx: number,
|
|
26
|
+
): number {
|
|
27
|
+
const match = template.match(
|
|
28
|
+
/repeat\(auto-fill, minmax\(min\(100%, max\((\d+)px, \(100% - (\d+) \* [^)]+\) \/ (\d+)\)\), 1fr\)\)/,
|
|
29
|
+
);
|
|
30
|
+
if (!match) throw new Error(`unexpected template: ${template}`);
|
|
31
|
+
const [, min, gapCount, maxColumns] = match;
|
|
32
|
+
const share =
|
|
33
|
+
(containerWidth - Number(gapCount) * gapPx) / Number(maxColumns);
|
|
34
|
+
const floor = Math.min(containerWidth, Math.max(Number(min), share));
|
|
35
|
+
if (floor <= 0) return Number(maxColumns);
|
|
36
|
+
return Math.max(
|
|
37
|
+
1,
|
|
38
|
+
Math.floor((containerWidth + gapPx) / (floor + gapPx) + FIT_EPSILON),
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
describe("grid column template", () => {
|
|
43
|
+
it("keeps every column at the widest row's count once the container is wide", () => {
|
|
44
|
+
const template = gridColumnsTemplate(6, GAP, MIN);
|
|
45
|
+
expect(resolveColumnCount(template, 1600, 16)).toBe(6);
|
|
46
|
+
expect(resolveColumnCount(template, 2400, 16)).toBe(6);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it("drops columns rather than shrinking them below the minimum", () => {
|
|
50
|
+
const template = gridColumnsTemplate(6, GAP, MIN);
|
|
51
|
+
expect(resolveColumnCount(template, 976, 16)).toBe(3);
|
|
52
|
+
expect(resolveColumnCount(template, 657, 16)).toBe(2);
|
|
53
|
+
expect(resolveColumnCount(template, 328, 16)).toBe(1);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it("never asks for a track wider than the container", () => {
|
|
57
|
+
const template = gridColumnsTemplate(4, GAP, MIN);
|
|
58
|
+
expect(template).toContain("min(100%,");
|
|
59
|
+
expect(resolveColumnCount(template, 200, 16)).toBe(1);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it("gives rows of the same grid one shared template, so they stay aligned", () => {
|
|
63
|
+
expect(gridColumnsTemplate(4, GAP, MIN)).toBe(
|
|
64
|
+
gridColumnsTemplate(4, GAP, MIN),
|
|
65
|
+
);
|
|
66
|
+
expect(gridColumnsTemplate(4, GAP, MIN)).not.toBe(
|
|
67
|
+
gridColumnsTemplate(3, GAP, MIN),
|
|
68
|
+
);
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it("treats a row-less grid as a single column", () => {
|
|
72
|
+
expect(gridColumnsTemplate(0, GAP, MIN)).toBe(
|
|
73
|
+
gridColumnsTemplate(1, GAP, MIN),
|
|
74
|
+
);
|
|
75
|
+
expect(resolveColumnCount(gridColumnsTemplate(1, GAP, MIN), 976, 16)).toBe(
|
|
76
|
+
1,
|
|
77
|
+
);
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it("subtracts the gaps the row actually renders", () => {
|
|
81
|
+
expect(gridColumnsTemplate(3, "2rem", MIN)).toContain("(100% - 2 * 2rem)");
|
|
82
|
+
expect(gridColumnsTemplate(1, GAP, MIN)).toContain("(100% - 0 * 1rem)");
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it("honours a caller-supplied minimum", () => {
|
|
86
|
+
expect(gridColumnsTemplate(4, GAP, "120px")).toContain("120px");
|
|
87
|
+
expect(
|
|
88
|
+
resolveColumnCount(gridColumnsTemplate(4, GAP, "120px"), 657, 16),
|
|
89
|
+
).toBe(4);
|
|
90
|
+
});
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
describe("grid components", () => {
|
|
94
|
+
it("drive both the container and its rows from the shared template", () => {
|
|
95
|
+
for (const file of ["Grid.vue", "GridRow.vue"]) {
|
|
96
|
+
const source = sourceOf(file);
|
|
97
|
+
expect(source).toContain("gridColumnsTemplate(");
|
|
98
|
+
expect(source).not.toMatch(/repeat\(\$\{/);
|
|
99
|
+
}
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
it("keeps rows spanning the full grid width", () => {
|
|
103
|
+
expect(sourceOf("GridRow.vue")).toContain('gridColumn: "1 / -1"');
|
|
104
|
+
});
|
|
105
|
+
});
|