torch-glare 2.1.0 → 2.1.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.
Files changed (65) hide show
  1. package/apps/lib/components/Badge.tsx +34 -137
  2. package/apps/lib/components/BadgeField.tsx +4 -4
  3. package/apps/lib/components/DataViews/ARCHITECTURE.md +439 -0
  4. package/apps/lib/components/DataViews/DataViewsConfigPanel.tsx +416 -0
  5. package/apps/lib/components/DataViews/DataViewsHeader.tsx +126 -0
  6. package/apps/lib/components/DataViews/DataViewsLayout.tsx +300 -0
  7. package/apps/lib/components/DataViews/FilterPanel.tsx +324 -0
  8. package/apps/lib/components/DataViews/InboxView.tsx +514 -0
  9. package/apps/lib/components/DataViews/KanbanView.tsx +242 -0
  10. package/apps/lib/components/DataViews/PanelControls.tsx +80 -0
  11. package/apps/lib/components/DataViews/SettingsPanel.tsx +285 -0
  12. package/apps/lib/components/DataViews/TableView.tsx +232 -0
  13. package/apps/lib/components/DataViews/TreeView.tsx +363 -0
  14. package/apps/lib/components/DataViews/badgeAdapter.ts +45 -0
  15. package/apps/lib/components/DataViews/fieldRenderers.tsx +334 -0
  16. package/apps/lib/components/DataViews/filters/DateRangePopover.tsx +113 -0
  17. package/apps/lib/components/DataViews/filters/PresetChips.tsx +45 -0
  18. package/apps/lib/components/DataViews/filters/RangeSliderWithInputs.tsx +154 -0
  19. package/apps/lib/components/DataViews/index.ts +30 -0
  20. package/apps/lib/components/DataViews/tree/TreeDrawer.tsx +54 -0
  21. package/apps/lib/components/DataViews/tree/TreeSidebar.tsx +77 -0
  22. package/apps/lib/components/DataViews/types.ts +177 -0
  23. package/apps/lib/components/TreeFolder/TreeFolder.tsx +387 -0
  24. package/apps/lib/components/TreeFolder/TreeFolderBreadcrumb.tsx +80 -0
  25. package/apps/lib/components/TreeFolder/TreeFolderRow.tsx +235 -0
  26. package/apps/lib/components/TreeFolder/TreeFolderStyles.tsx +60 -0
  27. package/apps/lib/components/TreeFolder/icons.tsx +63 -0
  28. package/apps/lib/components/TreeFolder/index.ts +17 -0
  29. package/apps/lib/components/TreeFolder/treeFolderUtils.ts +114 -0
  30. package/apps/lib/components/TreeFolder/types.ts +68 -0
  31. package/apps/lib/components/TreeFolder/useTreeFolderDnD.ts +261 -0
  32. package/apps/lib/hooks/useDataViewsState.ts +169 -0
  33. package/apps/lib/hooks/useIsMobile.ts +21 -0
  34. package/apps/lib/utils/dataViews/columnUtils.ts +130 -0
  35. package/apps/lib/utils/dataViews/fieldUtils.ts +198 -0
  36. package/apps/lib/utils/dataViews/nestedDataUtils.tsx +364 -0
  37. package/apps/lib/utils/dataViews/pathUtils.ts +132 -0
  38. package/apps/lib/utils/dataViews/rangeUtils.ts +225 -0
  39. package/apps/lib/utils/dataViews/treeUtils.ts +403 -0
  40. package/dist/bin/index.js +3 -3
  41. package/dist/bin/index.js.map +1 -1
  42. package/dist/src/commands/add.d.ts.map +1 -1
  43. package/dist/src/commands/add.js +29 -6
  44. package/dist/src/commands/add.js.map +1 -1
  45. package/dist/src/commands/utils.d.ts.map +1 -1
  46. package/dist/src/commands/utils.js +22 -2
  47. package/dist/src/commands/utils.js.map +1 -1
  48. package/dist/src/shared/copyComponentsRecursively.d.ts.map +1 -1
  49. package/dist/src/shared/copyComponentsRecursively.js +8 -1
  50. package/dist/src/shared/copyComponentsRecursively.js.map +1 -1
  51. package/dist/src/shared/getDependenciesAndInstallNestedComponents.d.ts +18 -4
  52. package/dist/src/shared/getDependenciesAndInstallNestedComponents.d.ts.map +1 -1
  53. package/dist/src/shared/getDependenciesAndInstallNestedComponents.js +110 -40
  54. package/dist/src/shared/getDependenciesAndInstallNestedComponents.js.map +1 -1
  55. package/docs/components/badge-field.md +21 -21
  56. package/docs/components/badge.md +156 -483
  57. package/docs/components/form-stepper.md +244 -0
  58. package/docs/components/stepper.md +215 -0
  59. package/docs/components/timeline.md +248 -0
  60. package/docs/reference/components.md +8 -7
  61. package/docs/reference/types.md +34 -26
  62. package/docs/tutorials/theming-basics.md +30 -27
  63. package/package.json +1 -1
  64. /package/docs/components/{labeled-checkbox.md → labeled-check-box.md} +0 -0
  65. /package/docs/components/{tree-dropdown.md → tree-drop-down.md} +0 -0
