mvc-kit 2.7.1 → 2.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/README.md +47 -1
- package/agent-config/claude-code/skills/guide/SKILL.md +1 -0
- package/agent-config/claude-code/skills/guide/anti-patterns.md +3 -3
- package/agent-config/claude-code/skills/guide/api-reference.md +146 -2
- package/agent-config/claude-code/skills/guide/patterns.md +120 -0
- package/agent-config/claude-code/skills/scaffold/templates/model.md +38 -1
- package/agent-config/copilot/copilot-instructions.md +54 -1
- package/agent-config/cursor/cursorrules +54 -1
- package/dist/Collection.cjs +69 -17
- package/dist/Collection.cjs.map +1 -1
- package/dist/Collection.d.ts.map +1 -1
- package/dist/Collection.js +69 -17
- package/dist/Collection.js.map +1 -1
- package/dist/Feed.cjs +86 -0
- package/dist/Feed.cjs.map +1 -0
- package/dist/Feed.d.ts +46 -0
- package/dist/Feed.d.ts.map +1 -0
- package/dist/Feed.js +86 -0
- package/dist/Feed.js.map +1 -0
- package/dist/Model.cjs +22 -4
- package/dist/Model.cjs.map +1 -1
- package/dist/Model.d.ts +2 -0
- package/dist/Model.d.ts.map +1 -1
- package/dist/Model.js +22 -4
- package/dist/Model.js.map +1 -1
- package/dist/Pagination.cjs +84 -0
- package/dist/Pagination.cjs.map +1 -0
- package/dist/Pagination.d.ts +39 -0
- package/dist/Pagination.d.ts.map +1 -0
- package/dist/Pagination.js +84 -0
- package/dist/Pagination.js.map +1 -0
- package/dist/PersistentCollection.cjs +16 -15
- package/dist/PersistentCollection.cjs.map +1 -1
- package/dist/PersistentCollection.d.ts +7 -1
- package/dist/PersistentCollection.d.ts.map +1 -1
- package/dist/PersistentCollection.js +16 -15
- package/dist/PersistentCollection.js.map +1 -1
- package/dist/Resource.cjs +23 -156
- package/dist/Resource.cjs.map +1 -1
- package/dist/Resource.d.ts +3 -2
- package/dist/Resource.d.ts.map +1 -1
- package/dist/Resource.js +23 -156
- package/dist/Resource.js.map +1 -1
- package/dist/Selection.cjs +99 -0
- package/dist/Selection.cjs.map +1 -0
- package/dist/Selection.d.ts +36 -0
- package/dist/Selection.d.ts.map +1 -0
- package/dist/Selection.js +99 -0
- package/dist/Selection.js.map +1 -0
- package/dist/Sorting.cjs +114 -0
- package/dist/Sorting.cjs.map +1 -0
- package/dist/Sorting.d.ts +43 -0
- package/dist/Sorting.d.ts.map +1 -0
- package/dist/Sorting.js +114 -0
- package/dist/Sorting.js.map +1 -0
- package/dist/ViewModel.cjs +177 -227
- package/dist/ViewModel.cjs.map +1 -1
- package/dist/ViewModel.d.ts +9 -12
- package/dist/ViewModel.d.ts.map +1 -1
- package/dist/ViewModel.js +177 -227
- package/dist/ViewModel.js.map +1 -1
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/mvc-kit.cjs +8 -0
- package/dist/mvc-kit.cjs.map +1 -1
- package/dist/mvc-kit.js +8 -0
- package/dist/mvc-kit.js.map +1 -1
- package/dist/react/components/CardList.cjs +42 -0
- package/dist/react/components/CardList.cjs.map +1 -0
- package/dist/react/components/CardList.d.ts +22 -0
- package/dist/react/components/CardList.d.ts.map +1 -0
- package/dist/react/components/CardList.js +42 -0
- package/dist/react/components/CardList.js.map +1 -0
- package/dist/react/components/DataTable.cjs +179 -0
- package/dist/react/components/DataTable.cjs.map +1 -0
- package/dist/react/components/DataTable.d.ts +30 -0
- package/dist/react/components/DataTable.d.ts.map +1 -0
- package/dist/react/components/DataTable.js +179 -0
- package/dist/react/components/DataTable.js.map +1 -0
- package/dist/react/components/InfiniteScroll.cjs +44 -0
- package/dist/react/components/InfiniteScroll.cjs.map +1 -0
- package/dist/react/components/InfiniteScroll.d.ts +21 -0
- package/dist/react/components/InfiniteScroll.d.ts.map +1 -0
- package/dist/react/components/InfiniteScroll.js +44 -0
- package/dist/react/components/InfiniteScroll.js.map +1 -0
- package/dist/react/components/types.cjs +15 -0
- package/dist/react/components/types.cjs.map +1 -0
- package/dist/react/components/types.d.ts +71 -0
- package/dist/react/components/types.d.ts.map +1 -0
- package/dist/react/components/types.js +15 -0
- package/dist/react/components/types.js.map +1 -0
- package/dist/react/index.d.ts +8 -1
- package/dist/react/index.d.ts.map +1 -1
- package/dist/react/use-instance.cjs +31 -21
- package/dist/react/use-instance.cjs.map +1 -1
- package/dist/react/use-instance.d.ts +1 -1
- package/dist/react/use-instance.d.ts.map +1 -1
- package/dist/react/use-instance.js +32 -22
- package/dist/react/use-instance.js.map +1 -1
- package/dist/react/use-model.cjs +29 -2
- package/dist/react/use-model.cjs.map +1 -1
- package/dist/react/use-model.d.ts +9 -0
- package/dist/react/use-model.d.ts.map +1 -1
- package/dist/react/use-model.js +30 -3
- package/dist/react/use-model.js.map +1 -1
- package/dist/react-native/NativeCollection.cjs +3 -0
- package/dist/react-native/NativeCollection.cjs.map +1 -1
- package/dist/react-native/NativeCollection.d.ts +3 -0
- package/dist/react-native/NativeCollection.d.ts.map +1 -1
- package/dist/react-native/NativeCollection.js +3 -0
- package/dist/react-native/NativeCollection.js.map +1 -1
- package/dist/react.cjs +7 -0
- package/dist/react.cjs.map +1 -1
- package/dist/react.js +8 -1
- package/dist/react.js.map +1 -1
- package/dist/walkPrototypeChain.cjs.map +1 -1
- package/dist/walkPrototypeChain.d.ts +2 -2
- package/dist/walkPrototypeChain.js.map +1 -1
- package/dist/web/idb.cjs.map +1 -1
- package/dist/web/idb.d.ts +18 -0
- package/dist/web/idb.d.ts.map +1 -1
- package/dist/web/idb.js.map +1 -1
- package/dist/wrapAsyncMethods.cjs +159 -0
- package/dist/wrapAsyncMethods.cjs.map +1 -0
- package/dist/wrapAsyncMethods.d.ts +37 -0
- package/dist/wrapAsyncMethods.d.ts.map +1 -0
- package/dist/wrapAsyncMethods.js +159 -0
- package/dist/wrapAsyncMethods.js.map +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
3
|
+
class Selection {
|
|
4
|
+
_selected = /* @__PURE__ */ new Set();
|
|
5
|
+
_listeners = /* @__PURE__ */ new Set();
|
|
6
|
+
_readonlyView = this._selected;
|
|
7
|
+
// ── Readable state ──
|
|
8
|
+
/** Read-only view of currently selected keys. */
|
|
9
|
+
get selected() {
|
|
10
|
+
return this._readonlyView;
|
|
11
|
+
}
|
|
12
|
+
/** Number of currently selected items. */
|
|
13
|
+
get count() {
|
|
14
|
+
return this._selected.size;
|
|
15
|
+
}
|
|
16
|
+
/** Whether any items are currently selected. */
|
|
17
|
+
get hasSelection() {
|
|
18
|
+
return this._selected.size > 0;
|
|
19
|
+
}
|
|
20
|
+
// ── Query ──
|
|
21
|
+
/** Check whether a specific key is selected. */
|
|
22
|
+
isSelected(key) {
|
|
23
|
+
return this._selected.has(key);
|
|
24
|
+
}
|
|
25
|
+
// ── Actions ──
|
|
26
|
+
/** Toggle a key's selection state (select if unselected, deselect if selected). */
|
|
27
|
+
toggle(key) {
|
|
28
|
+
if (this._selected.has(key)) {
|
|
29
|
+
this._selected.delete(key);
|
|
30
|
+
} else {
|
|
31
|
+
this._selected.add(key);
|
|
32
|
+
}
|
|
33
|
+
this._publish();
|
|
34
|
+
}
|
|
35
|
+
/** Add one or more keys to the selection. */
|
|
36
|
+
select(...keys) {
|
|
37
|
+
let changed = false;
|
|
38
|
+
for (const key of keys) {
|
|
39
|
+
if (!this._selected.has(key)) {
|
|
40
|
+
this._selected.add(key);
|
|
41
|
+
changed = true;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
if (changed) this._publish();
|
|
45
|
+
}
|
|
46
|
+
/** Remove one or more keys from the selection. */
|
|
47
|
+
deselect(...keys) {
|
|
48
|
+
let changed = false;
|
|
49
|
+
for (const key of keys) {
|
|
50
|
+
if (this._selected.has(key)) {
|
|
51
|
+
this._selected.delete(key);
|
|
52
|
+
changed = true;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
if (changed) this._publish();
|
|
56
|
+
}
|
|
57
|
+
/** If all selected → deselect all, else select all. */
|
|
58
|
+
toggleAll(allKeys) {
|
|
59
|
+
const allSelected = allKeys.length > 0 && allKeys.every((k) => this._selected.has(k));
|
|
60
|
+
if (allSelected) {
|
|
61
|
+
this._selected.clear();
|
|
62
|
+
} else {
|
|
63
|
+
for (const key of allKeys) this._selected.add(key);
|
|
64
|
+
}
|
|
65
|
+
this._publish();
|
|
66
|
+
}
|
|
67
|
+
/** Replace the entire selection atomically. Single notification. */
|
|
68
|
+
set(...keys) {
|
|
69
|
+
if (keys.length === this._selected.size && keys.every((k) => this._selected.has(k))) return;
|
|
70
|
+
this._selected.clear();
|
|
71
|
+
for (const key of keys) this._selected.add(key);
|
|
72
|
+
this._publish();
|
|
73
|
+
}
|
|
74
|
+
/** Remove all items from the selection. */
|
|
75
|
+
clear() {
|
|
76
|
+
if (this._selected.size === 0) return;
|
|
77
|
+
this._selected.clear();
|
|
78
|
+
this._publish();
|
|
79
|
+
}
|
|
80
|
+
// ── Utility ──
|
|
81
|
+
/** Filter an array to only items whose key is in the selection. */
|
|
82
|
+
selectedFrom(items, keyOf) {
|
|
83
|
+
return items.filter((item) => this._selected.has(keyOf(item)));
|
|
84
|
+
}
|
|
85
|
+
// ── Subscribable interface ──
|
|
86
|
+
/** Subscribe to selection changes. Returns an unsubscribe function. */
|
|
87
|
+
subscribe(cb) {
|
|
88
|
+
this._listeners.add(cb);
|
|
89
|
+
return () => {
|
|
90
|
+
this._listeners.delete(cb);
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
_publish() {
|
|
94
|
+
this._readonlyView = new Set(this._selected);
|
|
95
|
+
for (const cb of this._listeners) cb();
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
exports.Selection = Selection;
|
|
99
|
+
//# sourceMappingURL=Selection.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Selection.cjs","sources":["../src/Selection.ts"],"sourcesContent":["/**\n * Key-based selection set with toggle and select-all support.\n * Tracks which items are selected by their key (id).\n * Subscribable — auto-tracked when used as a ViewModel property.\n */\nexport class Selection<K extends string | number = string | number> {\n private _selected: Set<K> = new Set();\n private _listeners = new Set<() => void>();\n private _readonlyView: ReadonlySet<K> = this._selected;\n\n // ── Readable state ──\n\n /** Read-only view of currently selected keys. */\n get selected(): ReadonlySet<K> {\n return this._readonlyView;\n }\n\n /** Number of currently selected items. */\n get count(): number {\n return this._selected.size;\n }\n\n /** Whether any items are currently selected. */\n get hasSelection(): boolean {\n return this._selected.size > 0;\n }\n\n // ── Query ──\n\n /** Check whether a specific key is selected. */\n isSelected(key: K): boolean {\n return this._selected.has(key);\n }\n\n // ── Actions ──\n\n /** Toggle a key's selection state (select if unselected, deselect if selected). */\n toggle(key: K): void {\n if (this._selected.has(key)) {\n this._selected.delete(key);\n } else {\n this._selected.add(key);\n }\n this._publish();\n }\n\n /** Add one or more keys to the selection. */\n select(...keys: K[]): void {\n let changed = false;\n for (const key of keys) {\n if (!this._selected.has(key)) {\n this._selected.add(key);\n changed = true;\n }\n }\n if (changed) this._publish();\n }\n\n /** Remove one or more keys from the selection. */\n deselect(...keys: K[]): void {\n let changed = false;\n for (const key of keys) {\n if (this._selected.has(key)) {\n this._selected.delete(key);\n changed = true;\n }\n }\n if (changed) this._publish();\n }\n\n /** If all selected → deselect all, else select all. */\n toggleAll(allKeys: K[]): void {\n const allSelected = allKeys.length > 0 && allKeys.every(k => this._selected.has(k));\n if (allSelected) {\n this._selected.clear();\n } else {\n for (const key of allKeys) this._selected.add(key);\n }\n this._publish();\n }\n\n /** Replace the entire selection atomically. Single notification. */\n set(...keys: K[]): void {\n // Check if anything actually changed\n if (keys.length === this._selected.size && keys.every(k => this._selected.has(k))) return;\n this._selected.clear();\n for (const key of keys) this._selected.add(key);\n this._publish();\n }\n\n /** Remove all items from the selection. */\n clear(): void {\n if (this._selected.size === 0) return;\n this._selected.clear();\n this._publish();\n }\n\n // ── Utility ──\n\n /** Filter an array to only items whose key is in the selection. */\n selectedFrom<T>(items: T[], keyOf: (item: T) => K): T[] {\n return items.filter(item => this._selected.has(keyOf(item)));\n }\n\n // ── Subscribable interface ──\n\n /** Subscribe to selection changes. Returns an unsubscribe function. */\n subscribe(cb: () => void): () => void {\n this._listeners.add(cb);\n return () => { this._listeners.delete(cb); };\n }\n\n private _publish(): void {\n // Replace readonlyView so reference equality changes (needed for React)\n this._readonlyView = new Set(this._selected);\n for (const cb of this._listeners) cb();\n }\n}\n"],"names":[],"mappings":";;AAKO,MAAM,UAAuD;AAAA,EAC1D,gCAAwB,IAAA;AAAA,EACxB,iCAAiB,IAAA;AAAA,EACjB,gBAAgC,KAAK;AAAA;AAAA;AAAA,EAK7C,IAAI,WAA2B;AAC7B,WAAO,KAAK;AAAA,EACd;AAAA;AAAA,EAGA,IAAI,QAAgB;AAClB,WAAO,KAAK,UAAU;AAAA,EACxB;AAAA;AAAA,EAGA,IAAI,eAAwB;AAC1B,WAAO,KAAK,UAAU,OAAO;AAAA,EAC/B;AAAA;AAAA;AAAA,EAKA,WAAW,KAAiB;AAC1B,WAAO,KAAK,UAAU,IAAI,GAAG;AAAA,EAC/B;AAAA;AAAA;AAAA,EAKA,OAAO,KAAc;AACnB,QAAI,KAAK,UAAU,IAAI,GAAG,GAAG;AAC3B,WAAK,UAAU,OAAO,GAAG;AAAA,IAC3B,OAAO;AACL,WAAK,UAAU,IAAI,GAAG;AAAA,IACxB;AACA,SAAK,SAAA;AAAA,EACP;AAAA;AAAA,EAGA,UAAU,MAAiB;AACzB,QAAI,UAAU;AACd,eAAW,OAAO,MAAM;AACtB,UAAI,CAAC,KAAK,UAAU,IAAI,GAAG,GAAG;AAC5B,aAAK,UAAU,IAAI,GAAG;AACtB,kBAAU;AAAA,MACZ;AAAA,IACF;AACA,QAAI,cAAc,SAAA;AAAA,EACpB;AAAA;AAAA,EAGA,YAAY,MAAiB;AAC3B,QAAI,UAAU;AACd,eAAW,OAAO,MAAM;AACtB,UAAI,KAAK,UAAU,IAAI,GAAG,GAAG;AAC3B,aAAK,UAAU,OAAO,GAAG;AACzB,kBAAU;AAAA,MACZ;AAAA,IACF;AACA,QAAI,cAAc,SAAA;AAAA,EACpB;AAAA;AAAA,EAGA,UAAU,SAAoB;AAC5B,UAAM,cAAc,QAAQ,SAAS,KAAK,QAAQ,MAAM,CAAA,MAAK,KAAK,UAAU,IAAI,CAAC,CAAC;AAClF,QAAI,aAAa;AACf,WAAK,UAAU,MAAA;AAAA,IACjB,OAAO;AACL,iBAAW,OAAO,QAAS,MAAK,UAAU,IAAI,GAAG;AAAA,IACnD;AACA,SAAK,SAAA;AAAA,EACP;AAAA;AAAA,EAGA,OAAO,MAAiB;AAEtB,QAAI,KAAK,WAAW,KAAK,UAAU,QAAQ,KAAK,MAAM,CAAA,MAAK,KAAK,UAAU,IAAI,CAAC,CAAC,EAAG;AACnF,SAAK,UAAU,MAAA;AACf,eAAW,OAAO,KAAM,MAAK,UAAU,IAAI,GAAG;AAC9C,SAAK,SAAA;AAAA,EACP;AAAA;AAAA,EAGA,QAAc;AACZ,QAAI,KAAK,UAAU,SAAS,EAAG;AAC/B,SAAK,UAAU,MAAA;AACf,SAAK,SAAA;AAAA,EACP;AAAA;AAAA;AAAA,EAKA,aAAgB,OAAY,OAA4B;AACtD,WAAO,MAAM,OAAO,CAAA,SAAQ,KAAK,UAAU,IAAI,MAAM,IAAI,CAAC,CAAC;AAAA,EAC7D;AAAA;AAAA;AAAA,EAKA,UAAU,IAA4B;AACpC,SAAK,WAAW,IAAI,EAAE;AACtB,WAAO,MAAM;AAAE,WAAK,WAAW,OAAO,EAAE;AAAA,IAAG;AAAA,EAC7C;AAAA,EAEQ,WAAiB;AAEvB,SAAK,gBAAgB,IAAI,IAAI,KAAK,SAAS;AAC3C,eAAW,MAAM,KAAK,WAAY,IAAA;AAAA,EACpC;AACF;;"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Key-based selection set with toggle and select-all support.
|
|
3
|
+
* Tracks which items are selected by their key (id).
|
|
4
|
+
* Subscribable — auto-tracked when used as a ViewModel property.
|
|
5
|
+
*/
|
|
6
|
+
export declare class Selection<K extends string | number = string | number> {
|
|
7
|
+
private _selected;
|
|
8
|
+
private _listeners;
|
|
9
|
+
private _readonlyView;
|
|
10
|
+
/** Read-only view of currently selected keys. */
|
|
11
|
+
get selected(): ReadonlySet<K>;
|
|
12
|
+
/** Number of currently selected items. */
|
|
13
|
+
get count(): number;
|
|
14
|
+
/** Whether any items are currently selected. */
|
|
15
|
+
get hasSelection(): boolean;
|
|
16
|
+
/** Check whether a specific key is selected. */
|
|
17
|
+
isSelected(key: K): boolean;
|
|
18
|
+
/** Toggle a key's selection state (select if unselected, deselect if selected). */
|
|
19
|
+
toggle(key: K): void;
|
|
20
|
+
/** Add one or more keys to the selection. */
|
|
21
|
+
select(...keys: K[]): void;
|
|
22
|
+
/** Remove one or more keys from the selection. */
|
|
23
|
+
deselect(...keys: K[]): void;
|
|
24
|
+
/** If all selected → deselect all, else select all. */
|
|
25
|
+
toggleAll(allKeys: K[]): void;
|
|
26
|
+
/** Replace the entire selection atomically. Single notification. */
|
|
27
|
+
set(...keys: K[]): void;
|
|
28
|
+
/** Remove all items from the selection. */
|
|
29
|
+
clear(): void;
|
|
30
|
+
/** Filter an array to only items whose key is in the selection. */
|
|
31
|
+
selectedFrom<T>(items: T[], keyOf: (item: T) => K): T[];
|
|
32
|
+
/** Subscribe to selection changes. Returns an unsubscribe function. */
|
|
33
|
+
subscribe(cb: () => void): () => void;
|
|
34
|
+
private _publish;
|
|
35
|
+
}
|
|
36
|
+
//# sourceMappingURL=Selection.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Selection.d.ts","sourceRoot":"","sources":["../src/Selection.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,qBAAa,SAAS,CAAC,CAAC,SAAS,MAAM,GAAG,MAAM,GAAG,MAAM,GAAG,MAAM;IAChE,OAAO,CAAC,SAAS,CAAqB;IACtC,OAAO,CAAC,UAAU,CAAyB;IAC3C,OAAO,CAAC,aAAa,CAAkC;IAIvD,iDAAiD;IACjD,IAAI,QAAQ,IAAI,WAAW,CAAC,CAAC,CAAC,CAE7B;IAED,0CAA0C;IAC1C,IAAI,KAAK,IAAI,MAAM,CAElB;IAED,gDAAgD;IAChD,IAAI,YAAY,IAAI,OAAO,CAE1B;IAID,gDAAgD;IAChD,UAAU,CAAC,GAAG,EAAE,CAAC,GAAG,OAAO;IAM3B,mFAAmF;IACnF,MAAM,CAAC,GAAG,EAAE,CAAC,GAAG,IAAI;IASpB,6CAA6C;IAC7C,MAAM,CAAC,GAAG,IAAI,EAAE,CAAC,EAAE,GAAG,IAAI;IAW1B,kDAAkD;IAClD,QAAQ,CAAC,GAAG,IAAI,EAAE,CAAC,EAAE,GAAG,IAAI;IAW5B,uDAAuD;IACvD,SAAS,CAAC,OAAO,EAAE,CAAC,EAAE,GAAG,IAAI;IAU7B,oEAAoE;IACpE,GAAG,CAAC,GAAG,IAAI,EAAE,CAAC,EAAE,GAAG,IAAI;IAQvB,2CAA2C;IAC3C,KAAK,IAAI,IAAI;IAQb,mEAAmE;IACnE,YAAY,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,EAAE,KAAK,EAAE,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,EAAE;IAMvD,uEAAuE;IACvE,SAAS,CAAC,EAAE,EAAE,MAAM,IAAI,GAAG,MAAM,IAAI;IAKrC,OAAO,CAAC,QAAQ;CAKjB"}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
class Selection {
|
|
2
|
+
_selected = /* @__PURE__ */ new Set();
|
|
3
|
+
_listeners = /* @__PURE__ */ new Set();
|
|
4
|
+
_readonlyView = this._selected;
|
|
5
|
+
// ── Readable state ──
|
|
6
|
+
/** Read-only view of currently selected keys. */
|
|
7
|
+
get selected() {
|
|
8
|
+
return this._readonlyView;
|
|
9
|
+
}
|
|
10
|
+
/** Number of currently selected items. */
|
|
11
|
+
get count() {
|
|
12
|
+
return this._selected.size;
|
|
13
|
+
}
|
|
14
|
+
/** Whether any items are currently selected. */
|
|
15
|
+
get hasSelection() {
|
|
16
|
+
return this._selected.size > 0;
|
|
17
|
+
}
|
|
18
|
+
// ── Query ──
|
|
19
|
+
/** Check whether a specific key is selected. */
|
|
20
|
+
isSelected(key) {
|
|
21
|
+
return this._selected.has(key);
|
|
22
|
+
}
|
|
23
|
+
// ── Actions ──
|
|
24
|
+
/** Toggle a key's selection state (select if unselected, deselect if selected). */
|
|
25
|
+
toggle(key) {
|
|
26
|
+
if (this._selected.has(key)) {
|
|
27
|
+
this._selected.delete(key);
|
|
28
|
+
} else {
|
|
29
|
+
this._selected.add(key);
|
|
30
|
+
}
|
|
31
|
+
this._publish();
|
|
32
|
+
}
|
|
33
|
+
/** Add one or more keys to the selection. */
|
|
34
|
+
select(...keys) {
|
|
35
|
+
let changed = false;
|
|
36
|
+
for (const key of keys) {
|
|
37
|
+
if (!this._selected.has(key)) {
|
|
38
|
+
this._selected.add(key);
|
|
39
|
+
changed = true;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
if (changed) this._publish();
|
|
43
|
+
}
|
|
44
|
+
/** Remove one or more keys from the selection. */
|
|
45
|
+
deselect(...keys) {
|
|
46
|
+
let changed = false;
|
|
47
|
+
for (const key of keys) {
|
|
48
|
+
if (this._selected.has(key)) {
|
|
49
|
+
this._selected.delete(key);
|
|
50
|
+
changed = true;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
if (changed) this._publish();
|
|
54
|
+
}
|
|
55
|
+
/** If all selected → deselect all, else select all. */
|
|
56
|
+
toggleAll(allKeys) {
|
|
57
|
+
const allSelected = allKeys.length > 0 && allKeys.every((k) => this._selected.has(k));
|
|
58
|
+
if (allSelected) {
|
|
59
|
+
this._selected.clear();
|
|
60
|
+
} else {
|
|
61
|
+
for (const key of allKeys) this._selected.add(key);
|
|
62
|
+
}
|
|
63
|
+
this._publish();
|
|
64
|
+
}
|
|
65
|
+
/** Replace the entire selection atomically. Single notification. */
|
|
66
|
+
set(...keys) {
|
|
67
|
+
if (keys.length === this._selected.size && keys.every((k) => this._selected.has(k))) return;
|
|
68
|
+
this._selected.clear();
|
|
69
|
+
for (const key of keys) this._selected.add(key);
|
|
70
|
+
this._publish();
|
|
71
|
+
}
|
|
72
|
+
/** Remove all items from the selection. */
|
|
73
|
+
clear() {
|
|
74
|
+
if (this._selected.size === 0) return;
|
|
75
|
+
this._selected.clear();
|
|
76
|
+
this._publish();
|
|
77
|
+
}
|
|
78
|
+
// ── Utility ──
|
|
79
|
+
/** Filter an array to only items whose key is in the selection. */
|
|
80
|
+
selectedFrom(items, keyOf) {
|
|
81
|
+
return items.filter((item) => this._selected.has(keyOf(item)));
|
|
82
|
+
}
|
|
83
|
+
// ── Subscribable interface ──
|
|
84
|
+
/** Subscribe to selection changes. Returns an unsubscribe function. */
|
|
85
|
+
subscribe(cb) {
|
|
86
|
+
this._listeners.add(cb);
|
|
87
|
+
return () => {
|
|
88
|
+
this._listeners.delete(cb);
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
_publish() {
|
|
92
|
+
this._readonlyView = new Set(this._selected);
|
|
93
|
+
for (const cb of this._listeners) cb();
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
export {
|
|
97
|
+
Selection
|
|
98
|
+
};
|
|
99
|
+
//# sourceMappingURL=Selection.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Selection.js","sources":["../src/Selection.ts"],"sourcesContent":["/**\n * Key-based selection set with toggle and select-all support.\n * Tracks which items are selected by their key (id).\n * Subscribable — auto-tracked when used as a ViewModel property.\n */\nexport class Selection<K extends string | number = string | number> {\n private _selected: Set<K> = new Set();\n private _listeners = new Set<() => void>();\n private _readonlyView: ReadonlySet<K> = this._selected;\n\n // ── Readable state ──\n\n /** Read-only view of currently selected keys. */\n get selected(): ReadonlySet<K> {\n return this._readonlyView;\n }\n\n /** Number of currently selected items. */\n get count(): number {\n return this._selected.size;\n }\n\n /** Whether any items are currently selected. */\n get hasSelection(): boolean {\n return this._selected.size > 0;\n }\n\n // ── Query ──\n\n /** Check whether a specific key is selected. */\n isSelected(key: K): boolean {\n return this._selected.has(key);\n }\n\n // ── Actions ──\n\n /** Toggle a key's selection state (select if unselected, deselect if selected). */\n toggle(key: K): void {\n if (this._selected.has(key)) {\n this._selected.delete(key);\n } else {\n this._selected.add(key);\n }\n this._publish();\n }\n\n /** Add one or more keys to the selection. */\n select(...keys: K[]): void {\n let changed = false;\n for (const key of keys) {\n if (!this._selected.has(key)) {\n this._selected.add(key);\n changed = true;\n }\n }\n if (changed) this._publish();\n }\n\n /** Remove one or more keys from the selection. */\n deselect(...keys: K[]): void {\n let changed = false;\n for (const key of keys) {\n if (this._selected.has(key)) {\n this._selected.delete(key);\n changed = true;\n }\n }\n if (changed) this._publish();\n }\n\n /** If all selected → deselect all, else select all. */\n toggleAll(allKeys: K[]): void {\n const allSelected = allKeys.length > 0 && allKeys.every(k => this._selected.has(k));\n if (allSelected) {\n this._selected.clear();\n } else {\n for (const key of allKeys) this._selected.add(key);\n }\n this._publish();\n }\n\n /** Replace the entire selection atomically. Single notification. */\n set(...keys: K[]): void {\n // Check if anything actually changed\n if (keys.length === this._selected.size && keys.every(k => this._selected.has(k))) return;\n this._selected.clear();\n for (const key of keys) this._selected.add(key);\n this._publish();\n }\n\n /** Remove all items from the selection. */\n clear(): void {\n if (this._selected.size === 0) return;\n this._selected.clear();\n this._publish();\n }\n\n // ── Utility ──\n\n /** Filter an array to only items whose key is in the selection. */\n selectedFrom<T>(items: T[], keyOf: (item: T) => K): T[] {\n return items.filter(item => this._selected.has(keyOf(item)));\n }\n\n // ── Subscribable interface ──\n\n /** Subscribe to selection changes. Returns an unsubscribe function. */\n subscribe(cb: () => void): () => void {\n this._listeners.add(cb);\n return () => { this._listeners.delete(cb); };\n }\n\n private _publish(): void {\n // Replace readonlyView so reference equality changes (needed for React)\n this._readonlyView = new Set(this._selected);\n for (const cb of this._listeners) cb();\n }\n}\n"],"names":[],"mappings":"AAKO,MAAM,UAAuD;AAAA,EAC1D,gCAAwB,IAAA;AAAA,EACxB,iCAAiB,IAAA;AAAA,EACjB,gBAAgC,KAAK;AAAA;AAAA;AAAA,EAK7C,IAAI,WAA2B;AAC7B,WAAO,KAAK;AAAA,EACd;AAAA;AAAA,EAGA,IAAI,QAAgB;AAClB,WAAO,KAAK,UAAU;AAAA,EACxB;AAAA;AAAA,EAGA,IAAI,eAAwB;AAC1B,WAAO,KAAK,UAAU,OAAO;AAAA,EAC/B;AAAA;AAAA;AAAA,EAKA,WAAW,KAAiB;AAC1B,WAAO,KAAK,UAAU,IAAI,GAAG;AAAA,EAC/B;AAAA;AAAA;AAAA,EAKA,OAAO,KAAc;AACnB,QAAI,KAAK,UAAU,IAAI,GAAG,GAAG;AAC3B,WAAK,UAAU,OAAO,GAAG;AAAA,IAC3B,OAAO;AACL,WAAK,UAAU,IAAI,GAAG;AAAA,IACxB;AACA,SAAK,SAAA;AAAA,EACP;AAAA;AAAA,EAGA,UAAU,MAAiB;AACzB,QAAI,UAAU;AACd,eAAW,OAAO,MAAM;AACtB,UAAI,CAAC,KAAK,UAAU,IAAI,GAAG,GAAG;AAC5B,aAAK,UAAU,IAAI,GAAG;AACtB,kBAAU;AAAA,MACZ;AAAA,IACF;AACA,QAAI,cAAc,SAAA;AAAA,EACpB;AAAA;AAAA,EAGA,YAAY,MAAiB;AAC3B,QAAI,UAAU;AACd,eAAW,OAAO,MAAM;AACtB,UAAI,KAAK,UAAU,IAAI,GAAG,GAAG;AAC3B,aAAK,UAAU,OAAO,GAAG;AACzB,kBAAU;AAAA,MACZ;AAAA,IACF;AACA,QAAI,cAAc,SAAA;AAAA,EACpB;AAAA;AAAA,EAGA,UAAU,SAAoB;AAC5B,UAAM,cAAc,QAAQ,SAAS,KAAK,QAAQ,MAAM,CAAA,MAAK,KAAK,UAAU,IAAI,CAAC,CAAC;AAClF,QAAI,aAAa;AACf,WAAK,UAAU,MAAA;AAAA,IACjB,OAAO;AACL,iBAAW,OAAO,QAAS,MAAK,UAAU,IAAI,GAAG;AAAA,IACnD;AACA,SAAK,SAAA;AAAA,EACP;AAAA;AAAA,EAGA,OAAO,MAAiB;AAEtB,QAAI,KAAK,WAAW,KAAK,UAAU,QAAQ,KAAK,MAAM,CAAA,MAAK,KAAK,UAAU,IAAI,CAAC,CAAC,EAAG;AACnF,SAAK,UAAU,MAAA;AACf,eAAW,OAAO,KAAM,MAAK,UAAU,IAAI,GAAG;AAC9C,SAAK,SAAA;AAAA,EACP;AAAA;AAAA,EAGA,QAAc;AACZ,QAAI,KAAK,UAAU,SAAS,EAAG;AAC/B,SAAK,UAAU,MAAA;AACf,SAAK,SAAA;AAAA,EACP;AAAA;AAAA;AAAA,EAKA,aAAgB,OAAY,OAA4B;AACtD,WAAO,MAAM,OAAO,CAAA,SAAQ,KAAK,UAAU,IAAI,MAAM,IAAI,CAAC,CAAC;AAAA,EAC7D;AAAA;AAAA;AAAA,EAKA,UAAU,IAA4B;AACpC,SAAK,WAAW,IAAI,EAAE;AACtB,WAAO,MAAM;AAAE,WAAK,WAAW,OAAO,EAAE;AAAA,IAAG;AAAA,EAC7C;AAAA,EAEQ,WAAiB;AAEvB,SAAK,gBAAgB,IAAI,IAAI,KAAK,SAAS;AAC3C,eAAW,MAAM,KAAK,WAAY,IAAA;AAAA,EACpC;AACF;"}
|
package/dist/Sorting.cjs
ADDED
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
3
|
+
class Sorting {
|
|
4
|
+
_sorts;
|
|
5
|
+
_listeners = /* @__PURE__ */ new Set();
|
|
6
|
+
constructor(options) {
|
|
7
|
+
this._sorts = Object.freeze(options?.sorts?.map((s) => ({ ...s })) ?? []);
|
|
8
|
+
}
|
|
9
|
+
// ── Readable state ──
|
|
10
|
+
/** Current list of active sort descriptors, in priority order. */
|
|
11
|
+
get sorts() {
|
|
12
|
+
return this._sorts;
|
|
13
|
+
}
|
|
14
|
+
/** Primary sort key (first descriptor), or null when empty. */
|
|
15
|
+
get key() {
|
|
16
|
+
return this._sorts.length > 0 ? this._sorts[0].key : null;
|
|
17
|
+
}
|
|
18
|
+
/** Primary sort direction. Defaults to 'asc' when empty. */
|
|
19
|
+
get direction() {
|
|
20
|
+
return this._sorts.length > 0 ? this._sorts[0].direction : "asc";
|
|
21
|
+
}
|
|
22
|
+
// ── Query ──
|
|
23
|
+
/** Whether the given key is currently sorted. */
|
|
24
|
+
isSorted(key) {
|
|
25
|
+
return this._sorts.some((s) => s.key === key);
|
|
26
|
+
}
|
|
27
|
+
/** Returns the sort direction for a key, or null if not sorted. */
|
|
28
|
+
directionOf(key) {
|
|
29
|
+
const found = this._sorts.find((s) => s.key === key);
|
|
30
|
+
return found ? found.direction : null;
|
|
31
|
+
}
|
|
32
|
+
/** Returns the priority index of a sorted key, or -1 if not sorted. */
|
|
33
|
+
indexOf(key) {
|
|
34
|
+
return this._sorts.findIndex((s) => s.key === key);
|
|
35
|
+
}
|
|
36
|
+
// ── Actions ──
|
|
37
|
+
/** 3-click cycle: not sorted → asc → desc → removed. */
|
|
38
|
+
toggle(key) {
|
|
39
|
+
const idx = this.indexOf(key);
|
|
40
|
+
if (idx === -1) {
|
|
41
|
+
this._sorts = Object.freeze([...this._sorts, { key, direction: "asc" }]);
|
|
42
|
+
} else if (this._sorts[idx].direction === "asc") {
|
|
43
|
+
const next = this._sorts.map(
|
|
44
|
+
(s, i) => i === idx ? { key: s.key, direction: "desc" } : s
|
|
45
|
+
);
|
|
46
|
+
this._sorts = Object.freeze(next);
|
|
47
|
+
} else {
|
|
48
|
+
this._sorts = Object.freeze(this._sorts.filter((_, i) => i !== idx));
|
|
49
|
+
}
|
|
50
|
+
this._notify();
|
|
51
|
+
}
|
|
52
|
+
/** Replace all with a single sort. */
|
|
53
|
+
setSort(key, direction) {
|
|
54
|
+
this._sorts = Object.freeze([{ key, direction }]);
|
|
55
|
+
this._notify();
|
|
56
|
+
}
|
|
57
|
+
/** Replace all sorts. */
|
|
58
|
+
setSorts(sorts) {
|
|
59
|
+
this._sorts = Object.freeze(sorts.map((s) => ({ ...s })));
|
|
60
|
+
this._notify();
|
|
61
|
+
}
|
|
62
|
+
/** Clear all sort descriptors. */
|
|
63
|
+
reset() {
|
|
64
|
+
this._sorts = Object.freeze([]);
|
|
65
|
+
this._notify();
|
|
66
|
+
}
|
|
67
|
+
// ── Pipeline ──
|
|
68
|
+
/** Sort an array using the current descriptors. Returns a new sorted array. */
|
|
69
|
+
apply(items, compareFn) {
|
|
70
|
+
if (this._sorts.length === 0) return items;
|
|
71
|
+
const sorted = items.slice();
|
|
72
|
+
const sorts = this._sorts;
|
|
73
|
+
sorted.sort((a, b) => {
|
|
74
|
+
for (const { key, direction } of sorts) {
|
|
75
|
+
let cmp;
|
|
76
|
+
if (compareFn) {
|
|
77
|
+
cmp = compareFn(a, b, key, direction);
|
|
78
|
+
} else {
|
|
79
|
+
cmp = defaultCompare(a, b, key);
|
|
80
|
+
}
|
|
81
|
+
if (direction === "desc") cmp = -cmp;
|
|
82
|
+
if (cmp !== 0) return cmp;
|
|
83
|
+
}
|
|
84
|
+
return 0;
|
|
85
|
+
});
|
|
86
|
+
return sorted;
|
|
87
|
+
}
|
|
88
|
+
// ── Subscribable interface ──
|
|
89
|
+
/** Subscribe to sort state changes. Returns an unsubscribe function. */
|
|
90
|
+
subscribe(cb) {
|
|
91
|
+
this._listeners.add(cb);
|
|
92
|
+
return () => {
|
|
93
|
+
this._listeners.delete(cb);
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
_notify() {
|
|
97
|
+
for (const cb of this._listeners) cb();
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
function defaultCompare(a, b, key) {
|
|
101
|
+
const aVal = a[key];
|
|
102
|
+
const bVal = b[key];
|
|
103
|
+
if (aVal == null && bVal == null) return 0;
|
|
104
|
+
if (aVal == null) return -1;
|
|
105
|
+
if (bVal == null) return 1;
|
|
106
|
+
if (typeof aVal === "string" && typeof bVal === "string") {
|
|
107
|
+
return aVal.localeCompare(bVal);
|
|
108
|
+
}
|
|
109
|
+
if (aVal < bVal) return -1;
|
|
110
|
+
if (aVal > bVal) return 1;
|
|
111
|
+
return 0;
|
|
112
|
+
}
|
|
113
|
+
exports.Sorting = Sorting;
|
|
114
|
+
//# sourceMappingURL=Sorting.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Sorting.cjs","sources":["../src/Sorting.ts"],"sourcesContent":["/** Describes a single sort column with key and direction. */\nexport interface SortDescriptor {\n key: string;\n direction: 'asc' | 'desc';\n}\n\n/**\n * Multi-column sort state manager with a comparator pipeline.\n * Maintains an ordered list of sort descriptors and applies them to arrays.\n * Subscribable — auto-tracked when used as a ViewModel property.\n */\nexport class Sorting<T = any> {\n private _sorts: readonly SortDescriptor[];\n private _listeners = new Set<() => void>();\n\n constructor(options?: { sorts?: SortDescriptor[] }) {\n this._sorts = Object.freeze(options?.sorts?.map(s => ({ ...s })) ?? []);\n }\n\n // ── Readable state ──\n\n /** Current list of active sort descriptors, in priority order. */\n get sorts(): readonly SortDescriptor[] {\n return this._sorts;\n }\n\n /** Primary sort key (first descriptor), or null when empty. */\n get key(): string | null {\n return this._sorts.length > 0 ? this._sorts[0].key : null;\n }\n\n /** Primary sort direction. Defaults to 'asc' when empty. */\n get direction(): 'asc' | 'desc' {\n return this._sorts.length > 0 ? this._sorts[0].direction : 'asc';\n }\n\n // ── Query ──\n\n /** Whether the given key is currently sorted. */\n isSorted(key: string): boolean {\n return this._sorts.some(s => s.key === key);\n }\n\n /** Returns the sort direction for a key, or null if not sorted. */\n directionOf(key: string): 'asc' | 'desc' | null {\n const found = this._sorts.find(s => s.key === key);\n return found ? found.direction : null;\n }\n\n /** Returns the priority index of a sorted key, or -1 if not sorted. */\n indexOf(key: string): number {\n return this._sorts.findIndex(s => s.key === key);\n }\n\n // ── Actions ──\n\n /** 3-click cycle: not sorted → asc → desc → removed. */\n toggle(key: string): void {\n const idx = this.indexOf(key);\n if (idx === -1) {\n // Add as asc\n this._sorts = Object.freeze([...this._sorts, { key, direction: 'asc' as const }]);\n } else if (this._sorts[idx].direction === 'asc') {\n // Flip to desc\n const next = this._sorts.map((s, i) =>\n i === idx ? { key: s.key, direction: 'desc' as const } : s\n );\n this._sorts = Object.freeze(next);\n } else {\n // Remove\n this._sorts = Object.freeze(this._sorts.filter((_, i) => i !== idx));\n }\n this._notify();\n }\n\n /** Replace all with a single sort. */\n setSort(key: string, direction: 'asc' | 'desc'): void {\n this._sorts = Object.freeze([{ key, direction }]);\n this._notify();\n }\n\n /** Replace all sorts. */\n setSorts(sorts: SortDescriptor[]): void {\n this._sorts = Object.freeze(sorts.map(s => ({ ...s })));\n this._notify();\n }\n\n /** Clear all sort descriptors. */\n reset(): void {\n this._sorts = Object.freeze([]);\n this._notify();\n }\n\n // ── Pipeline ──\n\n /** Sort an array using the current descriptors. Returns a new sorted array. */\n apply(\n items: T[],\n compareFn?: (a: T, b: T, key: string, dir: 'asc' | 'desc') => number,\n ): T[] {\n if (this._sorts.length === 0) return items;\n const sorted = items.slice();\n const sorts = this._sorts;\n sorted.sort((a, b) => {\n for (const { key, direction } of sorts) {\n let cmp: number;\n if (compareFn) {\n cmp = compareFn(a, b, key, direction);\n } else {\n cmp = defaultCompare(a, b, key);\n }\n if (direction === 'desc') cmp = -cmp;\n if (cmp !== 0) return cmp;\n }\n return 0;\n });\n return sorted;\n }\n\n // ── Subscribable interface ──\n\n /** Subscribe to sort state changes. Returns an unsubscribe function. */\n subscribe(cb: () => void): () => void {\n this._listeners.add(cb);\n return () => { this._listeners.delete(cb); };\n }\n\n private _notify(): void {\n for (const cb of this._listeners) cb();\n }\n}\n\nfunction defaultCompare(a: any, b: any, key: string): number {\n const aVal = a[key];\n const bVal = b[key];\n if (aVal == null && bVal == null) return 0;\n if (aVal == null) return -1;\n if (bVal == null) return 1;\n if (typeof aVal === 'string' && typeof bVal === 'string') {\n return aVal.localeCompare(bVal);\n }\n if (aVal < bVal) return -1;\n if (aVal > bVal) return 1;\n return 0;\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/** Describes a single sort column with key and direction. */
|
|
2
|
+
export interface SortDescriptor {
|
|
3
|
+
key: string;
|
|
4
|
+
direction: 'asc' | 'desc';
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Multi-column sort state manager with a comparator pipeline.
|
|
8
|
+
* Maintains an ordered list of sort descriptors and applies them to arrays.
|
|
9
|
+
* Subscribable — auto-tracked when used as a ViewModel property.
|
|
10
|
+
*/
|
|
11
|
+
export declare class Sorting<T = any> {
|
|
12
|
+
private _sorts;
|
|
13
|
+
private _listeners;
|
|
14
|
+
constructor(options?: {
|
|
15
|
+
sorts?: SortDescriptor[];
|
|
16
|
+
});
|
|
17
|
+
/** Current list of active sort descriptors, in priority order. */
|
|
18
|
+
get sorts(): readonly SortDescriptor[];
|
|
19
|
+
/** Primary sort key (first descriptor), or null when empty. */
|
|
20
|
+
get key(): string | null;
|
|
21
|
+
/** Primary sort direction. Defaults to 'asc' when empty. */
|
|
22
|
+
get direction(): 'asc' | 'desc';
|
|
23
|
+
/** Whether the given key is currently sorted. */
|
|
24
|
+
isSorted(key: string): boolean;
|
|
25
|
+
/** Returns the sort direction for a key, or null if not sorted. */
|
|
26
|
+
directionOf(key: string): 'asc' | 'desc' | null;
|
|
27
|
+
/** Returns the priority index of a sorted key, or -1 if not sorted. */
|
|
28
|
+
indexOf(key: string): number;
|
|
29
|
+
/** 3-click cycle: not sorted → asc → desc → removed. */
|
|
30
|
+
toggle(key: string): void;
|
|
31
|
+
/** Replace all with a single sort. */
|
|
32
|
+
setSort(key: string, direction: 'asc' | 'desc'): void;
|
|
33
|
+
/** Replace all sorts. */
|
|
34
|
+
setSorts(sorts: SortDescriptor[]): void;
|
|
35
|
+
/** Clear all sort descriptors. */
|
|
36
|
+
reset(): void;
|
|
37
|
+
/** Sort an array using the current descriptors. Returns a new sorted array. */
|
|
38
|
+
apply(items: T[], compareFn?: (a: T, b: T, key: string, dir: 'asc' | 'desc') => number): T[];
|
|
39
|
+
/** Subscribe to sort state changes. Returns an unsubscribe function. */
|
|
40
|
+
subscribe(cb: () => void): () => void;
|
|
41
|
+
private _notify;
|
|
42
|
+
}
|
|
43
|
+
//# sourceMappingURL=Sorting.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Sorting.d.ts","sourceRoot":"","sources":["../src/Sorting.ts"],"names":[],"mappings":"AAAA,6DAA6D;AAC7D,MAAM,WAAW,cAAc;IAC7B,GAAG,EAAE,MAAM,CAAC;IACZ,SAAS,EAAE,KAAK,GAAG,MAAM,CAAC;CAC3B;AAED;;;;GAIG;AACH,qBAAa,OAAO,CAAC,CAAC,GAAG,GAAG;IAC1B,OAAO,CAAC,MAAM,CAA4B;IAC1C,OAAO,CAAC,UAAU,CAAyB;gBAE/B,OAAO,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,cAAc,EAAE,CAAA;KAAE;IAMlD,kEAAkE;IAClE,IAAI,KAAK,IAAI,SAAS,cAAc,EAAE,CAErC;IAED,+DAA+D;IAC/D,IAAI,GAAG,IAAI,MAAM,GAAG,IAAI,CAEvB;IAED,4DAA4D;IAC5D,IAAI,SAAS,IAAI,KAAK,GAAG,MAAM,CAE9B;IAID,iDAAiD;IACjD,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO;IAI9B,mEAAmE;IACnE,WAAW,CAAC,GAAG,EAAE,MAAM,GAAG,KAAK,GAAG,MAAM,GAAG,IAAI;IAK/C,uEAAuE;IACvE,OAAO,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM;IAM5B,wDAAwD;IACxD,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI;IAkBzB,sCAAsC;IACtC,OAAO,CAAC,GAAG,EAAE,MAAM,EAAE,SAAS,EAAE,KAAK,GAAG,MAAM,GAAG,IAAI;IAKrD,yBAAyB;IACzB,QAAQ,CAAC,KAAK,EAAE,cAAc,EAAE,GAAG,IAAI;IAKvC,kCAAkC;IAClC,KAAK,IAAI,IAAI;IAOb,+EAA+E;IAC/E,KAAK,CACH,KAAK,EAAE,CAAC,EAAE,EACV,SAAS,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,KAAK,GAAG,MAAM,KAAK,MAAM,GACnE,CAAC,EAAE;IAsBN,wEAAwE;IACxE,SAAS,CAAC,EAAE,EAAE,MAAM,IAAI,GAAG,MAAM,IAAI;IAKrC,OAAO,CAAC,OAAO;CAGhB"}
|
package/dist/Sorting.js
ADDED
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
class Sorting {
|
|
2
|
+
_sorts;
|
|
3
|
+
_listeners = /* @__PURE__ */ new Set();
|
|
4
|
+
constructor(options) {
|
|
5
|
+
this._sorts = Object.freeze(options?.sorts?.map((s) => ({ ...s })) ?? []);
|
|
6
|
+
}
|
|
7
|
+
// ── Readable state ──
|
|
8
|
+
/** Current list of active sort descriptors, in priority order. */
|
|
9
|
+
get sorts() {
|
|
10
|
+
return this._sorts;
|
|
11
|
+
}
|
|
12
|
+
/** Primary sort key (first descriptor), or null when empty. */
|
|
13
|
+
get key() {
|
|
14
|
+
return this._sorts.length > 0 ? this._sorts[0].key : null;
|
|
15
|
+
}
|
|
16
|
+
/** Primary sort direction. Defaults to 'asc' when empty. */
|
|
17
|
+
get direction() {
|
|
18
|
+
return this._sorts.length > 0 ? this._sorts[0].direction : "asc";
|
|
19
|
+
}
|
|
20
|
+
// ── Query ──
|
|
21
|
+
/** Whether the given key is currently sorted. */
|
|
22
|
+
isSorted(key) {
|
|
23
|
+
return this._sorts.some((s) => s.key === key);
|
|
24
|
+
}
|
|
25
|
+
/** Returns the sort direction for a key, or null if not sorted. */
|
|
26
|
+
directionOf(key) {
|
|
27
|
+
const found = this._sorts.find((s) => s.key === key);
|
|
28
|
+
return found ? found.direction : null;
|
|
29
|
+
}
|
|
30
|
+
/** Returns the priority index of a sorted key, or -1 if not sorted. */
|
|
31
|
+
indexOf(key) {
|
|
32
|
+
return this._sorts.findIndex((s) => s.key === key);
|
|
33
|
+
}
|
|
34
|
+
// ── Actions ──
|
|
35
|
+
/** 3-click cycle: not sorted → asc → desc → removed. */
|
|
36
|
+
toggle(key) {
|
|
37
|
+
const idx = this.indexOf(key);
|
|
38
|
+
if (idx === -1) {
|
|
39
|
+
this._sorts = Object.freeze([...this._sorts, { key, direction: "asc" }]);
|
|
40
|
+
} else if (this._sorts[idx].direction === "asc") {
|
|
41
|
+
const next = this._sorts.map(
|
|
42
|
+
(s, i) => i === idx ? { key: s.key, direction: "desc" } : s
|
|
43
|
+
);
|
|
44
|
+
this._sorts = Object.freeze(next);
|
|
45
|
+
} else {
|
|
46
|
+
this._sorts = Object.freeze(this._sorts.filter((_, i) => i !== idx));
|
|
47
|
+
}
|
|
48
|
+
this._notify();
|
|
49
|
+
}
|
|
50
|
+
/** Replace all with a single sort. */
|
|
51
|
+
setSort(key, direction) {
|
|
52
|
+
this._sorts = Object.freeze([{ key, direction }]);
|
|
53
|
+
this._notify();
|
|
54
|
+
}
|
|
55
|
+
/** Replace all sorts. */
|
|
56
|
+
setSorts(sorts) {
|
|
57
|
+
this._sorts = Object.freeze(sorts.map((s) => ({ ...s })));
|
|
58
|
+
this._notify();
|
|
59
|
+
}
|
|
60
|
+
/** Clear all sort descriptors. */
|
|
61
|
+
reset() {
|
|
62
|
+
this._sorts = Object.freeze([]);
|
|
63
|
+
this._notify();
|
|
64
|
+
}
|
|
65
|
+
// ── Pipeline ──
|
|
66
|
+
/** Sort an array using the current descriptors. Returns a new sorted array. */
|
|
67
|
+
apply(items, compareFn) {
|
|
68
|
+
if (this._sorts.length === 0) return items;
|
|
69
|
+
const sorted = items.slice();
|
|
70
|
+
const sorts = this._sorts;
|
|
71
|
+
sorted.sort((a, b) => {
|
|
72
|
+
for (const { key, direction } of sorts) {
|
|
73
|
+
let cmp;
|
|
74
|
+
if (compareFn) {
|
|
75
|
+
cmp = compareFn(a, b, key, direction);
|
|
76
|
+
} else {
|
|
77
|
+
cmp = defaultCompare(a, b, key);
|
|
78
|
+
}
|
|
79
|
+
if (direction === "desc") cmp = -cmp;
|
|
80
|
+
if (cmp !== 0) return cmp;
|
|
81
|
+
}
|
|
82
|
+
return 0;
|
|
83
|
+
});
|
|
84
|
+
return sorted;
|
|
85
|
+
}
|
|
86
|
+
// ── Subscribable interface ──
|
|
87
|
+
/** Subscribe to sort state changes. Returns an unsubscribe function. */
|
|
88
|
+
subscribe(cb) {
|
|
89
|
+
this._listeners.add(cb);
|
|
90
|
+
return () => {
|
|
91
|
+
this._listeners.delete(cb);
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
_notify() {
|
|
95
|
+
for (const cb of this._listeners) cb();
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
function defaultCompare(a, b, key) {
|
|
99
|
+
const aVal = a[key];
|
|
100
|
+
const bVal = b[key];
|
|
101
|
+
if (aVal == null && bVal == null) return 0;
|
|
102
|
+
if (aVal == null) return -1;
|
|
103
|
+
if (bVal == null) return 1;
|
|
104
|
+
if (typeof aVal === "string" && typeof bVal === "string") {
|
|
105
|
+
return aVal.localeCompare(bVal);
|
|
106
|
+
}
|
|
107
|
+
if (aVal < bVal) return -1;
|
|
108
|
+
if (aVal > bVal) return 1;
|
|
109
|
+
return 0;
|
|
110
|
+
}
|
|
111
|
+
export {
|
|
112
|
+
Sorting
|
|
113
|
+
};
|
|
114
|
+
//# sourceMappingURL=Sorting.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Sorting.js","sources":["../src/Sorting.ts"],"sourcesContent":["/** Describes a single sort column with key and direction. */\nexport interface SortDescriptor {\n key: string;\n direction: 'asc' | 'desc';\n}\n\n/**\n * Multi-column sort state manager with a comparator pipeline.\n * Maintains an ordered list of sort descriptors and applies them to arrays.\n * Subscribable — auto-tracked when used as a ViewModel property.\n */\nexport class Sorting<T = any> {\n private _sorts: readonly SortDescriptor[];\n private _listeners = new Set<() => void>();\n\n constructor(options?: { sorts?: SortDescriptor[] }) {\n this._sorts = Object.freeze(options?.sorts?.map(s => ({ ...s })) ?? []);\n }\n\n // ── Readable state ──\n\n /** Current list of active sort descriptors, in priority order. */\n get sorts(): readonly SortDescriptor[] {\n return this._sorts;\n }\n\n /** Primary sort key (first descriptor), or null when empty. */\n get key(): string | null {\n return this._sorts.length > 0 ? this._sorts[0].key : null;\n }\n\n /** Primary sort direction. Defaults to 'asc' when empty. */\n get direction(): 'asc' | 'desc' {\n return this._sorts.length > 0 ? this._sorts[0].direction : 'asc';\n }\n\n // ── Query ──\n\n /** Whether the given key is currently sorted. */\n isSorted(key: string): boolean {\n return this._sorts.some(s => s.key === key);\n }\n\n /** Returns the sort direction for a key, or null if not sorted. */\n directionOf(key: string): 'asc' | 'desc' | null {\n const found = this._sorts.find(s => s.key === key);\n return found ? found.direction : null;\n }\n\n /** Returns the priority index of a sorted key, or -1 if not sorted. */\n indexOf(key: string): number {\n return this._sorts.findIndex(s => s.key === key);\n }\n\n // ── Actions ──\n\n /** 3-click cycle: not sorted → asc → desc → removed. */\n toggle(key: string): void {\n const idx = this.indexOf(key);\n if (idx === -1) {\n // Add as asc\n this._sorts = Object.freeze([...this._sorts, { key, direction: 'asc' as const }]);\n } else if (this._sorts[idx].direction === 'asc') {\n // Flip to desc\n const next = this._sorts.map((s, i) =>\n i === idx ? { key: s.key, direction: 'desc' as const } : s\n );\n this._sorts = Object.freeze(next);\n } else {\n // Remove\n this._sorts = Object.freeze(this._sorts.filter((_, i) => i !== idx));\n }\n this._notify();\n }\n\n /** Replace all with a single sort. */\n setSort(key: string, direction: 'asc' | 'desc'): void {\n this._sorts = Object.freeze([{ key, direction }]);\n this._notify();\n }\n\n /** Replace all sorts. */\n setSorts(sorts: SortDescriptor[]): void {\n this._sorts = Object.freeze(sorts.map(s => ({ ...s })));\n this._notify();\n }\n\n /** Clear all sort descriptors. */\n reset(): void {\n this._sorts = Object.freeze([]);\n this._notify();\n }\n\n // ── Pipeline ──\n\n /** Sort an array using the current descriptors. Returns a new sorted array. */\n apply(\n items: T[],\n compareFn?: (a: T, b: T, key: string, dir: 'asc' | 'desc') => number,\n ): T[] {\n if (this._sorts.length === 0) return items;\n const sorted = items.slice();\n const sorts = this._sorts;\n sorted.sort((a, b) => {\n for (const { key, direction } of sorts) {\n let cmp: number;\n if (compareFn) {\n cmp = compareFn(a, b, key, direction);\n } else {\n cmp = defaultCompare(a, b, key);\n }\n if (direction === 'desc') cmp = -cmp;\n if (cmp !== 0) return cmp;\n }\n return 0;\n });\n return sorted;\n }\n\n // ── Subscribable interface ──\n\n /** Subscribe to sort state changes. Returns an unsubscribe function. */\n subscribe(cb: () => void): () => void {\n this._listeners.add(cb);\n return () => { this._listeners.delete(cb); };\n }\n\n private _notify(): void {\n for (const cb of this._listeners) cb();\n }\n}\n\nfunction defaultCompare(a: any, b: any, key: string): number {\n const aVal = a[key];\n const bVal = b[key];\n if (aVal == null && bVal == null) return 0;\n if (aVal == null) return -1;\n if (bVal == null) return 1;\n if (typeof aVal === 'string' && typeof bVal === 'string') {\n return aVal.localeCompare(bVal);\n }\n if (aVal < bVal) return -1;\n if (aVal > bVal) return 1;\n return 0;\n}\n"],"names":[],"mappings":"AAWO,MAAM,QAAiB;AAAA,EACpB;AAAA,EACA,iCAAiB,IAAA;AAAA,EAEzB,YAAY,SAAwC;AAClD,SAAK,SAAS,OAAO,OAAO,SAAS,OAAO,IAAI,CAAA,OAAM,EAAE,GAAG,EAAA,EAAI,KAAK,CAAA,CAAE;AAAA,EACxE;AAAA;AAAA;AAAA,EAKA,IAAI,QAAmC;AACrC,WAAO,KAAK;AAAA,EACd;AAAA;AAAA,EAGA,IAAI,MAAqB;AACvB,WAAO,KAAK,OAAO,SAAS,IAAI,KAAK,OAAO,CAAC,EAAE,MAAM;AAAA,EACvD;AAAA;AAAA,EAGA,IAAI,YAA4B;AAC9B,WAAO,KAAK,OAAO,SAAS,IAAI,KAAK,OAAO,CAAC,EAAE,YAAY;AAAA,EAC7D;AAAA;AAAA;AAAA,EAKA,SAAS,KAAsB;AAC7B,WAAO,KAAK,OAAO,KAAK,CAAA,MAAK,EAAE,QAAQ,GAAG;AAAA,EAC5C;AAAA;AAAA,EAGA,YAAY,KAAoC;AAC9C,UAAM,QAAQ,KAAK,OAAO,KAAK,CAAA,MAAK,EAAE,QAAQ,GAAG;AACjD,WAAO,QAAQ,MAAM,YAAY;AAAA,EACnC;AAAA;AAAA,EAGA,QAAQ,KAAqB;AAC3B,WAAO,KAAK,OAAO,UAAU,CAAA,MAAK,EAAE,QAAQ,GAAG;AAAA,EACjD;AAAA;AAAA;AAAA,EAKA,OAAO,KAAmB;AACxB,UAAM,MAAM,KAAK,QAAQ,GAAG;AAC5B,QAAI,QAAQ,IAAI;AAEd,WAAK,SAAS,OAAO,OAAO,CAAC,GAAG,KAAK,QAAQ,EAAE,KAAK,WAAW,MAAA,CAAgB,CAAC;AAAA,IAClF,WAAW,KAAK,OAAO,GAAG,EAAE,cAAc,OAAO;AAE/C,YAAM,OAAO,KAAK,OAAO;AAAA,QAAI,CAAC,GAAG,MAC/B,MAAM,MAAM,EAAE,KAAK,EAAE,KAAK,WAAW,WAAoB;AAAA,MAAA;AAE3D,WAAK,SAAS,OAAO,OAAO,IAAI;AAAA,IAClC,OAAO;AAEL,WAAK,SAAS,OAAO,OAAO,KAAK,OAAO,OAAO,CAAC,GAAG,MAAM,MAAM,GAAG,CAAC;AAAA,IACrE;AACA,SAAK,QAAA;AAAA,EACP;AAAA;AAAA,EAGA,QAAQ,KAAa,WAAiC;AACpD,SAAK,SAAS,OAAO,OAAO,CAAC,EAAE,KAAK,UAAA,CAAW,CAAC;AAChD,SAAK,QAAA;AAAA,EACP;AAAA;AAAA,EAGA,SAAS,OAA+B;AACtC,SAAK,SAAS,OAAO,OAAO,MAAM,IAAI,QAAM,EAAE,GAAG,EAAA,EAAI,CAAC;AACtD,SAAK,QAAA;AAAA,EACP;AAAA;AAAA,EAGA,QAAc;AACZ,SAAK,SAAS,OAAO,OAAO,CAAA,CAAE;AAC9B,SAAK,QAAA;AAAA,EACP;AAAA;AAAA;AAAA,EAKA,MACE,OACA,WACK;AACL,QAAI,KAAK,OAAO,WAAW,EAAG,QAAO;AACrC,UAAM,SAAS,MAAM,MAAA;AACrB,UAAM,QAAQ,KAAK;AACnB,WAAO,KAAK,CAAC,GAAG,MAAM;AACpB,iBAAW,EAAE,KAAK,UAAA,KAAe,OAAO;AACtC,YAAI;AACJ,YAAI,WAAW;AACb,gBAAM,UAAU,GAAG,GAAG,KAAK,SAAS;AAAA,QACtC,OAAO;AACL,gBAAM,eAAe,GAAG,GAAG,GAAG;AAAA,QAChC;AACA,YAAI,cAAc,OAAQ,OAAM,CAAC;AACjC,YAAI,QAAQ,EAAG,QAAO;AAAA,MACxB;AACA,aAAO;AAAA,IACT,CAAC;AACD,WAAO;AAAA,EACT;AAAA;AAAA;AAAA,EAKA,UAAU,IAA4B;AACpC,SAAK,WAAW,IAAI,EAAE;AACtB,WAAO,MAAM;AAAE,WAAK,WAAW,OAAO,EAAE;AAAA,IAAG;AAAA,EAC7C;AAAA,EAEQ,UAAgB;AACtB,eAAW,MAAM,KAAK,WAAY,IAAA;AAAA,EACpC;AACF;AAEA,SAAS,eAAe,GAAQ,GAAQ,KAAqB;AAC3D,QAAM,OAAO,EAAE,GAAG;AAClB,QAAM,OAAO,EAAE,GAAG;AAClB,MAAI,QAAQ,QAAQ,QAAQ,KAAM,QAAO;AACzC,MAAI,QAAQ,KAAM,QAAO;AACzB,MAAI,QAAQ,KAAM,QAAO;AACzB,MAAI,OAAO,SAAS,YAAY,OAAO,SAAS,UAAU;AACxD,WAAO,KAAK,cAAc,IAAI;AAAA,EAChC;AACA,MAAI,OAAO,KAAM,QAAO;AACxB,MAAI,OAAO,KAAM,QAAO;AACxB,SAAO;AACT;"}
|