@gridkitjs/core 0.0.1 → 0.2.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/dist/index.d.ts CHANGED
@@ -23,11 +23,30 @@ type FieldPath<Row> = {
23
23
  type ColumnType = "dateTime" | "date" | "time" | "number" | "string" | "boolean" | "decimal" | "currency" | "percent";
24
24
  type ColumnAlignment = "left" | "center" | "right";
25
25
  /**
26
- * @typeParam Header - What a header renders to. This package is
26
+ * What `cellTemplate` receives. An object rather than positional arguments so
27
+ * that a later addition — a sort state, whether the row is selected — is not a
28
+ * breaking change to every template already written.
29
+ */
30
+ interface CellTemplateContext<Row> {
31
+ /**
32
+ * This cell's value, read off the column's field path. `unknown` because a
33
+ * dotted path's type cannot be recovered here; a template narrows it.
34
+ */
35
+ value: unknown;
36
+ /** The whole row, for a template that needs a sibling field. */
37
+ row: Row;
38
+ /**
39
+ * Position among the rows as rendered. Once paging exists this is the index
40
+ * within the page; an index into the whole dataset would be a second field.
41
+ */
42
+ rowIndex: number;
43
+ }
44
+ /**
45
+ * @typeParam Node - What a header or cell renders to. This package is
27
46
  * framework-agnostic so it defaults to `string`; `@gridkitjs/react` binds it to
28
- * `ReactNode` so a header callback can return JSX.
47
+ * `ReactNode` so a template can return JSX.
29
48
  */
30
- interface ColumnDefinition<Row, Header = string> {
49
+ interface ColumnDefinition<Row, Node = string> {
31
50
  /**
32
51
  * Path to this cell's value: a key of `Row`, or `"Parent.Child"` for a leaf
33
52
  * one level inside a nested object. Any string still compiles — the union
@@ -41,7 +60,13 @@ interface ColumnDefinition<Row, Header = string> {
41
60
  */
42
61
  id?: string;
43
62
  /** Header content, or a function returning it at render time. */
44
- header?: Header | (() => Header) | undefined;
63
+ headerTemplate?: Node | (() => Node) | undefined;
64
+ /**
65
+ * Renders this column's cells in place of the raw value at `field`. The
66
+ * value is still resolved and handed over, so a template that only formats
67
+ * it never repeats the field path.
68
+ */
69
+ cellTemplate?: ((context: CellTemplateContext<Row>) => Node) | undefined;
45
70
  /**
46
71
  * The value type of this column's cells. This package is framework-agnostic so it
47
72
  * defaults to `string`;
@@ -63,6 +88,11 @@ interface ColumnDefinition<Row, Header = string> {
63
88
  maxWidth?: number;
64
89
  /** Whether this column can be resized, overriding the grid-level default. */
65
90
  resizable?: boolean;
91
+ /**
92
+ * Whether this column can be dragged to a new position, overriding the
93
+ * grid-level default. A column that cannot move can still be moved past.
94
+ */
95
+ reorderable?: boolean;
66
96
  }
67
97
  /**
68
98
  * Widths the user has changed, keyed by column id. Holding only what changed —
@@ -89,14 +119,16 @@ interface ColumnResolveOptions {
89
119
  sizes?: Partial<ColumnSizeDefaults> | undefined;
90
120
  /** Whether columns are resizable, unless a column says otherwise. */
91
121
  resizable?: boolean | undefined;
122
+ /** Whether columns are reorderable, unless a column says otherwise. */
123
+ reorderable?: boolean | undefined;
92
124
  }
93
125
  /**
94
126
  * A column paired with everything it takes to render it. Each field is a
95
127
  * decision made once here rather than in each adapter, so a second framework
96
128
  * binding renders identically without repeating the logic.
97
129
  */
98
- interface ResolvedColumn<Row, Header = string> {
99
- column: ColumnDefinition<Row, Header>;
130
+ interface ResolvedColumn<Row, Node = string> {
131
+ column: ColumnDefinition<Row, Node>;
100
132
  id: string;
101
133
  width: number;
102
134
  /**
@@ -106,13 +138,15 @@ interface ResolvedColumn<Row, Header = string> {
106
138
  */
107
139
  sized: boolean;
108
140
  /**
109
- * What the header shows: the column's own `header`, or a label read off the
110
- * field path. `string` is in the union because that fallback is text
111
- * whatever `Header` is bound to.
141
+ * What the header shows: the column's own `headerTemplate`, or a label read
142
+ * off the field path. `string` is in the union because that fallback is text
143
+ * whatever `Node` is bound to.
112
144
  */
113
- label: Header | string;
145
+ label: Node | string;
114
146
  /** Whether this column can be resized, after the grid-level default. */
115
147
  resizable: boolean;
148
+ /** Whether this column can be dragged, after the grid-level default. */
149
+ reorderable: boolean;
116
150
  /** How this column's cells align, after falling back to its type. */
117
151
  alignment: ColumnAlignment;
118
152
  }
@@ -136,6 +170,25 @@ interface ColumnResizeEvent {
136
170
  readonly sizing: ColumnSizingState;
137
171
  readonly phase: "move" | "end";
138
172
  }
173
+ /**
174
+ * Column ids in the order they render. It need not list every column: one
175
+ * absent from it keeps its position among the definitions and follows those
176
+ * that are listed.
177
+ */
178
+ type ColumnOrderState = readonly string[];
179
+ /**
180
+ * Which side of a column a drop lands on, normalised away by
181
+ * `resolveDropBefore` so it reaches no state.
182
+ */
183
+ type DropSide = "before" | "after";
184
+ /**
185
+ * Reports a user reorder. Unlike a resize there is no `"move"` phase — the
186
+ * order does not change until the drop.
187
+ */
188
+ interface ColumnOrderEvent {
189
+ readonly columnId: string;
190
+ readonly order: ColumnOrderState;
191
+ }
139
192
 
140
193
  /**
141
194
  * How a column of `type` aligns by default. Numeric types align right so that
@@ -159,11 +212,11 @@ declare function defineColumnsFromRows<Row>(rows: readonly Row[]): readonly Colu
159
212
  */
160
213
  declare function getColumnId<Row>(column: ColumnDefinition<Row, unknown>): string;
161
214
  /**
162
- * What a column's header shows: its own `header`, called first if it is a
163
- * function, or else a label read off the field path — `"Application.Id"`
215
+ * What a column's header shows: its own `headerTemplate`, called first if it is
216
+ * a function, or else a label read off the field path — `"Application.Id"`
164
217
  * reading as `"Application Id"`.
165
218
  */
166
- declare function resolveColumnLabel<Row, Header>(column: ColumnDefinition<Row, Header>): Header | string;
219
+ declare function resolveColumnLabel<Row, Node>(column: ColumnDefinition<Row, Node>): Node | string;
167
220
  /**
168
221
  * Accesses a value at a dotted path, e.g. `foo.bar.baz`.
169
222
  *
@@ -173,6 +226,40 @@ declare function resolveColumnLabel<Row, Header>(column: ColumnDefinition<Row, H
173
226
  */
174
227
  declare function accessDotted(obj: unknown, path: string): unknown;
175
228
 
229
+ /**
230
+ * Puts `columns` in the order `order` gives. Ids it lists come first, in that
231
+ * order; columns it omits keep their position among the definitions and
232
+ * follow. Ids naming no column are ignored.
233
+ *
234
+ * Tolerating both gaps is what lets a stored order outlive the column list it
235
+ * was made against.
236
+ */
237
+ declare function applyColumnOrder<Row, Node>(columns: readonly ColumnDefinition<Row, Node>[], order: ColumnOrderState): readonly ColumnDefinition<Row, Node>[];
238
+ /**
239
+ * The order with `movedId` lifted out and put back in front of `beforeId`, or
240
+ * at the end when that is `null`.
241
+ *
242
+ * Returns `orderedIds` itself when the move changes nothing, so callers can
243
+ * treat an unchanged reference as "no move happened".
244
+ */
245
+ declare function moveColumnBefore(orderedIds: ColumnOrderState, movedId: string, beforeId: string | null): ColumnOrderState;
246
+ /**
247
+ * Whether moving `movedId` in front of `beforeId` would rearrange anything, so
248
+ * that a drop indicator promises exactly the moves that happen.
249
+ *
250
+ * Defined against `moveColumnBefore` rather than restating its conditions, so
251
+ * the two cannot disagree about what a no-op is.
252
+ */
253
+ declare function movesColumn(orderedIds: ColumnOrderState, movedId: string, beforeId: string | null): boolean;
254
+ /**
255
+ * The id a drop on `side` of `targetId` lands in front of, which is `null`
256
+ * past the last column.
257
+ *
258
+ * "After C" and "before D" name the same gap, so collapsing both to one id
259
+ * keeps a drop target a single nullable string.
260
+ */
261
+ declare function resolveDropBefore(orderedIds: ColumnOrderState, targetId: string, side: DropSide): string | null;
262
+
176
263
  /**
177
264
  * Applied to any column that does not size itself. The minimum is a width a
178
265
  * header stays legible at rather than a measured value — a column dragged
@@ -197,8 +284,8 @@ declare function clampColumnWidth(width: number, constraints: ColumnConstraints)
197
284
  * The sizing state winning means a user's drag outlives a re-render; a column
198
285
  * absent from it still tracks a `width` edited in the definition.
199
286
  */
200
- declare function resolveColumnWidths<Row, Header>(columns: readonly ColumnDefinition<Row, Header>[], sizing: ColumnSizingState, options?: ColumnResolveOptions): readonly ResolvedColumn<Row, Header>[];
201
- declare function totalColumnWidth<Row, Header>(resolved: readonly ResolvedColumn<Row, Header>[]): number;
287
+ declare function resolveColumnWidths<Row, Node>(columns: readonly ColumnDefinition<Row, Node>[], sizing: ColumnSizingState, options?: ColumnResolveOptions): readonly ResolvedColumn<Row, Node>[];
288
+ declare function totalColumnWidth<Row, Node>(resolved: readonly ResolvedColumn<Row, Node>[]): number;
202
289
  /**
203
290
  * Opens a resize. The session captures its constraints and starting point up
204
291
  * front, so applying a pointer position later is arithmetic on numbers alone —
@@ -220,7 +307,7 @@ declare function applyColumnResize(session: ColumnResizeSession, position: numbe
220
307
  * them. Columns the user has sized are left at that width and the rest share
221
308
  * what is left, so dragging one column does not rubber-band it back.
222
309
  */
223
- declare function fitColumnsToWidth<Row, Header>(resolved: readonly ResolvedColumn<Row, Header>[], availableWidth: number, defaults?: Partial<ColumnSizeDefaults>): readonly ResolvedColumn<Row, Header>[];
310
+ declare function fitColumnsToWidth<Row, Node>(resolved: readonly ResolvedColumn<Row, Node>[], availableWidth: number, defaults?: Partial<ColumnSizeDefaults>): readonly ResolvedColumn<Row, Node>[];
224
311
  /**
225
312
  * The width a column needs to show `measuredWidth` of content.
226
313
  *
@@ -229,4 +316,4 @@ declare function fitColumnsToWidth<Row, Header>(resolved: readonly ResolvedColum
229
316
  */
230
317
  declare function sizeColumnToContent(measuredWidth: number, constraints: ColumnConstraints, padding?: number): number;
231
318
 
232
- export { type ColumnAlignment, type ColumnConstraints, type ColumnDefinition, type ColumnResizeEvent, type ColumnResizeSession, type ColumnResolveOptions, type ColumnSizeDefaults, type ColumnSizingState, type ColumnType, DEFAULT_COLUMN_SIZES, type FieldPath, KEYBOARD_STEP, type ResolvedColumn, accessDotted, alignmentForType, applyColumnResize, beginColumnResize, clampColumnWidth, defineColumnsFromRows, fitColumnsToWidth, getColumnId, resolveColumnConstraints, resolveColumnLabel, resolveColumnWidths, sizeColumnToContent, totalColumnWidth };
319
+ export { type CellTemplateContext, type ColumnAlignment, type ColumnConstraints, type ColumnDefinition, type ColumnOrderEvent, type ColumnOrderState, type ColumnResizeEvent, type ColumnResizeSession, type ColumnResolveOptions, type ColumnSizeDefaults, type ColumnSizingState, type ColumnType, DEFAULT_COLUMN_SIZES, type DropSide, type FieldPath, KEYBOARD_STEP, type ResolvedColumn, accessDotted, alignmentForType, applyColumnOrder, applyColumnResize, beginColumnResize, clampColumnWidth, defineColumnsFromRows, fitColumnsToWidth, getColumnId, moveColumnBefore, movesColumn, resolveColumnConstraints, resolveColumnLabel, resolveColumnWidths, resolveDropBefore, sizeColumnToContent, totalColumnWidth };
package/dist/index.js CHANGED
@@ -61,16 +61,53 @@ function getColumnId(column) {
61
61
  return column.id ?? column.field;
62
62
  }
63
63
  function resolveColumnLabel(column) {
64
- const { header } = column;
65
- if (header === void 0) {
64
+ const { headerTemplate } = column;
65
+ if (headerTemplate === void 0) {
66
66
  return column.field.split(".").join(" ");
67
67
  }
68
- return typeof header === "function" ? header() : header;
68
+ return typeof headerTemplate === "function" ? headerTemplate() : headerTemplate;
69
69
  }
70
70
  function accessDotted(obj, path) {
71
71
  return path.split(".").reduce((value, key) => isNested(value) ? value[key] : void 0, obj);
72
72
  }
73
73
 
74
+ // src/util/ordering.ts
75
+ function applyColumnOrder(columns, order) {
76
+ if (order.length === 0) {
77
+ return columns;
78
+ }
79
+ const byId = new Map(columns.map((column) => [getColumnId(column), column]));
80
+ const listed = order.map((id) => byId.get(id)).filter((column) => column !== void 0);
81
+ const taken = new Set(listed.map((column) => getColumnId(column)));
82
+ return [
83
+ ...listed,
84
+ ...columns.filter((column) => !taken.has(getColumnId(column)))
85
+ ];
86
+ }
87
+ function moveColumnBefore(orderedIds, movedId, beforeId) {
88
+ if (movedId === beforeId || !orderedIds.includes(movedId)) {
89
+ return orderedIds;
90
+ }
91
+ const without = orderedIds.filter((id) => id !== movedId);
92
+ const target = beforeId === null ? without.length : without.indexOf(beforeId);
93
+ if (target === -1) {
94
+ return orderedIds;
95
+ }
96
+ const next = [...without];
97
+ next.splice(target, 0, movedId);
98
+ return next.every((id, index) => id === orderedIds[index]) ? orderedIds : next;
99
+ }
100
+ function movesColumn(orderedIds, movedId, beforeId) {
101
+ return moveColumnBefore(orderedIds, movedId, beforeId) !== orderedIds;
102
+ }
103
+ function resolveDropBefore(orderedIds, targetId, side) {
104
+ if (side === "before") {
105
+ return targetId;
106
+ }
107
+ const index = orderedIds.indexOf(targetId);
108
+ return index === -1 ? null : orderedIds[index + 1] ?? null;
109
+ }
110
+
74
111
  // src/util/sizing.ts
75
112
  var DEFAULT_COLUMN_SIZES = {
76
113
  width: 150,
@@ -106,6 +143,7 @@ function resolveColumnWidths(columns, sizing, options) {
106
143
  ),
107
144
  label: resolveColumnLabel(column),
108
145
  resizable: column.resizable ?? options?.resizable ?? false,
146
+ reorderable: column.reorderable ?? options?.reorderable ?? false,
109
147
  alignment: column.alignment ?? alignmentForType(column.type ?? "string")
110
148
  };
111
149
  });
@@ -180,15 +218,19 @@ export {
180
218
  KEYBOARD_STEP,
181
219
  accessDotted,
182
220
  alignmentForType,
221
+ applyColumnOrder,
183
222
  applyColumnResize,
184
223
  beginColumnResize,
185
224
  clampColumnWidth,
186
225
  defineColumnsFromRows,
187
226
  fitColumnsToWidth,
188
227
  getColumnId,
228
+ moveColumnBefore,
229
+ movesColumn,
189
230
  resolveColumnConstraints,
190
231
  resolveColumnLabel,
191
232
  resolveColumnWidths,
233
+ resolveDropBefore,
192
234
  sizeColumnToContent,
193
235
  totalColumnWidth
194
236
  };
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/util/grid.ts","../src/util/sizing.ts"],"sourcesContent":["import type { ColumnAlignment, ColumnDefinition, ColumnType } from \"../types\";\n\n/**\n * Narrows to a value worth drilling into. Mirrors the `LeafValue` union in\n * `../types`: arrays and `Date`s are cell values rather than groups of\n * columns, and `null` cannot be introspected. Keep the two in sync.\n */\nfunction isNested(value: unknown): value is Record<string, unknown> {\n return (\n typeof value === \"object\" &&\n value !== null &&\n !Array.isArray(value) &&\n !(value instanceof Date)\n );\n}\n\nfunction getType(value: unknown): ColumnType {\n if (value instanceof Date) return \"dateTime\";\n if (typeof value === \"string\") return \"string\";\n if (typeof value === \"number\") return \"number\";\n if (typeof value === \"boolean\") return \"boolean\";\n if (typeof value === \"bigint\") return \"number\";\n if (typeof value === \"symbol\") return \"string\";\n return \"string\";\n}\n\n/**\n * How a column of `type` aligns by default. Numeric types align right so that\n * their digits line up by place value; everything else reads from the left.\n */\nexport function alignmentForType(type: ColumnType): ColumnAlignment {\n switch (type) {\n case \"number\":\n case \"decimal\":\n case \"currency\":\n case \"percent\":\n return \"right\";\n default:\n return \"left\";\n }\n}\n\nfunction getAlignment(value: unknown): ColumnAlignment {\n return alignmentForType(getType(value));\n}\n\n/**\n * Derives one column per field across `rows`, in the order each field is first\n * seen — so a key that only appears in a later row still gets a column,\n * appended at the end. Nested objects contribute their non-object properties\n * as `\"Parent.Child\"`; anything deeper is skipped, as is the nested object\n * itself.\n *\n * Order follows `Object.entries`, which lists integer-like keys first\n * regardless of the order they were written in.\n */\nexport function defineColumnsFromRows<Row>(\n rows: readonly Row[],\n): readonly ColumnDefinition<Row>[] {\n const columns: ColumnDefinition<Row>[] = [];\n const seen = new Set<string>();\n\n function add(field: string, value: unknown): void {\n if (seen.has(field)) {\n return;\n }\n seen.add(field);\n columns.push({\n field,\n type: getType(value),\n alignment: getAlignment(value),\n });\n }\n\n for (const row of rows) {\n if (!isNested(row)) {\n continue;\n }\n for (const [key, value] of Object.entries(row)) {\n if (!isNested(value)) {\n add(key, value);\n continue;\n }\n for (const [nestedKey, nestedValue] of Object.entries(value)) {\n if (!isNested(nestedValue)) {\n add(`${key}.${nestedKey}`, nestedValue);\n }\n }\n }\n }\n\n return columns;\n}\n\n/**\n * The key state about a column is stored under, and the key it renders with.\n * Every such lookup goes through here so the two can never disagree.\n */\nexport function getColumnId<Row>(\n column: ColumnDefinition<Row, unknown>,\n): string {\n return column.id ?? column.field;\n}\n\n/**\n * What a column's header shows: its own `header`, called first if it is a\n * function, or else a label read off the field path — `\"Application.Id\"`\n * reading as `\"Application Id\"`.\n */\nexport function resolveColumnLabel<Row, Header>(\n column: ColumnDefinition<Row, Header>,\n): Header | string {\n const { header } = column;\n if (header === undefined) {\n return column.field.split(\".\").join(\" \");\n }\n // `Header` is open, so it could itself be a function type and the `typeof`\n // check cannot narrow on its own. A header that is callable is the lazy\n // form by definition — no adapter binds `Header` to a function.\n return typeof header === \"function\" ? (header as () => Header)() : header;\n}\n\n/**\n * Accesses a value at a dotted path, e.g. `foo.bar.baz`.\n *\n * @param obj The object to access\n * @param path Path to the value, e.g. `\"foo.bar\"`\n * @returns The value at `path`, or `undefined` if it doesn't exist\n */\nexport function accessDotted(obj: unknown, path: string): unknown {\n return path\n .split(\".\")\n .reduce((value, key) => (isNested(value) ? value[key] : undefined), obj);\n}\n","import type {\n ColumnConstraints,\n ColumnDefinition,\n ColumnResizeSession,\n ColumnResolveOptions,\n ColumnSizeDefaults,\n ColumnSizingState,\n ResolvedColumn,\n} from \"../types\";\nimport { alignmentForType, getColumnId, resolveColumnLabel } from \"./grid\";\n\n/**\n * Applied to any column that does not size itself. The minimum is a width a\n * header stays legible at rather than a measured value — a column dragged\n * narrower than this reads as a rendering fault.\n */\nexport const DEFAULT_COLUMN_SIZES: ColumnSizeDefaults = {\n width: 150,\n minWidth: 40,\n maxWidth: Number.POSITIVE_INFINITY,\n};\n\n/** How far one arrow key press moves a column edge. */\nexport const KEYBOARD_STEP = 10;\n\nfunction withDefaults(\n defaults?: Partial<ColumnSizeDefaults>,\n): ColumnSizeDefaults {\n return { ...DEFAULT_COLUMN_SIZES, ...defaults };\n}\n\n/**\n * The bounds `column` may be sized between. Resolved in one place so that a\n * drag, a clamp and an auto-fit cannot disagree about a column's limits.\n */\nexport function resolveColumnConstraints<Row>(\n column: ColumnDefinition<Row, unknown>,\n defaults?: Partial<ColumnSizeDefaults>,\n): ColumnConstraints {\n const resolved = withDefaults(defaults);\n return {\n minWidth: column.minWidth ?? resolved.minWidth,\n maxWidth: column.maxWidth ?? resolved.maxWidth,\n };\n}\n\n/** Holds `width` within `constraints`. */\nexport function clampColumnWidth(\n width: number,\n constraints: ColumnConstraints,\n): number {\n return Math.min(Math.max(width, constraints.minWidth), constraints.maxWidth);\n}\n\n/**\n * Pairs each column with everything needed to render it — the width it sits\n * at, the label its header shows, whether it may be resized and how its cells\n * align. Widths take the first of the sizing state, the column's own `width`,\n * and the default, then clamp.\n *\n * The sizing state winning means a user's drag outlives a re-render; a column\n * absent from it still tracks a `width` edited in the definition.\n */\nexport function resolveColumnWidths<Row, Header>(\n columns: readonly ColumnDefinition<Row, Header>[],\n sizing: ColumnSizingState,\n options?: ColumnResolveOptions,\n): readonly ResolvedColumn<Row, Header>[] {\n const resolved = withDefaults(options?.sizes);\n\n return columns.map((column) => {\n const id = getColumnId(column);\n const stored = sizing[id];\n\n return {\n column,\n id,\n sized: stored !== undefined,\n width: clampColumnWidth(\n stored ?? column.width ?? resolved.width,\n resolveColumnConstraints(column, resolved),\n ),\n label: resolveColumnLabel(column),\n resizable: column.resizable ?? options?.resizable ?? false,\n alignment: column.alignment ?? alignmentForType(column.type ?? \"string\"),\n };\n });\n}\n\nexport function totalColumnWidth<Row, Header>(\n resolved: readonly ResolvedColumn<Row, Header>[],\n): number {\n return resolved.reduce((total, entry) => total + entry.width, 0);\n}\n\n/**\n * Opens a resize. The session captures its constraints and starting point up\n * front, so applying a pointer position later is arithmetic on numbers alone —\n * which is what keeps the drag testable without a DOM.\n *\n * @param startPosition Where the pointer went down, on the axis being dragged.\n */\nexport function beginColumnResize<Row>(\n column: ColumnDefinition<Row, unknown>,\n startWidth: number,\n startPosition: number,\n defaults?: Partial<ColumnSizeDefaults>,\n): ColumnResizeSession {\n return {\n columnId: getColumnId(column),\n startWidth,\n startPosition,\n constraints: resolveColumnConstraints(column, defaults),\n };\n}\n\n/**\n * The width the dragged column takes with the pointer at `position`. The delta\n * is signed, so a right-to-left adapter negates it and nothing else changes.\n */\nexport function applyColumnResize(\n session: ColumnResizeSession,\n position: number,\n): number {\n return clampColumnWidth(\n session.startWidth + (position - session.startPosition),\n session.constraints,\n );\n}\n\n/**\n * Grows columns proportionally until they fill `availableWidth`.\n *\n * Growth only: columns that already meet or exceed the width are returned\n * untouched, so a container too narrow for them scrolls rather than crushing\n * them. Columns the user has sized are left at that width and the rest share\n * what is left, so dragging one column does not rubber-band it back.\n */\nexport function fitColumnsToWidth<Row, Header>(\n resolved: readonly ResolvedColumn<Row, Header>[],\n availableWidth: number,\n defaults?: Partial<ColumnSizeDefaults>,\n): readonly ResolvedColumn<Row, Header>[] {\n const target = Math.floor(availableWidth);\n if (resolved.length === 0 || totalColumnWidth(resolved) >= target) {\n return resolved;\n }\n\n const entries = resolved.map((entry) => ({\n entry,\n width: entry.width,\n maxWidth: resolveColumnConstraints(entry.column, defaults).maxWidth,\n growable: !entry.sized,\n }));\n const sum = () => entries.reduce((total, item) => total + item.width, 0);\n\n // Clamping a column at its maximum frees surplus the others have to absorb,\n // so a pass that clamped anything is followed by another.\n for (;;) {\n const growing = entries.filter(\n (item) => item.growable && item.width < item.maxWidth,\n );\n const surplus = target - sum();\n if (surplus <= 0 || growing.length === 0) {\n break;\n }\n\n const base = growing.reduce((total, item) => total + item.width, 0);\n let clamped = false;\n\n for (const item of growing) {\n const share =\n base === 0 ? surplus / growing.length : (surplus * item.width) / base;\n\n if (item.width + share >= item.maxWidth) {\n item.width = item.maxWidth;\n clamped = true;\n } else {\n item.width += share;\n }\n }\n\n if (!clamped) {\n break;\n }\n }\n\n // Fractional widths would leave the table a sub-pixel short of its container\n // and show a scrollbar that never goes away, so floor every column and hand\n // the lost pixels back out one at a time.\n let remainder = target - entries.reduce((t, i) => t + Math.floor(i.width), 0);\n\n return entries.map((item) => {\n let width = Math.floor(item.width);\n if (remainder > 0 && item.growable && width < item.maxWidth) {\n width += 1;\n remainder -= 1;\n }\n return { ...item.entry, width };\n });\n}\n\n/**\n * The width a column needs to show `measuredWidth` of content.\n *\n * `padding` is an allowance on top of the measurement, since a column sized to\n * exactly its content can still round down into an ellipsis.\n */\nexport function sizeColumnToContent(\n measuredWidth: number,\n constraints: ColumnConstraints,\n padding = 2,\n): number {\n return clampColumnWidth(Math.ceil(measuredWidth + padding), constraints);\n}\n"],"mappings":";AAOA,SAAS,SAAS,OAAkD;AAClE,SACE,OAAO,UAAU,YACjB,UAAU,QACV,CAAC,MAAM,QAAQ,KAAK,KACpB,EAAE,iBAAiB;AAEvB;AAEA,SAAS,QAAQ,OAA4B;AAC3C,MAAI,iBAAiB,KAAM,QAAO;AAClC,MAAI,OAAO,UAAU,SAAU,QAAO;AACtC,MAAI,OAAO,UAAU,SAAU,QAAO;AACtC,MAAI,OAAO,UAAU,UAAW,QAAO;AACvC,MAAI,OAAO,UAAU,SAAU,QAAO;AACtC,MAAI,OAAO,UAAU,SAAU,QAAO;AACtC,SAAO;AACT;AAMO,SAAS,iBAAiB,MAAmC;AAClE,UAAQ,MAAM;AAAA,IACZ,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AACH,aAAO;AAAA,IACT;AACE,aAAO;AAAA,EACX;AACF;AAEA,SAAS,aAAa,OAAiC;AACrD,SAAO,iBAAiB,QAAQ,KAAK,CAAC;AACxC;AAYO,SAAS,sBACd,MACkC;AAClC,QAAM,UAAmC,CAAC;AAC1C,QAAM,OAAO,oBAAI,IAAY;AAE7B,WAAS,IAAI,OAAe,OAAsB;AAChD,QAAI,KAAK,IAAI,KAAK,GAAG;AACnB;AAAA,IACF;AACA,SAAK,IAAI,KAAK;AACd,YAAQ,KAAK;AAAA,MACX;AAAA,MACA,MAAM,QAAQ,KAAK;AAAA,MACnB,WAAW,aAAa,KAAK;AAAA,IAC/B,CAAC;AAAA,EACH;AAEA,aAAW,OAAO,MAAM;AACtB,QAAI,CAAC,SAAS,GAAG,GAAG;AAClB;AAAA,IACF;AACA,eAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,GAAG,GAAG;AAC9C,UAAI,CAAC,SAAS,KAAK,GAAG;AACpB,YAAI,KAAK,KAAK;AACd;AAAA,MACF;AACA,iBAAW,CAAC,WAAW,WAAW,KAAK,OAAO,QAAQ,KAAK,GAAG;AAC5D,YAAI,CAAC,SAAS,WAAW,GAAG;AAC1B,cAAI,GAAG,GAAG,IAAI,SAAS,IAAI,WAAW;AAAA,QACxC;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAEA,SAAO;AACT;AAMO,SAAS,YACd,QACQ;AACR,SAAO,OAAO,MAAM,OAAO;AAC7B;AAOO,SAAS,mBACd,QACiB;AACjB,QAAM,EAAE,OAAO,IAAI;AACnB,MAAI,WAAW,QAAW;AACxB,WAAO,OAAO,MAAM,MAAM,GAAG,EAAE,KAAK,GAAG;AAAA,EACzC;AAIA,SAAO,OAAO,WAAW,aAAc,OAAwB,IAAI;AACrE;AASO,SAAS,aAAa,KAAc,MAAuB;AAChE,SAAO,KACJ,MAAM,GAAG,EACT,OAAO,CAAC,OAAO,QAAS,SAAS,KAAK,IAAI,MAAM,GAAG,IAAI,QAAY,GAAG;AAC3E;;;ACrHO,IAAM,uBAA2C;AAAA,EACtD,OAAO;AAAA,EACP,UAAU;AAAA,EACV,UAAU,OAAO;AACnB;AAGO,IAAM,gBAAgB;AAE7B,SAAS,aACP,UACoB;AACpB,SAAO,EAAE,GAAG,sBAAsB,GAAG,SAAS;AAChD;AAMO,SAAS,yBACd,QACA,UACmB;AACnB,QAAM,WAAW,aAAa,QAAQ;AACtC,SAAO;AAAA,IACL,UAAU,OAAO,YAAY,SAAS;AAAA,IACtC,UAAU,OAAO,YAAY,SAAS;AAAA,EACxC;AACF;AAGO,SAAS,iBACd,OACA,aACQ;AACR,SAAO,KAAK,IAAI,KAAK,IAAI,OAAO,YAAY,QAAQ,GAAG,YAAY,QAAQ;AAC7E;AAWO,SAAS,oBACd,SACA,QACA,SACwC;AACxC,QAAM,WAAW,aAAa,SAAS,KAAK;AAE5C,SAAO,QAAQ,IAAI,CAAC,WAAW;AAC7B,UAAM,KAAK,YAAY,MAAM;AAC7B,UAAM,SAAS,OAAO,EAAE;AAExB,WAAO;AAAA,MACL;AAAA,MACA;AAAA,MACA,OAAO,WAAW;AAAA,MAClB,OAAO;AAAA,QACL,UAAU,OAAO,SAAS,SAAS;AAAA,QACnC,yBAAyB,QAAQ,QAAQ;AAAA,MAC3C;AAAA,MACA,OAAO,mBAAmB,MAAM;AAAA,MAChC,WAAW,OAAO,aAAa,SAAS,aAAa;AAAA,MACrD,WAAW,OAAO,aAAa,iBAAiB,OAAO,QAAQ,QAAQ;AAAA,IACzE;AAAA,EACF,CAAC;AACH;AAEO,SAAS,iBACd,UACQ;AACR,SAAO,SAAS,OAAO,CAAC,OAAO,UAAU,QAAQ,MAAM,OAAO,CAAC;AACjE;AASO,SAAS,kBACd,QACA,YACA,eACA,UACqB;AACrB,SAAO;AAAA,IACL,UAAU,YAAY,MAAM;AAAA,IAC5B;AAAA,IACA;AAAA,IACA,aAAa,yBAAyB,QAAQ,QAAQ;AAAA,EACxD;AACF;AAMO,SAAS,kBACd,SACA,UACQ;AACR,SAAO;AAAA,IACL,QAAQ,cAAc,WAAW,QAAQ;AAAA,IACzC,QAAQ;AAAA,EACV;AACF;AAUO,SAAS,kBACd,UACA,gBACA,UACwC;AACxC,QAAM,SAAS,KAAK,MAAM,cAAc;AACxC,MAAI,SAAS,WAAW,KAAK,iBAAiB,QAAQ,KAAK,QAAQ;AACjE,WAAO;AAAA,EACT;AAEA,QAAM,UAAU,SAAS,IAAI,CAAC,WAAW;AAAA,IACvC;AAAA,IACA,OAAO,MAAM;AAAA,IACb,UAAU,yBAAyB,MAAM,QAAQ,QAAQ,EAAE;AAAA,IAC3D,UAAU,CAAC,MAAM;AAAA,EACnB,EAAE;AACF,QAAM,MAAM,MAAM,QAAQ,OAAO,CAAC,OAAO,SAAS,QAAQ,KAAK,OAAO,CAAC;AAIvE,aAAS;AACP,UAAM,UAAU,QAAQ;AAAA,MACtB,CAAC,SAAS,KAAK,YAAY,KAAK,QAAQ,KAAK;AAAA,IAC/C;AACA,UAAM,UAAU,SAAS,IAAI;AAC7B,QAAI,WAAW,KAAK,QAAQ,WAAW,GAAG;AACxC;AAAA,IACF;AAEA,UAAM,OAAO,QAAQ,OAAO,CAAC,OAAO,SAAS,QAAQ,KAAK,OAAO,CAAC;AAClE,QAAI,UAAU;AAEd,eAAW,QAAQ,SAAS;AAC1B,YAAM,QACJ,SAAS,IAAI,UAAU,QAAQ,SAAU,UAAU,KAAK,QAAS;AAEnE,UAAI,KAAK,QAAQ,SAAS,KAAK,UAAU;AACvC,aAAK,QAAQ,KAAK;AAClB,kBAAU;AAAA,MACZ,OAAO;AACL,aAAK,SAAS;AAAA,MAChB;AAAA,IACF;AAEA,QAAI,CAAC,SAAS;AACZ;AAAA,IACF;AAAA,EACF;AAKA,MAAI,YAAY,SAAS,QAAQ,OAAO,CAAC,GAAG,MAAM,IAAI,KAAK,MAAM,EAAE,KAAK,GAAG,CAAC;AAE5E,SAAO,QAAQ,IAAI,CAAC,SAAS;AAC3B,QAAI,QAAQ,KAAK,MAAM,KAAK,KAAK;AACjC,QAAI,YAAY,KAAK,KAAK,YAAY,QAAQ,KAAK,UAAU;AAC3D,eAAS;AACT,mBAAa;AAAA,IACf;AACA,WAAO,EAAE,GAAG,KAAK,OAAO,MAAM;AAAA,EAChC,CAAC;AACH;AAQO,SAAS,oBACd,eACA,aACA,UAAU,GACF;AACR,SAAO,iBAAiB,KAAK,KAAK,gBAAgB,OAAO,GAAG,WAAW;AACzE;","names":[]}
1
+ {"version":3,"sources":["../src/util/grid.ts","../src/util/ordering.ts","../src/util/sizing.ts"],"sourcesContent":["import type { ColumnAlignment, ColumnDefinition, ColumnType } from \"../types\";\n\n/**\n * Narrows to a value worth drilling into. Mirrors the `LeafValue` union in\n * `../types`: arrays and `Date`s are cell values rather than groups of\n * columns, and `null` cannot be introspected. Keep the two in sync.\n */\nfunction isNested(value: unknown): value is Record<string, unknown> {\n return (\n typeof value === \"object\" &&\n value !== null &&\n !Array.isArray(value) &&\n !(value instanceof Date)\n );\n}\n\nfunction getType(value: unknown): ColumnType {\n if (value instanceof Date) return \"dateTime\";\n if (typeof value === \"string\") return \"string\";\n if (typeof value === \"number\") return \"number\";\n if (typeof value === \"boolean\") return \"boolean\";\n if (typeof value === \"bigint\") return \"number\";\n if (typeof value === \"symbol\") return \"string\";\n return \"string\";\n}\n\n/**\n * How a column of `type` aligns by default. Numeric types align right so that\n * their digits line up by place value; everything else reads from the left.\n */\nexport function alignmentForType(type: ColumnType): ColumnAlignment {\n switch (type) {\n case \"number\":\n case \"decimal\":\n case \"currency\":\n case \"percent\":\n return \"right\";\n default:\n return \"left\";\n }\n}\n\nfunction getAlignment(value: unknown): ColumnAlignment {\n return alignmentForType(getType(value));\n}\n\n/**\n * Derives one column per field across `rows`, in the order each field is first\n * seen — so a key that only appears in a later row still gets a column,\n * appended at the end. Nested objects contribute their non-object properties\n * as `\"Parent.Child\"`; anything deeper is skipped, as is the nested object\n * itself.\n *\n * Order follows `Object.entries`, which lists integer-like keys first\n * regardless of the order they were written in.\n */\nexport function defineColumnsFromRows<Row>(\n rows: readonly Row[],\n): readonly ColumnDefinition<Row>[] {\n const columns: ColumnDefinition<Row>[] = [];\n const seen = new Set<string>();\n\n function add(field: string, value: unknown): void {\n if (seen.has(field)) {\n return;\n }\n seen.add(field);\n columns.push({\n field,\n type: getType(value),\n alignment: getAlignment(value),\n });\n }\n\n for (const row of rows) {\n if (!isNested(row)) {\n continue;\n }\n for (const [key, value] of Object.entries(row)) {\n if (!isNested(value)) {\n add(key, value);\n continue;\n }\n for (const [nestedKey, nestedValue] of Object.entries(value)) {\n if (!isNested(nestedValue)) {\n add(`${key}.${nestedKey}`, nestedValue);\n }\n }\n }\n }\n\n return columns;\n}\n\n/**\n * The key state about a column is stored under, and the key it renders with.\n * Every such lookup goes through here so the two can never disagree.\n */\nexport function getColumnId<Row>(\n column: ColumnDefinition<Row, unknown>,\n): string {\n return column.id ?? column.field;\n}\n\n/**\n * What a column's header shows: its own `headerTemplate`, called first if it is\n * a function, or else a label read off the field path — `\"Application.Id\"`\n * reading as `\"Application Id\"`.\n */\nexport function resolveColumnLabel<Row, Node>(\n column: ColumnDefinition<Row, Node>,\n): Node | string {\n const { headerTemplate } = column;\n if (headerTemplate === undefined) {\n return column.field.split(\".\").join(\" \");\n }\n // `Node` is open, so it could itself be a function type and the `typeof`\n // check cannot narrow on its own. A header that is callable is the lazy\n // form by definition — no adapter binds `Node` to a function.\n return typeof headerTemplate === \"function\"\n ? (headerTemplate as () => Node)()\n : headerTemplate;\n}\n\n/**\n * Accesses a value at a dotted path, e.g. `foo.bar.baz`.\n *\n * @param obj The object to access\n * @param path Path to the value, e.g. `\"foo.bar\"`\n * @returns The value at `path`, or `undefined` if it doesn't exist\n */\nexport function accessDotted(obj: unknown, path: string): unknown {\n return path\n .split(\".\")\n .reduce((value, key) => (isNested(value) ? value[key] : undefined), obj);\n}\n","import type { ColumnDefinition, ColumnOrderState, DropSide } from \"../types\";\nimport { getColumnId } from \"./grid\";\n\n/**\n * Puts `columns` in the order `order` gives. Ids it lists come first, in that\n * order; columns it omits keep their position among the definitions and\n * follow. Ids naming no column are ignored.\n *\n * Tolerating both gaps is what lets a stored order outlive the column list it\n * was made against.\n */\nexport function applyColumnOrder<Row, Node>(\n columns: readonly ColumnDefinition<Row, Node>[],\n order: ColumnOrderState,\n): readonly ColumnDefinition<Row, Node>[] {\n if (order.length === 0) {\n return columns;\n }\n\n const byId = new Map(columns.map((column) => [getColumnId(column), column]));\n const listed = order\n .map((id) => byId.get(id))\n .filter((column) => column !== undefined);\n const taken = new Set(listed.map((column) => getColumnId(column)));\n\n return [\n ...listed,\n ...columns.filter((column) => !taken.has(getColumnId(column))),\n ];\n}\n\n/**\n * The order with `movedId` lifted out and put back in front of `beforeId`, or\n * at the end when that is `null`.\n *\n * Returns `orderedIds` itself when the move changes nothing, so callers can\n * treat an unchanged reference as \"no move happened\".\n */\nexport function moveColumnBefore(\n orderedIds: ColumnOrderState,\n movedId: string,\n beforeId: string | null,\n): ColumnOrderState {\n if (movedId === beforeId || !orderedIds.includes(movedId)) {\n return orderedIds;\n }\n\n const without = orderedIds.filter((id) => id !== movedId);\n // Taken after the removal: an index read before it would be one too far\n // right for every move that travels rightwards.\n const target = beforeId === null ? without.length : without.indexOf(beforeId);\n if (target === -1) {\n return orderedIds;\n }\n\n const next = [...without];\n next.splice(target, 0, movedId);\n return next.every((id, index) => id === orderedIds[index])\n ? orderedIds\n : next;\n}\n\n/**\n * Whether moving `movedId` in front of `beforeId` would rearrange anything, so\n * that a drop indicator promises exactly the moves that happen.\n *\n * Defined against `moveColumnBefore` rather than restating its conditions, so\n * the two cannot disagree about what a no-op is.\n */\nexport function movesColumn(\n orderedIds: ColumnOrderState,\n movedId: string,\n beforeId: string | null,\n): boolean {\n return moveColumnBefore(orderedIds, movedId, beforeId) !== orderedIds;\n}\n\n/**\n * The id a drop on `side` of `targetId` lands in front of, which is `null`\n * past the last column.\n *\n * \"After C\" and \"before D\" name the same gap, so collapsing both to one id\n * keeps a drop target a single nullable string.\n */\nexport function resolveDropBefore(\n orderedIds: ColumnOrderState,\n targetId: string,\n side: DropSide,\n): string | null {\n if (side === \"before\") {\n return targetId;\n }\n const index = orderedIds.indexOf(targetId);\n return index === -1 ? null : (orderedIds[index + 1] ?? null);\n}\n","import type {\n ColumnConstraints,\n ColumnDefinition,\n ColumnResizeSession,\n ColumnResolveOptions,\n ColumnSizeDefaults,\n ColumnSizingState,\n ResolvedColumn,\n} from \"../types\";\nimport { alignmentForType, getColumnId, resolveColumnLabel } from \"./grid\";\n\n/**\n * Applied to any column that does not size itself. The minimum is a width a\n * header stays legible at rather than a measured value — a column dragged\n * narrower than this reads as a rendering fault.\n */\nexport const DEFAULT_COLUMN_SIZES: ColumnSizeDefaults = {\n width: 150,\n minWidth: 40,\n maxWidth: Number.POSITIVE_INFINITY,\n};\n\n/** How far one arrow key press moves a column edge. */\nexport const KEYBOARD_STEP = 10;\n\nfunction withDefaults(\n defaults?: Partial<ColumnSizeDefaults>,\n): ColumnSizeDefaults {\n return { ...DEFAULT_COLUMN_SIZES, ...defaults };\n}\n\n/**\n * The bounds `column` may be sized between. Resolved in one place so that a\n * drag, a clamp and an auto-fit cannot disagree about a column's limits.\n */\nexport function resolveColumnConstraints<Row>(\n column: ColumnDefinition<Row, unknown>,\n defaults?: Partial<ColumnSizeDefaults>,\n): ColumnConstraints {\n const resolved = withDefaults(defaults);\n return {\n minWidth: column.minWidth ?? resolved.minWidth,\n maxWidth: column.maxWidth ?? resolved.maxWidth,\n };\n}\n\n/** Holds `width` within `constraints`. */\nexport function clampColumnWidth(\n width: number,\n constraints: ColumnConstraints,\n): number {\n return Math.min(Math.max(width, constraints.minWidth), constraints.maxWidth);\n}\n\n/**\n * Pairs each column with everything needed to render it — the width it sits\n * at, the label its header shows, whether it may be resized and how its cells\n * align. Widths take the first of the sizing state, the column's own `width`,\n * and the default, then clamp.\n *\n * The sizing state winning means a user's drag outlives a re-render; a column\n * absent from it still tracks a `width` edited in the definition.\n */\nexport function resolveColumnWidths<Row, Node>(\n columns: readonly ColumnDefinition<Row, Node>[],\n sizing: ColumnSizingState,\n options?: ColumnResolveOptions,\n): readonly ResolvedColumn<Row, Node>[] {\n const resolved = withDefaults(options?.sizes);\n\n return columns.map((column) => {\n const id = getColumnId(column);\n const stored = sizing[id];\n\n return {\n column,\n id,\n sized: stored !== undefined,\n width: clampColumnWidth(\n stored ?? column.width ?? resolved.width,\n resolveColumnConstraints(column, resolved),\n ),\n label: resolveColumnLabel(column),\n resizable: column.resizable ?? options?.resizable ?? false,\n reorderable: column.reorderable ?? options?.reorderable ?? false,\n alignment: column.alignment ?? alignmentForType(column.type ?? \"string\"),\n };\n });\n}\n\nexport function totalColumnWidth<Row, Node>(\n resolved: readonly ResolvedColumn<Row, Node>[],\n): number {\n return resolved.reduce((total, entry) => total + entry.width, 0);\n}\n\n/**\n * Opens a resize. The session captures its constraints and starting point up\n * front, so applying a pointer position later is arithmetic on numbers alone —\n * which is what keeps the drag testable without a DOM.\n *\n * @param startPosition Where the pointer went down, on the axis being dragged.\n */\nexport function beginColumnResize<Row>(\n column: ColumnDefinition<Row, unknown>,\n startWidth: number,\n startPosition: number,\n defaults?: Partial<ColumnSizeDefaults>,\n): ColumnResizeSession {\n return {\n columnId: getColumnId(column),\n startWidth,\n startPosition,\n constraints: resolveColumnConstraints(column, defaults),\n };\n}\n\n/**\n * The width the dragged column takes with the pointer at `position`. The delta\n * is signed, so a right-to-left adapter negates it and nothing else changes.\n */\nexport function applyColumnResize(\n session: ColumnResizeSession,\n position: number,\n): number {\n return clampColumnWidth(\n session.startWidth + (position - session.startPosition),\n session.constraints,\n );\n}\n\n/**\n * Grows columns proportionally until they fill `availableWidth`.\n *\n * Growth only: columns that already meet or exceed the width are returned\n * untouched, so a container too narrow for them scrolls rather than crushing\n * them. Columns the user has sized are left at that width and the rest share\n * what is left, so dragging one column does not rubber-band it back.\n */\nexport function fitColumnsToWidth<Row, Node>(\n resolved: readonly ResolvedColumn<Row, Node>[],\n availableWidth: number,\n defaults?: Partial<ColumnSizeDefaults>,\n): readonly ResolvedColumn<Row, Node>[] {\n const target = Math.floor(availableWidth);\n if (resolved.length === 0 || totalColumnWidth(resolved) >= target) {\n return resolved;\n }\n\n const entries = resolved.map((entry) => ({\n entry,\n width: entry.width,\n maxWidth: resolveColumnConstraints(entry.column, defaults).maxWidth,\n growable: !entry.sized,\n }));\n const sum = () => entries.reduce((total, item) => total + item.width, 0);\n\n // Clamping a column at its maximum frees surplus the others have to absorb,\n // so a pass that clamped anything is followed by another.\n for (;;) {\n const growing = entries.filter(\n (item) => item.growable && item.width < item.maxWidth,\n );\n const surplus = target - sum();\n if (surplus <= 0 || growing.length === 0) {\n break;\n }\n\n const base = growing.reduce((total, item) => total + item.width, 0);\n let clamped = false;\n\n for (const item of growing) {\n const share =\n base === 0 ? surplus / growing.length : (surplus * item.width) / base;\n\n if (item.width + share >= item.maxWidth) {\n item.width = item.maxWidth;\n clamped = true;\n } else {\n item.width += share;\n }\n }\n\n if (!clamped) {\n break;\n }\n }\n\n // Fractional widths would leave the table a sub-pixel short of its container\n // and show a scrollbar that never goes away, so floor every column and hand\n // the lost pixels back out one at a time.\n let remainder = target - entries.reduce((t, i) => t + Math.floor(i.width), 0);\n\n return entries.map((item) => {\n let width = Math.floor(item.width);\n if (remainder > 0 && item.growable && width < item.maxWidth) {\n width += 1;\n remainder -= 1;\n }\n return { ...item.entry, width };\n });\n}\n\n/**\n * The width a column needs to show `measuredWidth` of content.\n *\n * `padding` is an allowance on top of the measurement, since a column sized to\n * exactly its content can still round down into an ellipsis.\n */\nexport function sizeColumnToContent(\n measuredWidth: number,\n constraints: ColumnConstraints,\n padding = 2,\n): number {\n return clampColumnWidth(Math.ceil(measuredWidth + padding), constraints);\n}\n"],"mappings":"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","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gridkitjs/core",
3
- "version": "0.0.1",
3
+ "version": "0.2.0",
4
4
  "description": "Framework-agnostic data-grid logic: column resolution, alignment and sizing.",
5
5
  "license": "MIT",
6
6
  "author": "Blagoja Blazhevski",
@@ -22,19 +22,12 @@
22
22
  "engines": {
23
23
  "node": ">=22"
24
24
  },
25
- "main": "./src/index.ts",
26
- "types": "./src/index.ts",
25
+ "main": "./dist/index.js",
26
+ "types": "./dist/index.d.ts",
27
27
  "exports": {
28
- ".": "./src/index.ts"
29
- },
30
- "publishConfig": {
31
- "main": "./dist/index.js",
32
- "types": "./dist/index.d.ts",
33
- "exports": {
34
- ".": {
35
- "types": "./dist/index.d.ts",
36
- "import": "./dist/index.js"
37
- }
28
+ ".": {
29
+ "types": "./dist/index.d.ts",
30
+ "import": "./dist/index.js"
38
31
  }
39
32
  },
40
33
  "files": [
@@ -42,15 +35,15 @@
42
35
  "LICENSE",
43
36
  "README.md"
44
37
  ],
38
+ "devDependencies": {
39
+ "tsup": "^8.5.1",
40
+ "typescript": "^6.0.3",
41
+ "vitest": "^4.1.10"
42
+ },
45
43
  "scripts": {
46
44
  "build": "tsup",
47
45
  "typecheck": "tsc --noEmit",
48
46
  "lint": "eslint src",
49
47
  "test": "vitest run"
50
- },
51
- "devDependencies": {
52
- "tsup": "^8.5.1",
53
- "typescript": "^6.0.3",
54
- "vitest": "^4.1.10"
55
48
  }
56
- }
49
+ }