@@ -0,0 +1,225 @@
1
+ import type {
2
+ DynamicRecord,
3
+ FieldConfig,
4
+ FieldPreset,
5
+ FilterValue,
6
+ NumericRangeFilter,
7
+ DateRangeFilter,
8
+ RangeFilter,
9
+ } from "../../components/DataViews/types"
10
+ import { getByPath } from "./pathUtils"
11
+
12
+ export function isRangeFilter(v: unknown): v is RangeFilter {
13
+ return (
14
+ !!v &&
15
+ typeof v === "object" &&
16
+ !Array.isArray(v) &&
17
+ ((v as RangeFilter).kind === "number" || (v as RangeFilter).kind === "date")
18
+ )
19
+ }
20
+
21
+ export function isNumericRange(v: FilterValue | undefined | null): v is NumericRangeFilter {
22
+ return !!v && !Array.isArray(v) && (v as RangeFilter).kind === "number"
23
+ }
24
+
25
+ export function isDateRange(v: FilterValue | undefined | null): v is DateRangeFilter {
26
+ return !!v && !Array.isArray(v) && (v as RangeFilter).kind === "date"
27
+ }
28
+
29
+ export function isFilterActive(v: FilterValue | undefined): boolean {
30
+ if (v == null) return false
31
+ if (Array.isArray(v)) return v.length > 0
32
+ if (isNumericRange(v)) return v.min != null || v.max != null
33
+ if (isDateRange(v)) return v.from != null || v.to != null
34
+ return false
35
+ }
36
+
37
+ export function countActiveFilters(state: Record<string, FilterValue>): number {
38
+ let n = 0
39
+ for (const v of Object.values(state)) {
40
+ if (isFilterActive(v)) n++
41
+ }
42
+ return n
43
+ }
44
+
45
+ export function describeFilterValue(v: FilterValue): string {
46
+ if (Array.isArray(v)) return v.join(", ")
47
+ if (isNumericRange(v)) {
48
+ if (v.min != null && v.max != null) return `${v.min} – ${v.max}`
49
+ if (v.min != null) return `≥ ${v.min}`
50
+ if (v.max != null) return `≤ ${v.max}`
51
+ return "any"
52
+ }
53
+ if (isDateRange(v)) {
54
+ if (v.from && v.to) return `${v.from} → ${v.to}`
55
+ if (v.from) return `from ${v.from}`
56
+ if (v.to) return `until ${v.to}`
57
+ return "any"
58
+ }
59
+ return ""
60
+ }
61
+
62
+ export type NumericExtremes = { min: number; max: number }
63
+
64
+ export function computeNumericExtremes(
65
+ data: DynamicRecord[],
66
+ path: string,
67
+ ): NumericExtremes | null {
68
+ let min = Number.POSITIVE_INFINITY
69
+ let max = Number.NEGATIVE_INFINITY
70
+ let found = false
71
+ for (const item of data) {
72
+ const v = getByPath(item, path)
73
+ const n = typeof v === "number" ? v : Number(v)
74
+ if (!Number.isFinite(n)) continue
75
+ if (n < min) min = n
76
+ if (n > max) max = n
77
+ found = true
78
+ }
79
+ return found ? { min, max } : null
80
+ }
81
+
82
+ export function inferStep(field: FieldConfig, extremes: NumericExtremes): number {
83
+ if (field.rangeStep != null) return field.rangeStep
84
+ switch (field.type) {
85
+ case "star-rating": return 0.1
86
+ case "progress-bar": return 1
87
+ case "currency": {
88
+ const span = extremes.max - extremes.min
89
+ if (span >= 10000) return 100
90
+ if (span >= 1000) return 10
91
+ return 1
92
+ }
93
+ default: {
94
+ const span = extremes.max - extremes.min
95
+ if (span >= 10000) return 10
96
+ if (span >= 100) return 1
97
+ return 0.1
98
+ }
99
+ }
100
+ }
101
+
102
+ export type DateExtremes = { from: string; to: string }
103
+
104
+ export function computeDateExtremes(
105
+ data: DynamicRecord[],
106
+ path: string,
107
+ ): DateExtremes | null {
108
+ let minMs = Number.POSITIVE_INFINITY
109
+ let maxMs = Number.NEGATIVE_INFINITY
110
+ let found = false
111
+ for (const item of data) {
112
+ const v = getByPath(item, path)
113
+ if (v == null) continue
114
+ const ms = v instanceof Date ? v.getTime() : Date.parse(String(v))
115
+ if (!Number.isFinite(ms)) continue
116
+ if (ms < minMs) minMs = ms
117
+ if (ms > maxMs) maxMs = ms
118
+ found = true
119
+ }
120
+ if (!found) return null
121
+ return { from: toIsoDate(new Date(minMs)), to: toIsoDate(new Date(maxMs)) }
122
+ }
123
+
124
+ const RELATIVE = /^(-?\d+)([dwmy])$/
125
+
126
+ export function resolveRelativeDate(token: string | undefined): string | undefined {
127
+ if (!token) return undefined
128
+ if (token === "today" || token === "now") return toIsoDate(new Date())
129
+ if (token === "start-of-year") {
130
+ const now = new Date()
131
+ return toIsoDate(new Date(now.getFullYear(), 0, 1))
132
+ }
133
+ const m = RELATIVE.exec(token)
134
+ if (m) {
135
+ const n = parseInt(m[1], 10)
136
+ const unit = m[2]
137
+ const d = new Date()
138
+ switch (unit) {
139
+ case "d": d.setDate(d.getDate() + n); break
140
+ case "w": d.setDate(d.getDate() + n * 7); break
141
+ case "m": d.setMonth(d.getMonth() + n); break
142
+ case "y": d.setFullYear(d.getFullYear() + n); break
143
+ }
144
+ return toIsoDate(d)
145
+ }
146
+ const ms = Date.parse(token)
147
+ if (Number.isFinite(ms)) return toIsoDate(new Date(ms))
148
+ return token
149
+ }
150
+
151
+ export function toIsoDate(d: Date): string {
152
+ const yyyy = d.getFullYear()
153
+ const mm = String(d.getMonth() + 1).padStart(2, "0")
154
+ const dd = String(d.getDate()).padStart(2, "0")
155
+ return `${yyyy}-${mm}-${dd}`
156
+ }
157
+
158
+ export function presetToFilterValue(preset: FieldPreset): RangeFilter {
159
+ if ("from" in preset || "to" in preset) {
160
+ return {
161
+ kind: "date",
162
+ from: resolveRelativeDate(preset.from),
163
+ to: resolveRelativeDate(preset.to),
164
+ }
165
+ }
166
+ return {
167
+ kind: "number",
168
+ min: (preset as { min?: number }).min,
169
+ max: (preset as { max?: number }).max,
170
+ }
171
+ }
172
+
173
+ export function isPresetActive(preset: FieldPreset, current: FilterValue | undefined): boolean {
174
+ if (!current) return false
175
+ if (Array.isArray(current)) return false
176
+ if (current.kind === "number" && !("from" in preset) && !("to" in preset)) {
177
+ return (
178
+ ((preset as { min?: number }).min ?? null) === (current.min ?? null) &&
179
+ ((preset as { max?: number }).max ?? null) === (current.max ?? null)
180
+ )
181
+ }
182
+ if (current.kind === "date" && (("from" in preset) || ("to" in preset))) {
183
+ const want = presetToFilterValue(preset) as DateRangeFilter
184
+ return (want.from ?? null) === (current.from ?? null) && (want.to ?? null) === (current.to ?? null)
185
+ }
186
+ return false
187
+ }
188
+
189
+ const DEFAULT_PRESETS: Record<string, FieldPreset[]> = {
190
+ currency: [
191
+ { label: "<$100", max: 100 },
192
+ { label: "$100–$500", min: 100, max: 500 },
193
+ { label: "$500–$2k", min: 500, max: 2000 },
194
+ { label: "$2k+", min: 2000 },
195
+ ],
196
+ "progress-bar": [
197
+ { label: "0%", min: 0, max: 0 },
198
+ { label: "1–40%", min: 1, max: 40 },
199
+ { label: "40–70%", min: 40, max: 70 },
200
+ { label: "70–99%", min: 70, max: 99 },
201
+ { label: "100%", min: 100, max: 100 },
202
+ ],
203
+ "star-rating": [
204
+ { label: "≥4★", min: 4 },
205
+ { label: "≥3★", min: 3 },
206
+ { label: "<3★", max: 3 },
207
+ ],
208
+ date: [
209
+ { label: "Today", from: "today" },
210
+ { label: "Last 7 days", from: "-7d" },
211
+ { label: "Last 30 days", from: "-30d" },
212
+ { label: "This year", from: "start-of-year" },
213
+ ],
214
+ "date-format": [
215
+ { label: "Today", from: "today" },
216
+ { label: "Last 7 days", from: "-7d" },
217
+ { label: "Last 30 days", from: "-30d" },
218
+ { label: "This year", from: "start-of-year" },
219
+ ],
220
+ }
221
+
222
+ export function resolvePresets(field: FieldConfig): FieldPreset[] {
223
+ if (field.presets) return field.presets
224
+ return DEFAULT_PRESETS[field.type ?? ""] ?? []
225
+ }
@@ -0,0 +1,403 @@
1
+ import type { DynamicRecord, TreeConfig } from "../../components/DataViews/types"
2
+ import { getByPath, setByPath } from "./pathUtils"
3
+
4
+ export type TreeNode = {
5
+ id: string
6
+ record: DynamicRecord
7
+ children: TreeNode[]
8
+ depth: number
9
+ parent: TreeNode | null
10
+ }
11
+
12
+ export type ResolvedTreeConfig = {
13
+ childrenField?: string
14
+ parentField?: string
15
+ idField: string
16
+ orderField?: string
17
+ nodeLabel?: string
18
+ defaultExpanded: "all" | "roots" | "none"
19
+ }
20
+
21
+ const CHILDREN_PATTERNS = ["children", "items", "kids", "subItems", "nodes"]
22
+ const PARENT_PATTERNS = ["parentId", "parent_id", "parent", "managerId", "manager"]
23
+ const ID_PATTERNS = ["id", "_id", "uuid", "key"]
24
+
25
+ export function autoDetectTreeShape(
26
+ data: DynamicRecord[],
27
+ config: TreeConfig,
28
+ ): ResolvedTreeConfig {
29
+ const sample = data.find((r) => r != null && typeof r === "object") ?? {}
30
+
31
+ const idField = config.idField ?? pickKey(sample, ID_PATTERNS) ?? "id"
32
+
33
+ let childrenField = config.childrenField
34
+ let parentField = config.parentField
35
+
36
+ if (!childrenField && !parentField) {
37
+ childrenField = pickArrayKey(sample, CHILDREN_PATTERNS)
38
+ if (!childrenField) parentField = pickKey(sample, PARENT_PATTERNS)
39
+ }
40
+
41
+ return {
42
+ childrenField,
43
+ parentField,
44
+ idField,
45
+ orderField: config.orderField,
46
+ nodeLabel: config.nodeLabel,
47
+ defaultExpanded: config.defaultExpanded ?? "roots",
48
+ }
49
+ }
50
+
51
+ function pickKey(record: DynamicRecord, candidates: string[]): string | undefined {
52
+ for (const key of candidates) {
53
+ if (key in record) return key
54
+ }
55
+ return undefined
56
+ }
57
+
58
+ function pickArrayKey(record: DynamicRecord, candidates: string[]): string | undefined {
59
+ for (const key of candidates) {
60
+ if (key in record && Array.isArray(record[key])) return key
61
+ }
62
+ return undefined
63
+ }
64
+
65
+ export function buildTree(
66
+ data: DynamicRecord[],
67
+ config: ResolvedTreeConfig,
68
+ ): TreeNode[] {
69
+ if (!data || data.length === 0) return []
70
+ if (config.childrenField) return buildFromNested(data, config)
71
+ if (config.parentField) return buildFromFlat(data, config)
72
+ return data.map((record, idx) => makeNode(record, config, idx, null, 0))
73
+ }
74
+
75
+ function buildFromNested(
76
+ data: DynamicRecord[],
77
+ config: ResolvedTreeConfig,
78
+ ): TreeNode[] {
79
+ const seen = new Set<string>()
80
+
81
+ const visit = (record: DynamicRecord, depth: number, parent: TreeNode | null, idx: number): TreeNode => {
82
+ const node = makeNode(record, config, idx, parent, depth)
83
+ if (seen.has(node.id)) {
84
+ return { ...node, children: [] }
85
+ }
86
+ seen.add(node.id)
87
+
88
+ const rawChildren = getByPath(record, config.childrenField)
89
+ if (Array.isArray(rawChildren)) {
90
+ node.children = rawChildren.map((child, ci) => visit(child, depth + 1, node, ci))
91
+ }
92
+ return node
93
+ }
94
+
95
+ return data.map((record, idx) => visit(record, 0, null, idx))
96
+ }
97
+
98
+ function buildFromFlat(
99
+ data: DynamicRecord[],
100
+ config: ResolvedTreeConfig,
101
+ ): TreeNode[] {
102
+ const byId = new Map<string, TreeNode>()
103
+ for (let i = 0; i < data.length; i++) {
104
+ const record = data[i]
105
+ const id = String(getByPath(record, config.idField) ?? `__row${i}`)
106
+ byId.set(id, {
107
+ id,
108
+ record,
109
+ children: [],
110
+ depth: 0,
111
+ parent: null,
112
+ })
113
+ }
114
+
115
+ const roots: TreeNode[] = []
116
+ for (const node of byId.values()) {
117
+ const parentId = getByPath(node.record, config.parentField)
118
+ if (parentId == null) {
119
+ roots.push(node)
120
+ continue
121
+ }
122
+ const parent = byId.get(String(parentId))
123
+ if (!parent || parent === node) {
124
+ roots.push(node)
125
+ continue
126
+ }
127
+ node.parent = parent
128
+ parent.children.push(node)
129
+ }
130
+
131
+ const setDepth = (n: TreeNode, d: number, guard = new Set<TreeNode>()) => {
132
+ if (guard.has(n)) return
133
+ guard.add(n)
134
+ n.depth = d
135
+ for (const c of n.children) setDepth(c, d + 1, guard)
136
+ }
137
+ for (const r of roots) setDepth(r, 0)
138
+
139
+ return roots
140
+ }
141
+
142
+ function makeNode(
143
+ record: DynamicRecord,
144
+ config: ResolvedTreeConfig,
145
+ idx: number,
146
+ parent: TreeNode | null,
147
+ depth: number,
148
+ ): TreeNode {
149
+ const idValue = getByPath(record, config.idField)
150
+ const id = idValue != null ? String(idValue) : `__row${idx}`
151
+ return { id, record, children: [], depth, parent }
152
+ }
153
+
154
+ export function findNodeById(roots: TreeNode[], id: string): TreeNode | null {
155
+ for (const root of roots) {
156
+ if (root.id === id) return root
157
+ const inChild = findNodeById(root.children, id)
158
+ if (inChild) return inChild
159
+ }
160
+ return null
161
+ }
162
+
163
+ export function* walk(node: TreeNode): Generator<TreeNode> {
164
+ yield node
165
+ for (const c of node.children) yield* walk(c)
166
+ }
167
+
168
+ export function flatten(node: TreeNode): DynamicRecord[] {
169
+ const out: DynamicRecord[] = []
170
+ for (const n of walk(node)) out.push(n.record)
171
+ return out
172
+ }
173
+
174
+ export function descendantIds(node: TreeNode): Set<string> {
175
+ const out = new Set<string>()
176
+ for (const n of walk(node)) out.add(n.id)
177
+ return out
178
+ }
179
+
180
+ export function allIds(roots: TreeNode[]): Set<string> {
181
+ const out = new Set<string>()
182
+ for (const r of roots) for (const n of walk(r)) out.add(n.id)
183
+ return out
184
+ }
185
+
186
+ export function initialExpansion(roots: TreeNode[], mode: "all" | "roots" | "none"): Set<string> {
187
+ if (mode === "none") return new Set()
188
+ if (mode === "roots") return new Set(roots.map((r) => r.id))
189
+ return allIds(roots)
190
+ }
191
+
192
+ export function flattenAll(data: DynamicRecord[], childrenField: string): DynamicRecord[] {
193
+ const out: DynamicRecord[] = []
194
+ const dfs = (record: DynamicRecord) => {
195
+ if (record == null || typeof record !== "object") return
196
+ const { [childrenField]: kids, ...rest } = record
197
+ out.push(rest as DynamicRecord)
198
+ if (Array.isArray(kids)) for (const k of kids) dfs(k)
199
+ }
200
+ for (const r of data) dfs(r)
201
+ return out
202
+ }
203
+
204
+ export type MoveArgs = {
205
+ dragIds: string[]
206
+ parentId: string | null
207
+ index: number
208
+ }
209
+
210
+ export function applyMove(
211
+ data: DynamicRecord[],
212
+ config: ResolvedTreeConfig,
213
+ args: MoveArgs,
214
+ ): DynamicRecord[] {
215
+ if (config.childrenField) return applyMoveNested(data, config, args)
216
+ if (config.parentField) return applyMoveFlat(data, config, args)
217
+ return applyMoveTopLevel(data, config, args)
218
+ }
219
+
220
+ function recordId(record: DynamicRecord, idField: string, fallbackIdx: number): string {
221
+ const v = getByPath(record, idField)
222
+ return v != null ? String(v) : `__row${fallbackIdx}`
223
+ }
224
+
225
+ function applyMoveTopLevel(
226
+ data: DynamicRecord[],
227
+ config: ResolvedTreeConfig,
228
+ args: MoveArgs,
229
+ ): DynamicRecord[] {
230
+ const ids = new Set(args.dragIds)
231
+ const dragged: DynamicRecord[] = []
232
+ const remaining: DynamicRecord[] = []
233
+ data.forEach((rec, i) => {
234
+ if (ids.has(recordId(rec, config.idField, i))) dragged.push(rec)
235
+ else remaining.push(rec)
236
+ })
237
+ const target = args.parentId == null ? remaining : remaining
238
+ const insertAt = Math.max(0, Math.min(args.index, target.length))
239
+ return [...target.slice(0, insertAt), ...dragged, ...target.slice(insertAt)]
240
+ }
241
+
242
+ function applyMoveFlat(
243
+ data: DynamicRecord[],
244
+ config: ResolvedTreeConfig,
245
+ args: MoveArgs,
246
+ ): DynamicRecord[] {
247
+ const idField = config.idField
248
+ const parentField = config.parentField!
249
+ const orderField = config.orderField
250
+
251
+ const dragSet = new Set(args.dragIds)
252
+
253
+ if (args.parentId) {
254
+ for (const d of data) {
255
+ const id = String(getByPath(d, idField) ?? "")
256
+ if (!dragSet.has(id)) continue
257
+ let cur: string | null = args.parentId
258
+ while (cur) {
259
+ if (cur === id) return data
260
+ const search = cur
261
+ const parentRec = data.find((r) => String(getByPath(r, idField) ?? "") === search)
262
+ if (!parentRec) break
263
+ const next = getByPath(parentRec, parentField)
264
+ cur = next == null ? null : String(next)
265
+ }
266
+ }
267
+ }
268
+
269
+ const updatedParent: DynamicRecord[] = data.map((rec) => {
270
+ const id = String(getByPath(rec, idField) ?? "")
271
+ if (!dragSet.has(id)) return rec
272
+ return setByPath(rec, parentField, args.parentId ?? null)
273
+ })
274
+
275
+ if (!orderField) return updatedParent
276
+
277
+ const siblingsAfter = updatedParent.filter((rec) => {
278
+ const p = getByPath(rec, parentField)
279
+ const norm = p == null ? null : String(p)
280
+ return norm === (args.parentId ?? null)
281
+ })
282
+
283
+ const dragged: DynamicRecord[] = []
284
+ const others: DynamicRecord[] = []
285
+ siblingsAfter.forEach((rec) => {
286
+ const id = String(getByPath(rec, idField) ?? "")
287
+ if (dragSet.has(id)) dragged.push(rec)
288
+ else others.push(rec)
289
+ })
290
+
291
+ const insertAt = Math.max(0, Math.min(args.index, others.length))
292
+ const newOrder = [...others.slice(0, insertAt), ...dragged, ...others.slice(insertAt)]
293
+ const orderById = new Map<string, number>()
294
+ newOrder.forEach((rec, i) => {
295
+ const id = String(getByPath(rec, idField) ?? "")
296
+ orderById.set(id, i)
297
+ })
298
+
299
+ return updatedParent.map((rec) => {
300
+ const id = String(getByPath(rec, idField) ?? "")
301
+ if (!orderById.has(id)) return rec
302
+ return setByPath(rec, orderField, orderById.get(id)!)
303
+ })
304
+ }
305
+
306
+ function applyMoveNested(
307
+ data: DynamicRecord[],
308
+ config: ResolvedTreeConfig,
309
+ args: MoveArgs,
310
+ ): DynamicRecord[] {
311
+ const childrenField = config.childrenField!
312
+ const idField = config.idField
313
+ const dragSet = new Set(args.dragIds)
314
+
315
+ const isAncestorOfDrag = (record: DynamicRecord, ancestors: DynamicRecord[]): boolean => {
316
+ const id = String(getByPath(record, idField) ?? "")
317
+ if (dragSet.has(id)) {
318
+ for (const a of ancestors) {
319
+ const aid = String(getByPath(a, idField) ?? "")
320
+ if (aid === args.parentId) return true
321
+ }
322
+ }
323
+ const kids = getByPath(record, childrenField)
324
+ if (Array.isArray(kids)) {
325
+ for (const k of kids) {
326
+ if (isAncestorOfDrag(k, [...ancestors, record])) return true
327
+ }
328
+ }
329
+ return false
330
+ }
331
+ for (const r of data) if (isAncestorOfDrag(r, [])) return data
332
+
333
+ const extracted: DynamicRecord[] = []
334
+ const extract = (records: DynamicRecord[]): DynamicRecord[] =>
335
+ records
336
+ .map((rec) => {
337
+ const id = String(getByPath(rec, idField) ?? "")
338
+ const kids = getByPath(rec, childrenField)
339
+ const newKids = Array.isArray(kids) ? extract(kids) : kids
340
+ const next = Array.isArray(kids) ? setByPath(rec, childrenField, newKids) : rec
341
+ if (dragSet.has(id)) {
342
+ extracted.push(next)
343
+ return null
344
+ }
345
+ return next
346
+ })
347
+ .filter((r): r is DynamicRecord => r !== null)
348
+
349
+ let pruned = extract(data)
350
+
351
+ const insert = (records: DynamicRecord[]): DynamicRecord[] =>
352
+ records.map((rec) => {
353
+ const id = String(getByPath(rec, idField) ?? "")
354
+ if (id === args.parentId) {
355
+ const kids = getByPath(rec, childrenField)
356
+ const arr = Array.isArray(kids) ? [...kids] : []
357
+ const at = Math.max(0, Math.min(args.index, arr.length))
358
+ arr.splice(at, 0, ...extracted)
359
+ return setByPath(rec, childrenField, arr)
360
+ }
361
+ const kids = getByPath(rec, childrenField)
362
+ if (Array.isArray(kids)) {
363
+ return setByPath(rec, childrenField, insert(kids))
364
+ }
365
+ return rec
366
+ })
367
+
368
+ if (args.parentId == null) {
369
+ const at = Math.max(0, Math.min(args.index, pruned.length))
370
+ return [...pruned.slice(0, at), ...extracted, ...pruned.slice(at)]
371
+ }
372
+
373
+ return insert(pruned)
374
+ }
375
+
376
+ export function pruneTree(
377
+ roots: TreeNode[],
378
+ predicate: (record: DynamicRecord) => boolean,
379
+ ): TreeNode[] {
380
+ const visit = (node: TreeNode, parent: TreeNode | null): TreeNode | null => {
381
+ const newChildren: TreeNode[] = []
382
+ for (const c of node.children) {
383
+ const next = visit(c, node)
384
+ if (next) newChildren.push(next)
385
+ }
386
+ const selfMatches = predicate(node.record)
387
+ if (!selfMatches && newChildren.length === 0) return null
388
+ return {
389
+ id: node.id,
390
+ record: node.record,
391
+ depth: node.depth,
392
+ parent,
393
+ children: newChildren,
394
+ }
395
+ }
396
+
397
+ const out: TreeNode[] = []
398
+ for (const r of roots) {
399
+ const next = visit(r, null)
400
+ if (next) out.push(next)
401
+ }
402
+ return out
403
+ }
package/dist/bin/index.js CHANGED
@@ -12,7 +12,7 @@ const program = new Command();
12
12
  program
