@revenexx/studio-shared 0.3.0 → 0.3.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/dist/module.d.mts +1 -0
- package/dist/module.json +1 -1
- package/dist/module.mjs +5 -0
- package/dist/runtime/components/DataTable/ColumnsMenu.d.vue.ts +2 -0
- package/dist/runtime/components/DataTable/ColumnsMenu.vue +90 -34
- package/dist/runtime/components/DataTable/ColumnsMenu.vue.d.ts +2 -0
- package/dist/runtime/components/DataTable/DataTable.d.vue.ts +26 -2
- package/dist/runtime/components/DataTable/DataTable.vue +256 -108
- package/dist/runtime/components/DataTable/DataTable.vue.d.ts +26 -2
- package/dist/runtime/components/DataTable/FilterMenu.d.vue.ts +5 -3
- package/dist/runtime/components/DataTable/FilterMenu.vue +32 -36
- package/dist/runtime/components/DataTable/FilterMenu.vue.d.ts +5 -3
- package/dist/runtime/components/DataTable/types.d.ts +15 -2
- package/dist/runtime/components/Shared/FloatingPanel.d.vue.ts +55 -0
- package/dist/runtime/components/Shared/FloatingPanel.vue +118 -0
- package/dist/runtime/components/Shared/FloatingPanel.vue.d.ts +55 -0
- package/dist/runtime/composables/useFloatingPanel.d.ts +69 -0
- package/dist/runtime/composables/useFloatingPanel.js +112 -0
- package/dist/runtime/composables/useTalkbackChannels.d.ts +21 -6
- package/dist/runtime/composables/useTalkbackChannels.js +16 -12
- package/dist/runtime/lib/columnOrder.d.ts +45 -0
- package/dist/runtime/lib/columnOrder.js +28 -0
- package/dist/runtime/lib/columnVisibility.d.ts +41 -0
- package/dist/runtime/lib/columnVisibility.js +12 -0
- package/dist/runtime/lib/dataTableFilters.d.ts +83 -0
- package/dist/runtime/lib/dataTableFilters.js +23 -0
- package/dist/runtime/lib/i18nBridge.d.ts +7 -2
- package/dist/types.d.mts +2 -0
- package/package.json +2 -2
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The order a table's columns are drawn in, once the reader has an opinion.
|
|
3
|
+
*
|
|
4
|
+
* Sits beside `columnVisibility` because it has the SAME trap, and the trap has
|
|
5
|
+
* already shipped once in this file's neighbour.
|
|
6
|
+
*
|
|
7
|
+
* A table's column list is not fixed for the life of the component. A tab that
|
|
8
|
+
* offers Markets only once the service proves it has the column, or engagement
|
|
9
|
+
* once the deployment reports any, hands the table a LONGER array on a later
|
|
10
|
+
* render. If the remembered order were treated as the whole truth, every column
|
|
11
|
+
* that turned up afterwards would be missing from it — and the obvious
|
|
12
|
+
* implementation, "sort by index in the remembered list, unknowns last", puts
|
|
13
|
+
* that new column silently at the far right where nobody scrolls. Which is
|
|
14
|
+
* indistinguishable, from the outside, from never having been built.
|
|
15
|
+
*
|
|
16
|
+
* So what is remembered is the reader's own arrangement, and a column that is
|
|
17
|
+
* not in it keeps its DECLARED neighbours: it lands directly after whichever
|
|
18
|
+
* declared predecessor is nearest, or first if it has none. A column added
|
|
19
|
+
* between Channel and Status appears between Channel and Status, wherever the
|
|
20
|
+
* reader has since dragged those two.
|
|
21
|
+
*/
|
|
22
|
+
export interface OrderableColumn {
|
|
23
|
+
key: string;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* The declared columns, arranged.
|
|
27
|
+
*
|
|
28
|
+
* An empty `order` means the reader has not touched anything, which is not the
|
|
29
|
+
* same as an order that happens to match — the first is "use the declaration",
|
|
30
|
+
* the second is a decision that must survive the declaration changing.
|
|
31
|
+
*/
|
|
32
|
+
export declare function orderColumnsBy<T extends OrderableColumn>(columns: readonly T[], order: readonly string[]): T[];
|
|
33
|
+
/**
|
|
34
|
+
* Move one column and return the arrangement to remember.
|
|
35
|
+
*
|
|
36
|
+
* Always a COMPLETE list of keys, even when the previous one was partial or
|
|
37
|
+
* empty: after the first drag there is an opinion about every column, and a
|
|
38
|
+
* half-remembered order would leave the untouched ones re-deriving their
|
|
39
|
+
* position on every render.
|
|
40
|
+
*
|
|
41
|
+
* `to` is the index in the list as it will be — what a drop target means — and
|
|
42
|
+
* out-of-range values clamp rather than throw. A drag that ends outside the
|
|
43
|
+
* list means "leave it where it is", not "crash the picker".
|
|
44
|
+
*/
|
|
45
|
+
export declare function reorderColumns<T extends OrderableColumn>(columns: readonly T[], order: readonly string[], from: number, to: number): string[];
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
export function orderColumnsBy(columns, order) {
|
|
2
|
+
if (!order.length) return [...columns];
|
|
3
|
+
const byKey = new Map(columns.map((c) => [c.key, c]));
|
|
4
|
+
const remembered = new Set(order);
|
|
5
|
+
const result = order.filter((k) => byKey.has(k)).map((k) => byKey.get(k));
|
|
6
|
+
columns.forEach((column, declaredIndex) => {
|
|
7
|
+
if (remembered.has(column.key)) return;
|
|
8
|
+
let insertAt = 0;
|
|
9
|
+
for (let i = declaredIndex - 1; i >= 0; i--) {
|
|
10
|
+
const at = result.indexOf(columns[i]);
|
|
11
|
+
if (at !== -1) {
|
|
12
|
+
insertAt = at + 1;
|
|
13
|
+
break;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
result.splice(insertAt, 0, column);
|
|
17
|
+
});
|
|
18
|
+
return result;
|
|
19
|
+
}
|
|
20
|
+
export function reorderColumns(columns, order, from, to) {
|
|
21
|
+
const keys = orderColumnsBy(columns, order).map((c) => c.key);
|
|
22
|
+
if (from < 0 || from >= keys.length) return keys;
|
|
23
|
+
const clamped = Math.max(0, Math.min(to, keys.length - 1));
|
|
24
|
+
if (clamped === from) return keys;
|
|
25
|
+
const [moved] = keys.splice(from, 1);
|
|
26
|
+
keys.splice(clamped, 0, moved);
|
|
27
|
+
return keys;
|
|
28
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Which of a DataTable's columns are drawn.
|
|
3
|
+
*
|
|
4
|
+
* Pulled out of `DataTable.vue` because the rule has a trap in it that a
|
|
5
|
+
* component test would not have caught cheaply, and that shipped once already.
|
|
6
|
+
*
|
|
7
|
+
* A table's column list is NOT fixed for the life of the component. A tab that
|
|
8
|
+
* only offers Markets once the service proves it has the column, or a validity
|
|
9
|
+
* window once the deployment has grown one, hands the table a LONGER array on a
|
|
10
|
+
* later render than the one it was set up with.
|
|
11
|
+
*
|
|
12
|
+
* The table used to seed a set of visible keys once and filter by membership.
|
|
13
|
+
* Every column that turned up afterwards was therefore missing from that set and
|
|
14
|
+
* arrived SWITCHED OFF — present in the picker, unticked, and invisible to
|
|
15
|
+
* everybody who did not go looking. Nobody reports that: from the outside it
|
|
16
|
+
* looks like the column was never built.
|
|
17
|
+
*
|
|
18
|
+
* So what is remembered is the user's own decisions — the columns they ticked
|
|
19
|
+
* away — and everything else follows its declared default. A column that
|
|
20
|
+
* appears later gets that default, which is what somebody adding a column
|
|
21
|
+
* expects and what the previous rule quietly denied them.
|
|
22
|
+
*/
|
|
23
|
+
export interface VisibilityColumn {
|
|
24
|
+
key: string;
|
|
25
|
+
/** `false` means "start hidden". Absent means shown. */
|
|
26
|
+
visible?: boolean;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* The columns hidden before the user has touched anything — the declared
|
|
30
|
+
* defaults, and nothing else.
|
|
31
|
+
*/
|
|
32
|
+
export declare function initiallyHidden(columns: readonly VisibilityColumn[]): Set<string>;
|
|
33
|
+
/**
|
|
34
|
+
* The columns to draw: everything except what the user put away.
|
|
35
|
+
*
|
|
36
|
+
* Deliberately keyed on hidden rather than on visible. The set of things a
|
|
37
|
+
* person has decided against is stable; the set of things that exist is not.
|
|
38
|
+
*/
|
|
39
|
+
export declare function visibleColumnsOf<T extends VisibilityColumn>(columns: readonly T[], hidden: ReadonlySet<string>): T[];
|
|
40
|
+
/** Add or remove one column from the user's hidden set. */
|
|
41
|
+
export declare function toggleHidden(hidden: ReadonlySet<string>, key: string): Set<string>;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export function initiallyHidden(columns) {
|
|
2
|
+
return new Set(columns.filter((c) => c.visible === false).map((c) => c.key));
|
|
3
|
+
}
|
|
4
|
+
export function visibleColumnsOf(columns, hidden) {
|
|
5
|
+
return columns.filter((c) => !hidden.has(c.key));
|
|
6
|
+
}
|
|
7
|
+
export function toggleHidden(hidden, key) {
|
|
8
|
+
const next = new Set(hidden);
|
|
9
|
+
if (next.has(key)) next.delete(key);
|
|
10
|
+
else next.add(key);
|
|
11
|
+
return next;
|
|
12
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What a row's value is for a filter dimension — and why it can be several.
|
|
3
|
+
*
|
|
4
|
+
* Pulled out of `DataTable.vue` because the rule underneath it changed shape,
|
|
5
|
+
* and the change is the kind that reads as harmless and is not.
|
|
6
|
+
*
|
|
7
|
+
* ## A row used to belong to exactly one bucket
|
|
8
|
+
*
|
|
9
|
+
* `getter: (row) => string` gives every row ONE value per dimension. For status
|
|
10
|
+
* or channel that is the truth. For anything a row can have several of — the
|
|
11
|
+
* languages a template is written in, the markets it is scoped to, the tags on
|
|
12
|
+
* a product — it is not, and the only way to express it was to make the SET the
|
|
13
|
+
* value:
|
|
14
|
+
*
|
|
15
|
+
* getter: r => r.locales.join(', ')
|
|
16
|
+
*
|
|
17
|
+
* which produces a filter menu that reads
|
|
18
|
+
*
|
|
19
|
+
* Deutsch (Deutschland) 2
|
|
20
|
+
* Deutsch (Deutschland) + British English 19
|
|
21
|
+
* Deutsch (Deutschland) + British English + polski (Polska) 2
|
|
22
|
+
* Deutsch (Deutschland) + British English + français + polski 1
|
|
23
|
+
* …
|
|
24
|
+
*
|
|
25
|
+
* Two things are wrong with that and only one of them is cosmetic.
|
|
26
|
+
*
|
|
27
|
+
* The visible one: the options are COMBINATIONS, so the list grows with the
|
|
28
|
+
* powerset of the values. Four languages produce up to fifteen entries, six up
|
|
29
|
+
* to sixty-three. No menu, panel or layout survives that.
|
|
30
|
+
*
|
|
31
|
+
* The one that matters: "which of these have Polish?" cannot be asked. The
|
|
32
|
+
* answer is spread across three options, and none of the counts is it. An
|
|
33
|
+
* operator who picks the option that looks closest gets a confident, wrong
|
|
34
|
+
* number — the counts adding up to exactly the row total is the tell that this
|
|
35
|
+
* is a partition rather than a set of overlapping questions.
|
|
36
|
+
*
|
|
37
|
+
* ## So a getter may now return an array
|
|
38
|
+
*
|
|
39
|
+
* `string | string[] | null | undefined`. A row lands in as many buckets as it
|
|
40
|
+
* has values, the options become the four languages rather than the fifteen
|
|
41
|
+
* combinations, and "polski 6" means what it says.
|
|
42
|
+
*
|
|
43
|
+
* ## The string path is byte-for-byte what it was
|
|
44
|
+
*
|
|
45
|
+
* Deliberately: every existing surface passes a string getter and none of them
|
|
46
|
+
* should move a pixel. A string — INCLUDING the empty string — is one value,
|
|
47
|
+
* and `null`/`undefined` still collapse to `''` and count under that key, which
|
|
48
|
+
* is what the old `String(getter(row) ?? '')` did. Only the array path is new.
|
|
49
|
+
*
|
|
50
|
+
* Inside an array, empty entries ARE dropped: a template with no languages
|
|
51
|
+
* belongs to no language, and letting it pile into a `''` bucket would invent a
|
|
52
|
+
* silent option nobody declared.
|
|
53
|
+
*
|
|
54
|
+
* ## The counts stop summing to the row total, on purpose
|
|
55
|
+
*
|
|
56
|
+
* For a multi-valued dimension they now overlap, because a row with three
|
|
57
|
+
* languages is counted under three of them. That looks like a bug to anybody
|
|
58
|
+
* who checks the arithmetic, and correcting it would restore exactly the
|
|
59
|
+
* defect this exists to remove.
|
|
60
|
+
*/
|
|
61
|
+
export type FilterGetter<T> = (row: T) => string | string[] | null | undefined;
|
|
62
|
+
/**
|
|
63
|
+
* The buckets one row belongs to for one dimension.
|
|
64
|
+
*
|
|
65
|
+
* Always an array, so the caller never has to branch on which shape the surface
|
|
66
|
+
* happened to hand over.
|
|
67
|
+
*/
|
|
68
|
+
export declare function filterValues<T>(getter: FilterGetter<T>, row: T): string[];
|
|
69
|
+
/** How many rows sit in each bucket. Overlapping where a row has several. */
|
|
70
|
+
export declare function countByValue<T>(rows: readonly T[], getter: FilterGetter<T>): Record<string, number>;
|
|
71
|
+
/**
|
|
72
|
+
* Does a row survive one dimension's active selection?
|
|
73
|
+
*
|
|
74
|
+
* `active` may be one value or SEVERAL, because the filter panel that replaced
|
|
75
|
+
* the old cascading menu lets a reader tick more than one option in a facet.
|
|
76
|
+
* Several means ANY of them — "Active or Draft", not "Active and Draft", which
|
|
77
|
+
* is the only reading that can ever match anything: a row has one status.
|
|
78
|
+
*
|
|
79
|
+
* Not filtering is `null`, `undefined` or an EMPTY array, and none of those is
|
|
80
|
+
* the same as filtering on nothing. "All" is the absence of a choice, and a row
|
|
81
|
+
* whose value happens to be `''` must not be the only thing that survives it.
|
|
82
|
+
*/
|
|
83
|
+
export declare function matchesFilter<T>(getter: FilterGetter<T>, row: T, active: string | readonly string[] | null | undefined): boolean;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export function filterValues(getter, row) {
|
|
2
|
+
const raw = getter(row);
|
|
3
|
+
if (Array.isArray(raw)) {
|
|
4
|
+
return raw.map((v) => String(v ?? "")).filter((v) => v !== "");
|
|
5
|
+
}
|
|
6
|
+
return [String(raw ?? "")];
|
|
7
|
+
}
|
|
8
|
+
export function countByValue(rows, getter) {
|
|
9
|
+
const counts = {};
|
|
10
|
+
for (const row of rows) {
|
|
11
|
+
for (const value of filterValues(getter, row)) {
|
|
12
|
+
counts[value] = (counts[value] ?? 0) + 1;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
return counts;
|
|
16
|
+
}
|
|
17
|
+
export function matchesFilter(getter, row, active) {
|
|
18
|
+
if (active == null) return true;
|
|
19
|
+
const wanted = typeof active === "string" ? [active] : active;
|
|
20
|
+
if (!wanted.length) return true;
|
|
21
|
+
const values = filterValues(getter, row);
|
|
22
|
+
return wanted.some((v) => values.includes(v));
|
|
23
|
+
}
|
|
@@ -3,13 +3,18 @@ import type { Ref } from 'vue';
|
|
|
3
3
|
export type TranslateNamed = Record<string, unknown>;
|
|
4
4
|
/**
|
|
5
5
|
* The subset of vue-i18n's `t` the packages are allowed to rely on:
|
|
6
|
-
* `t(key)`, `t(key, named)`, `t(key, plural)`, `t(key,
|
|
6
|
+
* `t(key)`, `t(key, named)`, `t(key, plural)`, `t(key, named, plural)`.
|
|
7
|
+
*
|
|
8
|
+
* The last one is the order vue-i18n takes for a plural message that also has
|
|
9
|
+
* placeholders. Its `(key, plural, third)` overload reads the third argument as
|
|
10
|
+
* `TranslateOptions`, so a bare named bag passed there interpolates nothing —
|
|
11
|
+
* hence only the named-first form is offered here.
|
|
7
12
|
*/
|
|
8
13
|
export interface Translate {
|
|
9
14
|
(key: string): string;
|
|
10
15
|
(key: string, named: TranslateNamed): string;
|
|
11
16
|
(key: string, plural: number): string;
|
|
12
|
-
(key: string,
|
|
17
|
+
(key: string, named: TranslateNamed, plural: number): string;
|
|
13
18
|
}
|
|
14
19
|
export interface Translator {
|
|
15
20
|
t: Translate;
|
package/dist/types.d.mts
CHANGED
|
@@ -16,6 +16,8 @@ export { type TalkbackChannel, type TalkbackChannelOptions, type TalkbackMaybeRe
|
|
|
16
16
|
|
|
17
17
|
export { type MediaLibraryClient } from '../dist/runtime/composables/useMediaLibrary.js'
|
|
18
18
|
|
|
19
|
+
export { type FloatingPanelDock, type FloatingPanelMode, type FloatingPanelOptions } from '../dist/runtime/composables/useFloatingPanel.js'
|
|
20
|
+
|
|
19
21
|
export { type IsoOption } from '../dist/runtime/lib/isoLists.js'
|
|
20
22
|
|
|
21
23
|
export { type Translate, type TranslateNamed, type Translator } from '../dist/runtime/lib/i18nBridge.js'
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@revenexx/studio-shared",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.2",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Shared revenexx Studio shell primitives (PageHeader, SidebarNav, StateCard, DataTable, …) + the platform provider contract (usePlatformAuth/usePlatformTenant). A Nuxt module — add it to `modules`.",
|
|
6
6
|
"main": "./dist/module.mjs",
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
"@nuxt/kit": "^4.0.0"
|
|
29
29
|
},
|
|
30
30
|
"peerDependencies": {
|
|
31
|
-
"@revenexx/studio": "
|
|
31
|
+
"@revenexx/studio": "^0.2.3",
|
|
32
32
|
"@revenexx/talkback-js": "^0.3.0",
|
|
33
33
|
"@solar-icons/nuxt": "^1.2.0",
|
|
34
34
|
"centrifuge": ">=5.2.0",
|