13
13
  .name("torch-glare")
14
14
  .description("Torch Glare for managing React components")
15
- .version("1.0.8");
15
+ .version("2.2.0");
16
16
  program
17
17
  .command("init")
18
18
  .description("Initialize torch.json configuration file")
@@ -20,7 +20,7 @@ program
20
20
  program
21
21
  .command("add [component]")
22
22
  .description("Add a component interactively or install a specified one")
23
- .action((component) => add(component && `${component}.tsx`));
23
+ .action((component) => add(component));
24
24
  program
25
25
  .command("hook [hook]")
26
26
  .description("Add a hook interactively or install a specified one")
@@ -32,7 +32,7 @@ program
32
32
  program
33
33
  .command("util [util]")
34
34
  .description("Add a utils interactively or install a specified one")
35
- .action((util) => addUtil(util && `${util}.ts`));
35
+ .action((util) => addUtil(util));
36
36
  program
37
37
  .command("provider [provider]")
38
38
  .description("Add a provider interactively or install a specified one")
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../cli/bin/index.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,GAAG,EAAE,MAAM,wBAAwB,CAAC;AAC7C,OAAO,EAAE,UAAU,EAAE,MAAM,yBAAyB,CAAC;AACrD,OAAO,EAAE,OAAO,EAAE,MAAM,yBAAyB,CAAC;AAClD,OAAO,EAAE,SAAS,EAAE,MAAM,2BAA2B,CAAC;AACtD,OAAO,EAAE,OAAO,EAAE,MAAM,0BAA0B,CAAC;AACnD,OAAO,EAAE,WAAW,EAAE,MAAM,6BAA6B,CAAC;AAC1D,OAAO,EAAE,yBAAyB,EAAE,MAAM,2BAA2B,CAAC;AACtE,OAAO,EAAE,QAAQ,EAAE,MAAM,wBAAwB,CAAC;AAElD,MAAM,OAAO,GAAG,IAAI,OAAO,EAAE,CAAC;AAE9B,OAAO;KACJ,IAAI,CAAC,aAAa,CAAC;KACnB,WAAW,CAAC,2CAA2C,CAAC;KACxD,OAAO,CAAC,OAAO,CAAC,CAAC;AAEpB,OAAO;KACJ,OAAO,CAAC,MAAM,CAAC;KACf,WAAW,CAAC,0CAA0C,CAAC;KACvD,MAAM,CAAC,GAAG,EAAE,CAAC,UAAU,EAAE,CAAC,CAAC;AAE9B,OAAO;KACJ,OAAO,CAAC,iBAAiB,CAAC;KAC1B,WAAW,CAAC,0DAA0D,CAAC;KACvE,MAAM,CAAC,CAAC,SAAS,EAAE,EAAE,CAAC,GAAG,CAAC,SAAS,IAAI,GAAG,SAAS,MAAM,CAAC,CAAC,CAAC;AAE/D,OAAO;KACJ,OAAO,CAAC,aAAa,CAAC;KACtB,WAAW,CAAC,qDAAqD,CAAC;KAClE,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,OAAO,CAAC,IAAI,IAAI,GAAG,IAAI,EAAE,CAAC,CAAC,CAAC;AAEhD,OAAO;KACJ,OAAO,CAAC,iBAAiB,CAAC;KAC1B,WAAW,CAAC,uDAAuD,CAAC;KACpE,MAAM,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,SAAS,CAAC,MAAM,IAAI,GAAG,MAAM,MAAM,CAAC,CAAC,CAAC;AAE5D,OAAO;KACJ,OAAO,CAAC,aAAa,CAAC;KACtB,WAAW,CAAC,sDAAsD,CAAC;KACnE,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,OAAO,CAAC,IAAI,IAAI,GAAG,IAAI,KAAK,CAAC,CAAC,CAAC;AAEnD,OAAO;KACJ,OAAO,CAAC,qBAAqB,CAAC;KAC9B,WAAW,CAAC,yDAAyD,CAAC;KACtE,MAAM,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,WAAW,CAAC,QAAQ,IAAI,GAAG,QAAQ,EAAE,CAAC,CAAC,CAAC;AAEhE,OAAO;KACJ,OAAO,CAAC,QAAQ,CAAC;KACjB,WAAW,CAAC,6BAA6B,CAAC;KAC1C,MAAM,CAAC,GAAG,EAAE,CAAC,yBAAyB,EAAE,CAAC,CAAC;AAE7C,OAAO;KACJ,OAAO,CAAC,KAAK,CAAC;KACd,WAAW,CAAC,kDAAkD,CAAC;KAC/D,MAAM,CAAC,GAAG,EAAE,CAAC,QAAQ,EAAE,CAAC,CAAC;AAE5B,OAAO,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../cli/bin/index.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,GAAG,EAAE,MAAM,wBAAwB,CAAC;AAC7C,OAAO,EAAE,UAAU,EAAE,MAAM,yBAAyB,CAAC;AACrD,OAAO,EAAE,OAAO,EAAE,MAAM,yBAAyB,CAAC;AAClD,OAAO,EAAE,SAAS,EAAE,MAAM,2BAA2B,CAAC;AACtD,OAAO,EAAE,OAAO,EAAE,MAAM,0BAA0B,CAAC;AACnD,OAAO,EAAE,WAAW,EAAE,MAAM,6BAA6B,CAAC;AAC1D,OAAO,EAAE,yBAAyB,EAAE,MAAM,2BAA2B,CAAC;AACtE,OAAO,EAAE,QAAQ,EAAE,MAAM,wBAAwB,CAAC;AAElD,MAAM,OAAO,GAAG,IAAI,OAAO,EAAE,CAAC;AAE9B,OAAO;KACJ,IAAI,CAAC,aAAa,CAAC;KACnB,WAAW,CAAC,2CAA2C,CAAC;KACxD,OAAO,CAAC,OAAO,CAAC,CAAC;AAEpB,OAAO;KACJ,OAAO,CAAC,MAAM,CAAC;KACf,WAAW,CAAC,0CAA0C,CAAC;KACvD,MAAM,CAAC,GAAG,EAAE,CAAC,UAAU,EAAE,CAAC,CAAC;AAE9B,OAAO;KACJ,OAAO,CAAC,iBAAiB,CAAC;KAC1B,WAAW,CAAC,0DAA0D,CAAC;KACvE,MAAM,CAAC,CAAC,SAAS,EAAE,EAAE,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC,CAAC;AAEzC,OAAO;KACJ,OAAO,CAAC,aAAa,CAAC;KACtB,WAAW,CAAC,qDAAqD,CAAC;KAClE,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,OAAO,CAAC,IAAI,IAAI,GAAG,IAAI,EAAE,CAAC,CAAC,CAAC;AAEhD,OAAO;KACJ,OAAO,CAAC,iBAAiB,CAAC;KAC1B,WAAW,CAAC,uDAAuD,CAAC;KACpE,MAAM,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,SAAS,CAAC,MAAM,IAAI,GAAG,MAAM,MAAM,CAAC,CAAC,CAAC;AAE5D,OAAO;KACJ,OAAO,CAAC,aAAa,CAAC;KACtB,WAAW,CAAC,sDAAsD,CAAC;KACnE,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC;AAEnC,OAAO;KACJ,OAAO,CAAC,qBAAqB,CAAC;KAC9B,WAAW,CAAC,yDAAyD,CAAC;KACtE,MAAM,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,WAAW,CAAC,QAAQ,IAAI,GAAG,QAAQ,EAAE,CAAC,CAAC,CAAC;AAEhE,OAAO;KACJ,OAAO,CAAC,QAAQ,CAAC;KACjB,WAAW,CAAC,6BAA6B,CAAC;KAC1C,MAAM,CAAC,GAAG,EAAE,CAAC,yBAAyB,EAAE,CAAC,CAAC;AAE7C,OAAO;KACJ,OAAO,CAAC,KAAK,CAAC;KACd,WAAW,CAAC,kDAAkD,CAAC;KAC/D,MAAM,CAAC,GAAG,EAAE,CAAC,QAAQ,EAAE,CAAC,CAAC;AAE5B,OAAO,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"add.d.ts","sourceRoot":"","sources":["../../../cli/src/commands/add.ts"],"names":[],"mappings":"AAsBA;;;;GAIG;AACH,wBAAsB,GAAG,CAAC,SAAS,CAAC,EAAE,MAAM,EAAE,OAAO,GAAE,OAAe,GAAG,OAAO,CAAC,IAAI,CAAC,CAgCrF"}
1
+ {"version":3,"file":"add.d.ts","sourceRoot":"","sources":["../../../cli/src/commands/add.ts"],"names":[],"mappings":"AAuBA;;;;GAIG;AACH,wBAAsB,GAAG,CAAC,SAAS,CAAC,EAAE,MAAM,EAAE,OAAO,GAAE,OAAe,GAAG,OAAO,CAAC,IAAI,CAAC,CAiCrF"}