@segnavia/format 0.15.0 → 0.16.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.
@@ -0,0 +1,10 @@
1
+ //#region src/board-section-constants.d.ts
2
+ export declare const SECTION_PADDING = 24;
3
+ export declare const SECTION_TITLE_ROOM = 32;
4
+ export declare const SECTION_MIN_SIZE: {
5
+ readonly width: 240;
6
+ readonly height: 160;
7
+ };
8
+ export declare const SECTION_GAP = 16;
9
+ export declare const MAKE_ROOM_LIMIT = 50;
10
+ //#endregion
@@ -0,0 +1,11 @@
1
+ //#region src/board-section-constants.ts
2
+ const SECTION_PADDING = 24;
3
+ const SECTION_TITLE_ROOM = 32;
4
+ const SECTION_MIN_SIZE = {
5
+ width: 240,
6
+ height: 160
7
+ };
8
+ const SECTION_GAP = 16;
9
+ const MAKE_ROOM_LIMIT = 50;
10
+ //#endregion
11
+ export { MAKE_ROOM_LIMIT, SECTION_GAP, SECTION_MIN_SIZE, SECTION_PADDING, SECTION_TITLE_ROOM };
@@ -0,0 +1,20 @@
1
+ import { Board, GridCell } from "./board.js";
2
+ import { Box, Point } from "./deck.js";
3
+ import { BoardShapes } from "./board-geometry.js";
4
+ //#region src/board-section-grid.d.ts
5
+ export type GridDropTarget = {
6
+ anchor: string;
7
+ side: 'before' | 'after';
8
+ axis: 'row' | 'column';
9
+ } | {
10
+ row: number;
11
+ column: number;
12
+ axis: 'cell';
13
+ };
14
+ export declare function deriveSectionCells(board: Board, shapes: BoardShapes, id: string): GridCell[];
15
+ export declare function layoutSection(board: Board, shapes: BoardShapes, id: string): Board;
16
+ export declare function gridDropTarget(board: Board, shapes: BoardShapes, id: string, point: Point): GridDropTarget | undefined;
17
+ export declare function gridDropMarkerBox(board: Board, shapes: BoardShapes, id: string, target: GridDropTarget): Box | undefined;
18
+ export declare function removeFromGrid(board: Board, shapes: BoardShapes, ids: readonly string[]): Board;
19
+ export declare function insertIntoGrid(board: Board, shapes: BoardShapes, id: string, ids: readonly string[], target: GridDropTarget): Board;
20
+ //#endregion
@@ -0,0 +1,353 @@
1
+ import { itemHeight } from "./board-geometry.js";
2
+ import { SECTION_MIN_SIZE } from "./board-section-constants.js";
3
+ //#region src/board-section-grid.ts
4
+ function sectionOf(board, id) {
5
+ return board.sections.find((section) => section.id === id);
6
+ }
7
+ function dimensions(board, shapes, id) {
8
+ const item = board.items.find((entry) => entry.id === id);
9
+ const shape = shapes.items[id];
10
+ if (!item || !shape) throw new Error(`item "${id}" has no supplied shape; supply shapes.items["${id}"]`);
11
+ return {
12
+ width: item.width,
13
+ height: itemHeight(item, shape)
14
+ };
15
+ }
16
+ function deriveSectionCells(board, shapes, id) {
17
+ const section = sectionOf(board, id);
18
+ if (!section) return [];
19
+ const direction = board.direction === "rtl" ? -1 : 1;
20
+ const members = section.items.map((member) => {
21
+ const item = board.items.find((entry) => entry.id === member);
22
+ if (!item) return void 0;
23
+ return {
24
+ id: member,
25
+ x: item.x,
26
+ y: item.y,
27
+ bottom: item.y + dimensions(board, shapes, member).height
28
+ };
29
+ }).filter((item) => item !== void 0).sort((a, b) => a.y - b.y || direction * (a.x - b.x));
30
+ const cells = [];
31
+ let row = -1;
32
+ let bottom = Number.NEGATIVE_INFINITY;
33
+ let column = 0;
34
+ for (const member of members) {
35
+ if (row < 0 || member.y > bottom) {
36
+ row += 1;
37
+ column = 0;
38
+ bottom = member.bottom;
39
+ } else bottom = Math.max(bottom, member.bottom);
40
+ cells.push({
41
+ id: member.id,
42
+ row,
43
+ column
44
+ });
45
+ column += 1;
46
+ }
47
+ return cells;
48
+ }
49
+ function cellsFor(board, shapes, section) {
50
+ const members = new Set(section.items);
51
+ return section.cells?.filter((cell) => members.has(cell.id)).map((cell) => ({ ...cell })) ?? deriveSectionCells(board, shapes, section.id);
52
+ }
53
+ function normalized(cells) {
54
+ const rows = [...new Set(cells.map((cell) => cell.row))].sort((a, b) => a - b);
55
+ const columns = [...new Set(cells.map((cell) => cell.column))].sort((a, b) => a - b);
56
+ const rowIndex = new Map(rows.map((value, index) => [value, index]));
57
+ const columnIndex = new Map(columns.map((value, index) => [value, index]));
58
+ return cells.map((cell) => ({
59
+ ...cell,
60
+ row: rowIndex.get(cell.row) ?? 0,
61
+ column: columnIndex.get(cell.column) ?? 0
62
+ })).sort((a, b) => a.row - b.row || a.column - b.column);
63
+ }
64
+ function trackSizes(board, shapes, cells) {
65
+ const widths = Array.from({ length: Math.max(0, ...cells.map((cell) => cell.column + 1)) }, () => 0);
66
+ const heights = Array.from({ length: Math.max(0, ...cells.map((cell) => cell.row + 1)) }, () => 0);
67
+ for (const cell of cells) {
68
+ const size = dimensions(board, shapes, cell.id);
69
+ widths[cell.column] = Math.max(widths[cell.column] ?? 0, size.width);
70
+ heights[cell.row] = Math.max(heights[cell.row] ?? 0, size.height);
71
+ }
72
+ return {
73
+ widths,
74
+ heights
75
+ };
76
+ }
77
+ function starts(sizes) {
78
+ const result = [];
79
+ let value = 0;
80
+ for (const size of sizes) {
81
+ result.push(value);
82
+ value += size + 16;
83
+ }
84
+ return result;
85
+ }
86
+ function span(sizes) {
87
+ return sizes.reduce((sum, size) => sum + size, 0) + 16 * Math.max(0, sizes.length - 1);
88
+ }
89
+ function sectionGrid(board, shapes, section) {
90
+ const cells = normalized(cellsFor(board, shapes, section));
91
+ const { widths, heights } = trackSizes(board, shapes, cells);
92
+ return {
93
+ cells,
94
+ widths,
95
+ heights,
96
+ columnStarts: starts(widths),
97
+ rowStarts: starts(heights),
98
+ width: span(widths),
99
+ height: span(heights),
100
+ innerX: section.rectangle.x + 24,
101
+ innerY: section.rectangle.y + 24 + 32
102
+ };
103
+ }
104
+ function physicalX(board, grid, logicalStart, width) {
105
+ return board.direction === "rtl" ? grid.innerX + grid.width - logicalStart - width : grid.innerX + logicalStart;
106
+ }
107
+ function layoutSection(board, shapes, id) {
108
+ const section = sectionOf(board, id);
109
+ if (!section || section.layout === "manual") return board;
110
+ if (section.items.length === 0) return {
111
+ ...board,
112
+ sections: board.sections.map((entry) => entry.id === id ? {
113
+ ...entry,
114
+ cells: [],
115
+ rectangle: {
116
+ ...entry.rectangle,
117
+ ...SECTION_MIN_SIZE
118
+ }
119
+ } : entry)
120
+ };
121
+ const grid = sectionGrid(board, shapes, section);
122
+ const { cells } = grid;
123
+ const positions = /* @__PURE__ */ new Map();
124
+ for (const cell of cells) {
125
+ const width = dimensions(board, shapes, cell.id).width;
126
+ positions.set(cell.id, {
127
+ x: physicalX(board, grid, grid.columnStarts[cell.column] ?? 0, width),
128
+ y: grid.innerY + (grid.rowStarts[cell.row] ?? 0)
129
+ });
130
+ }
131
+ return {
132
+ ...board,
133
+ items: board.items.map((item) => {
134
+ const position = positions.get(item.id);
135
+ return position ? {
136
+ ...item,
137
+ ...position
138
+ } : item;
139
+ }),
140
+ sections: board.sections.map((entry) => entry.id === id ? {
141
+ ...entry,
142
+ cells,
143
+ items: cells.map((cell) => cell.id),
144
+ rectangle: {
145
+ ...entry.rectangle,
146
+ width: grid.width + 48,
147
+ height: grid.height + 48 + 32
148
+ }
149
+ } : entry)
150
+ };
151
+ }
152
+ function trackAt(value, sizes, offsets) {
153
+ if (value <= 0) return {
154
+ index: 0,
155
+ gap: false
156
+ };
157
+ for (let index = 0; index < sizes.length; index += 1) {
158
+ const end = (offsets[index] ?? 0) + (sizes[index] ?? 0);
159
+ if (value <= end) return {
160
+ index,
161
+ gap: false
162
+ };
163
+ if (index < sizes.length - 1 && value < end + 16) return {
164
+ index: index + 1,
165
+ gap: true
166
+ };
167
+ }
168
+ return {
169
+ index: Math.max(0, sizes.length - 1),
170
+ gap: false
171
+ };
172
+ }
173
+ function gridDropTarget(board, shapes, id, point) {
174
+ const section = sectionOf(board, id);
175
+ if (!section || section.layout === "manual") return void 0;
176
+ const box = section.rectangle;
177
+ if (point.x < box.x || point.x > box.x + box.width || point.y < box.y || point.y > box.y + box.height) return void 0;
178
+ const grid = sectionGrid(board, shapes, section);
179
+ const { cells } = grid;
180
+ if (cells.length === 0) return {
181
+ row: 0,
182
+ column: 0,
183
+ axis: "cell"
184
+ };
185
+ const xOffsets = grid.columnStarts;
186
+ const yOffsets = grid.rowStarts;
187
+ const fromStart = point.x - grid.innerX;
188
+ const x = board.direction === "rtl" ? grid.width - fromStart : fromStart;
189
+ const y = point.y - grid.innerY;
190
+ const columnTrack = trackAt(x, grid.widths, xOffsets);
191
+ const rowTrack = trackAt(y, grid.heights, yOffsets);
192
+ if (columnTrack.gap) return targetAtGap(cells, rowTrack.index, columnTrack.index, "row");
193
+ if (rowTrack.gap) return targetAtGap(cells, rowTrack.index, columnTrack.index, "column");
194
+ const cell = cells.find((entry) => entry.row === rowTrack.index && entry.column === columnTrack.index);
195
+ if (!cell) return {
196
+ row: rowTrack.index,
197
+ column: columnTrack.index,
198
+ axis: "cell"
199
+ };
200
+ const size = dimensions(board, shapes, cell.id);
201
+ const localX = x - (xOffsets[cell.column] ?? 0);
202
+ const localY = y - (yOffsets[cell.row] ?? 0);
203
+ return [
204
+ {
205
+ distance: Math.abs(localX),
206
+ target: {
207
+ anchor: cell.id,
208
+ side: "before",
209
+ axis: "row"
210
+ }
211
+ },
212
+ {
213
+ distance: Math.abs(size.width - localX),
214
+ target: {
215
+ anchor: cell.id,
216
+ side: "after",
217
+ axis: "row"
218
+ }
219
+ },
220
+ {
221
+ distance: Math.abs(localY),
222
+ target: {
223
+ anchor: cell.id,
224
+ side: "before",
225
+ axis: "column"
226
+ }
227
+ },
228
+ {
229
+ distance: Math.abs(size.height - localY),
230
+ target: {
231
+ anchor: cell.id,
232
+ side: "after",
233
+ axis: "column"
234
+ }
235
+ }
236
+ ].reduce((best, edge) => edge.distance < best.distance ? edge : best).target;
237
+ }
238
+ function targetAtGap(cells, row, column, axis) {
239
+ const track = cells.filter((cell) => axis === "row" ? cell.row === row : cell.column === column).sort((a, b) => axis === "row" ? a.column - b.column : a.row - b.row);
240
+ const coordinate = axis === "row" ? column : row;
241
+ const after = track.find((cell) => (axis === "row" ? cell.column : cell.row) >= coordinate);
242
+ if (after) return {
243
+ anchor: after.id,
244
+ side: "before",
245
+ axis
246
+ };
247
+ const before = track.at(-1);
248
+ return before ? {
249
+ anchor: before.id,
250
+ side: "after",
251
+ axis
252
+ } : void 0;
253
+ }
254
+ function gridDropMarkerBox(board, shapes, id, target) {
255
+ const section = sectionOf(board, id);
256
+ if (!section || section.layout === "manual") return void 0;
257
+ const grid = sectionGrid(board, shapes, section);
258
+ const { innerX, innerY } = grid;
259
+ if (grid.cells.length === 0) return {
260
+ x: innerX,
261
+ y: innerY,
262
+ width: Math.max(0, section.rectangle.width - 48),
263
+ height: Math.max(0, section.rectangle.height - 48 - 32)
264
+ };
265
+ const cell = target.axis === "cell" ? target : grid.cells.find((entry) => entry.id === target.anchor);
266
+ if (!cell) return void 0;
267
+ const columnStart = grid.columnStarts[cell.column] ?? grid.width;
268
+ const rowStart = grid.rowStarts[cell.row] ?? grid.height;
269
+ const width = grid.widths[cell.column] ?? 0;
270
+ const height = grid.heights[cell.row] ?? 0;
271
+ const x = physicalX(board, grid, columnStart, width);
272
+ const y = innerY + rowStart;
273
+ if (target.axis === "cell") return {
274
+ x,
275
+ y,
276
+ width,
277
+ height
278
+ };
279
+ if (target.axis === "row") return {
280
+ x: physicalX(board, grid, columnStart + (target.side === "after" ? width : 0), 0),
281
+ y,
282
+ width: 2,
283
+ height
284
+ };
285
+ return {
286
+ x,
287
+ y: y + (target.side === "after" ? height : 0),
288
+ width,
289
+ height: 2
290
+ };
291
+ }
292
+ function updateSection(section, cells) {
293
+ const next = normalized(cells);
294
+ return {
295
+ ...section,
296
+ cells: next,
297
+ items: next.map((cell) => cell.id)
298
+ };
299
+ }
300
+ function removeFromGrid(board, shapes, ids) {
301
+ const removed = new Set(ids);
302
+ return {
303
+ ...board,
304
+ sections: board.sections.map((section) => {
305
+ if (section.layout === "manual") return {
306
+ ...section,
307
+ items: section.items.filter((id) => !removed.has(id))
308
+ };
309
+ const cells = cellsFor(board, shapes, section);
310
+ const removedCells = cells.filter((cell) => removed.has(cell.id));
311
+ return updateSection(section, cells.filter((cell) => !removed.has(cell.id)).map((cell) => ({
312
+ ...cell,
313
+ column: cell.column - removedCells.filter((gone) => gone.row === cell.row && gone.column < cell.column).length
314
+ })));
315
+ })
316
+ };
317
+ }
318
+ function insertIntoGrid(board, shapes, id, ids, target) {
319
+ const unique = ids.filter((member, index) => ids.indexOf(member) === index);
320
+ const moved = new Set(unique);
321
+ if (target.axis !== "cell" && moved.has(target.anchor)) return board;
322
+ const removed = removeFromGrid(board, shapes, unique);
323
+ const section = sectionOf(removed, id);
324
+ if (!section || section.layout === "manual") return removed;
325
+ const cells = [...section.cells ?? []];
326
+ const anchor = target.axis === "cell" ? void 0 : cells.find((cell) => cell.id === target.anchor);
327
+ if (target.axis !== "cell" && !anchor) return removed;
328
+ const axis = target.axis === "cell" ? "row" : target.axis;
329
+ const row = target.axis === "cell" ? target.row : (anchor?.row ?? 0) + (axis === "column" && target.side === "after" ? 1 : 0);
330
+ const column = target.axis === "cell" ? target.column : (anchor?.column ?? 0) + (axis === "row" && target.side === "after" ? 1 : 0);
331
+ const inserted = unique.map((member, index) => ({
332
+ id: member,
333
+ row: row + (axis === "column" ? index : 0),
334
+ column: column + (axis === "row" ? index : 0)
335
+ }));
336
+ const shifted = inserted.some((placement) => cells.some((cell) => cell.row === placement.row && cell.column === placement.column)) ? cells.map((cell) => {
337
+ if (axis === "row" && cell.row === row && cell.column >= column) return {
338
+ ...cell,
339
+ column: cell.column + unique.length
340
+ };
341
+ if (axis === "column" && cell.column === column && cell.row >= row) return {
342
+ ...cell,
343
+ row: cell.row + unique.length
344
+ };
345
+ return cell;
346
+ }) : cells;
347
+ return {
348
+ ...removed,
349
+ sections: removed.sections.map((entry) => entry.id === id ? updateSection(entry, [...shifted, ...inserted]) : entry)
350
+ };
351
+ }
352
+ //#endregion
353
+ export { deriveSectionCells, gridDropMarkerBox, gridDropTarget, insertIntoGrid, layoutSection, removeFromGrid };
@@ -1,18 +1,12 @@
1
1
  import { Board, BoardSection } from "./board.js";
2
2
  import { Point } from "./deck.js";
3
3
  import { BoardShapes } from "./board-geometry.js";
4
+ import { MAKE_ROOM_LIMIT, SECTION_GAP, SECTION_MIN_SIZE, SECTION_PADDING, SECTION_TITLE_ROOM } from "./board-section-constants.js";
4
5
  //#region src/board-sections.d.ts
5
- export declare const SECTION_PADDING = 24;
6
- export declare const SECTION_TITLE_ROOM = 32;
7
- export declare const SECTION_MIN_SIZE: {
8
- readonly width: 240;
9
- readonly height: 160;
10
- };
11
- export declare const SECTION_GAP = 16;
12
- export declare const MAKE_ROOM_LIMIT = 50;
13
6
  export declare function fitSection(board: Board, shapes: BoardShapes, sectionId: string): Board;
14
7
  export declare function assignMembership(board: Board, shapes: BoardShapes, ids: readonly string[], point: Point, startingSections?: readonly BoardSection[]): Board;
15
8
  export declare function makeRoom(board: Board, shapes: BoardShapes, changed: readonly string[]): Board;
16
9
  export declare function fitTouchedSections(previous: Board, board: Board, shapes: BoardShapes, touched: readonly string[]): Board;
17
10
  export declare function settleBoard(previous: Board, board: Board, shapes: BoardShapes, touched: readonly string[]): Board;
18
- //#endregion
11
+ //#endregion
12
+ export { MAKE_ROOM_LIMIT, SECTION_GAP, SECTION_MIN_SIZE, SECTION_PADDING, SECTION_TITLE_ROOM };
@@ -1,14 +1,8 @@
1
1
  import { moveItems, moveSection } from "./board-edit.js";
2
2
  import { itemHeight } from "./board-geometry.js";
3
+ import { MAKE_ROOM_LIMIT, SECTION_GAP, SECTION_MIN_SIZE, SECTION_PADDING, SECTION_TITLE_ROOM } from "./board-section-constants.js";
4
+ import { layoutSection } from "./board-section-grid.js";
3
5
  //#region src/board-sections.ts
4
- const SECTION_PADDING = 24;
5
- const SECTION_TITLE_ROOM = 32;
6
- const SECTION_MIN_SIZE = {
7
- width: 240,
8
- height: 160
9
- };
10
- const SECTION_GAP = 16;
11
- const MAKE_ROOM_LIMIT = 50;
12
6
  function itemRectangle(board, shapes, id) {
13
7
  const item = board.items.find((entry) => entry.id === id);
14
8
  if (!item) return void 0;
@@ -197,10 +191,14 @@ function touchedSections(previous, board, touched) {
197
191
  return [...previous.sections, ...board.sections].filter((section) => touchedIds.has(section.id) || section.items.some((id) => touchedIds.has(id))).map((section) => section.id).filter((id, index, ids) => ids.indexOf(id) === index);
198
192
  }
199
193
  function fitSections(board, shapes, sectionIds) {
200
- return sectionIds.reduce((value, id) => fitSection(value, shapes, id), board);
194
+ return sectionIds.reduce((value, id) => {
195
+ return value.sections.find((entry) => entry.id === id)?.layout === "manual" ? fitSection(value, shapes, id) : layoutSection(value, shapes, id);
196
+ }, board);
201
197
  }
202
198
  function fitTouchedSections(previous, board, shapes, touched) {
203
- return fitSections(board, shapes, touchedSections(previous, board, touched));
199
+ return touchedSections(previous, board, touched).reduce((value, id) => {
200
+ return value.sections.find((entry) => entry.id === id)?.layout === "manual" ? fitSection(value, shapes, id) : value;
201
+ }, board);
204
202
  }
205
203
  function settleBoard(previous, board, shapes, touched) {
206
204
  const sectionIds = touchedSections(previous, board, touched);
package/dist/board.d.ts CHANGED
@@ -23,6 +23,12 @@ export declare const BoardLabel: z.ZodObject<{
23
23
  y: z.ZodNumber;
24
24
  }, z.core.$strip>;
25
25
  export type BoardLabel = z.infer<typeof BoardLabel>;
26
+ export declare const GridCell: z.ZodObject<{
27
+ id: z.ZodString;
28
+ row: z.ZodNumber;
29
+ column: z.ZodNumber;
30
+ }, z.core.$strip>;
31
+ export type GridCell = z.infer<typeof GridCell>;
26
32
  export declare const BoardSection: z.ZodObject<{
27
33
  id: z.ZodString;
28
34
  title: z.ZodString;
@@ -33,6 +39,15 @@ export declare const BoardSection: z.ZodObject<{
33
39
  height: z.ZodNumber;
34
40
  }, z.core.$strip>;
35
41
  items: z.ZodArray<z.ZodString>;
42
+ layout: z.ZodOptional<z.ZodEnum<{
43
+ auto: "auto";
44
+ manual: "manual";
45
+ }>>;
46
+ cells: z.ZodOptional<z.ZodArray<z.ZodObject<{
47
+ id: z.ZodString;
48
+ row: z.ZodNumber;
49
+ column: z.ZodNumber;
50
+ }, z.core.$strip>>>;
36
51
  }, z.core.$strip>;
37
52
  export type BoardSection = z.infer<typeof BoardSection>;
38
53
  export declare const Board: z.ZodObject<{
@@ -72,6 +87,15 @@ export declare const Board: z.ZodObject<{
72
87
  height: z.ZodNumber;
73
88
  }, z.core.$strip>;
74
89
  items: z.ZodArray<z.ZodString>;
90
+ layout: z.ZodOptional<z.ZodEnum<{
91
+ auto: "auto";
92
+ manual: "manual";
93
+ }>>;
94
+ cells: z.ZodOptional<z.ZodArray<z.ZodObject<{
95
+ id: z.ZodString;
96
+ row: z.ZodNumber;
97
+ column: z.ZodNumber;
98
+ }, z.core.$strip>>>;
75
99
  }, z.core.$strip>>;
76
100
  }, z.core.$strip>;
77
101
  export type Board = z.infer<typeof Board>;
package/dist/board.js CHANGED
@@ -24,11 +24,18 @@ const BoardLabel = z.object({
24
24
  x: z.number(),
25
25
  y: z.number()
26
26
  });
27
+ const GridCell = z.object({
28
+ id: z.string(),
29
+ row: z.number(),
30
+ column: z.number()
31
+ });
27
32
  const BoardSection = z.object({
28
33
  id: z.string(),
29
34
  title: z.string(),
30
35
  rectangle: Box,
31
- items: z.array(z.string())
36
+ items: z.array(z.string()),
37
+ layout: z.enum(["auto", "manual"]).optional(),
38
+ cells: z.array(GridCell).optional()
32
39
  });
33
40
  const Board = z.object({
34
41
  title: z.string(),
@@ -69,7 +76,51 @@ const Board = z.object({
69
76
  message: `section member "${id}" is not a board item id; use the id of an existing item`
70
77
  });
71
78
  });
79
+ if (section.layout === "manual" || !section.cells) return;
80
+ const members = new Set(section.items);
81
+ const positions = /* @__PURE__ */ new Set();
82
+ const cellIds = /* @__PURE__ */ new Set();
83
+ section.cells.forEach((cell, cellIndex) => {
84
+ const path = [
85
+ "sections",
86
+ sectionIndex,
87
+ "cells",
88
+ cellIndex
89
+ ];
90
+ if (!members.has(cell.id)) ctx.addIssue({
91
+ code: "custom",
92
+ path,
93
+ message: `section "${section.id}" cell "${cell.id}" is not a member; use the id of a section member`
94
+ });
95
+ if (!Number.isInteger(cell.row) || cell.row < 0 || !Number.isInteger(cell.column) || cell.column < 0) ctx.addIssue({
96
+ code: "custom",
97
+ path,
98
+ message: `section "${section.id}" cell "${cell.id}" has an invalid track index; use non-negative integer row and column indexes`
99
+ });
100
+ const position = `${cell.row}:${cell.column}`;
101
+ if (positions.has(position)) ctx.addIssue({
102
+ code: "custom",
103
+ path,
104
+ message: `section "${section.id}" cell "${cell.id}" shares position (${cell.row}, ${cell.column}); give every cell a unique position`
105
+ });
106
+ positions.add(position);
107
+ if (cellIds.has(cell.id)) ctx.addIssue({
108
+ code: "custom",
109
+ path,
110
+ message: `section "${section.id}" cell "${cell.id}" is duplicated; provide exactly one cell for each member`
111
+ });
112
+ cellIds.add(cell.id);
113
+ });
114
+ for (const id of section.items) if (!cellIds.has(id)) ctx.addIssue({
115
+ code: "custom",
116
+ path: [
117
+ "sections",
118
+ sectionIndex,
119
+ "cells"
120
+ ],
121
+ message: `section "${section.id}" member "${id}" has no cell; add one cell for every member`
122
+ });
72
123
  });
73
124
  });
74
125
  //#endregion
75
- export { Board, BoardItem, BoardLabel, BoardSection };
126
+ export { Board, BoardItem, BoardLabel, BoardSection, GridCell };
package/dist/index.d.ts CHANGED
@@ -1,9 +1,11 @@
1
- import { Board, BoardInput, BoardItem, BoardLabel, BoardSection } from "./board.js";
1
+ import { Board, BoardInput, BoardItem, BoardLabel, BoardSection, GridCell } from "./board.js";
2
2
  import { Annotation, Box, Cue, DECK_SIZE, Deck, DeckInput, DeckLimits, ImageFit, Manifest, Narration, Point, PublishedShot, SHOT_KEY, Shot, Size, Slide, SlideContent, Word, annotationCueIssues, exceedsLimits } from "./deck.js";
3
3
  import { BoardItemAnchor, MIN_BOARD_ITEM_WIDTH, addItem, moveItems, moveSection, removeItems, resizeItem } from "./board-edit.js";
4
4
  import { ArrangeOptions, BoardShapes, BoardView, MAX_BOARD_ZOOM, MIN_BOARD_ZOOM, ZoomBounds, arrange, boardBounds, boardToScreen, itemAt, itemHeight, revealView, screenToBoard, visibleItems, zoomAt, zoomToFit } from "./board-geometry.js";
5
5
  import { BoardListingGroup, boardListing, boardListingIsEmpty, foldForSearch, nameMatches } from "./board-listing.js";
6
- import { MAKE_ROOM_LIMIT, SECTION_GAP, SECTION_MIN_SIZE, SECTION_PADDING, SECTION_TITLE_ROOM, assignMembership, fitSection, fitTouchedSections, makeRoom, settleBoard } from "./board-sections.js";
6
+ import { GridDropTarget, deriveSectionCells, gridDropMarkerBox, gridDropTarget, insertIntoGrid, layoutSection, removeFromGrid } from "./board-section-grid.js";
7
+ import { MAKE_ROOM_LIMIT, SECTION_GAP, SECTION_MIN_SIZE, SECTION_PADDING, SECTION_TITLE_ROOM } from "./board-section-constants.js";
8
+ import { assignMembership, fitSection, fitTouchedSections, makeRoom, settleBoard } from "./board-sections.js";
7
9
  import { Book, BookContents, BookFrontMatterEntry, BookInput, BookMode, BookPage, BookSection, BookView, BookViewInput, Chapter, isBookContents, isHebrew, noteMarkerIssues } from "./book.js";
8
10
  import { DeckFromSlidesOptions, SlideRow, deckFromSlides } from "./deck-from-slides.js";
9
11
  import { Direction, TextDirection, firstStrongDirection, languageDirection, resolveDirection } from "./direction.js";
@@ -718,8 +720,17 @@ export declare function jsonSchemas(): {
718
720
  height: z.ZodNumber;
719
721
  }, z.core.$strip>;
720
722
  items: z.ZodArray<z.ZodString>;
723
+ layout: z.ZodOptional<z.ZodEnum<{
724
+ auto: "auto";
725
+ manual: "manual";
726
+ }>>;
727
+ cells: z.ZodOptional<z.ZodArray<z.ZodObject<{
728
+ id: z.ZodString;
729
+ row: z.ZodNumber;
730
+ column: z.ZodNumber;
731
+ }, z.core.$strip>>>;
721
732
  }, z.core.$strip>>;
722
733
  }, z.core.$strip>>;
723
734
  };
724
735
  //#endregion
725
- export { Annotation, type ArrangeOptions, Board, type BoardInput, BoardItem, type BoardItemAnchor, BoardLabel, type BoardListingGroup, BoardSection, type BoardShapes, type BoardView, Book, BookContents, BookFrontMatterEntry, type BookInput, BookMode, BookPage, BookSection, BookView, type BookViewInput, Box, Chapter, Cue, DECK_SIZE, DEFAULT_TRANSITION_DURATION_MS, Deck, type DeckFromSlidesOptions, type DeckInput, type DeckLimits, Direction, ImageFit, type Imposed, Lexicon, MAKE_ROOM_LIMIT, MARK_GAP_MM, MARK_LENGTH_MM, MAX_BOARD_ZOOM, MAX_SLIDE_TIMING_MS, MAX_TRANSITION_DURATION_MS, MIN_BOARD_ITEM_WIDTH, MIN_BOARD_ZOOM, MIN_TRANSITION_DURATION_MS, MM_PER_PT, Manifest, type MillimeterEdges, NAMED_PAPERS, Narration, NoteMarker, NumberSystem, PAPER_MM, PX_PER_MM, type PageFit, type PagePlacementKind, type PagePlacementLayout, Paper, type PaperBox, type Placement, Point, PronunciationEntry, PublishedShot, SECTION_GAP, SECTION_MIN_SIZE, SECTION_PADDING, SECTION_TITLE_ROOM, SHOT_KEY, Scripts, Shot, type ShotUse, Size, Slide, SlideContent, type SlideRow, TextDirection, Transition, TransitionInput, Word, type ZoomBounds, addItem, annotationCueIssues, arrange, assignMembership, boardBounds, boardListing, boardListingIsEmpty, boardToScreen, deckFromSlides, exceedsLimits, firstStrongDirection, fitPage, fitSection, fitTouchedSections, foldForSearch, formatNumber, impose, isBookContents, isHebrew, itemAt, itemHeight, languageDirection, lexiconVersion, makeRoom, mirrorPlacement, moveItems, moveSection, nameMatches, noteMarker, noteMarkerIssues, pagePlacementLayout, printBoxes, printPadding, printProperties, removeItems, resizeItem, resolveDirection, revealView, roundMillimeters, screenToBoard, settleBoard, sheetMm, shotsUsed, toMillimeters, visibleItems, zoomAt, zoomToFit };
736
+ export { Annotation, type ArrangeOptions, Board, type BoardInput, BoardItem, type BoardItemAnchor, BoardLabel, type BoardListingGroup, BoardSection, type BoardShapes, type BoardView, Book, BookContents, BookFrontMatterEntry, type BookInput, BookMode, BookPage, BookSection, BookView, type BookViewInput, Box, Chapter, Cue, DECK_SIZE, DEFAULT_TRANSITION_DURATION_MS, Deck, type DeckFromSlidesOptions, type DeckInput, type DeckLimits, Direction, GridCell, type GridDropTarget, ImageFit, type Imposed, Lexicon, MAKE_ROOM_LIMIT, MARK_GAP_MM, MARK_LENGTH_MM, MAX_BOARD_ZOOM, MAX_SLIDE_TIMING_MS, MAX_TRANSITION_DURATION_MS, MIN_BOARD_ITEM_WIDTH, MIN_BOARD_ZOOM, MIN_TRANSITION_DURATION_MS, MM_PER_PT, Manifest, type MillimeterEdges, NAMED_PAPERS, Narration, NoteMarker, NumberSystem, PAPER_MM, PX_PER_MM, type PageFit, type PagePlacementKind, type PagePlacementLayout, Paper, type PaperBox, type Placement, Point, PronunciationEntry, PublishedShot, SECTION_GAP, SECTION_MIN_SIZE, SECTION_PADDING, SECTION_TITLE_ROOM, SHOT_KEY, Scripts, Shot, type ShotUse, Size, Slide, SlideContent, type SlideRow, TextDirection, Transition, TransitionInput, Word, type ZoomBounds, addItem, annotationCueIssues, arrange, assignMembership, boardBounds, boardListing, boardListingIsEmpty, boardToScreen, deckFromSlides, deriveSectionCells, exceedsLimits, firstStrongDirection, fitPage, fitSection, fitTouchedSections, foldForSearch, formatNumber, gridDropMarkerBox, gridDropTarget, impose, insertIntoGrid, isBookContents, isHebrew, itemAt, itemHeight, languageDirection, layoutSection, lexiconVersion, makeRoom, mirrorPlacement, moveItems, moveSection, nameMatches, noteMarker, noteMarkerIssues, pagePlacementLayout, printBoxes, printPadding, printProperties, removeFromGrid, removeItems, resizeItem, resolveDirection, revealView, roundMillimeters, screenToBoard, settleBoard, sheetMm, shotsUsed, toMillimeters, visibleItems, zoomAt, zoomToFit };
package/dist/index.js CHANGED
@@ -3,7 +3,7 @@ import { Scripts } from "./scripts.js";
3
3
  import { MAX_SLIDE_TIMING_MS } from "./timing.js";
4
4
  import { DEFAULT_TRANSITION_DURATION_MS, MAX_TRANSITION_DURATION_MS, MIN_TRANSITION_DURATION_MS, Transition } from "./transition.js";
5
5
  import { Annotation, Box, Cue, DECK_SIZE, Deck, DeckShape, ImageFit, Manifest, Narration, Point, PublishedShot, SHOT_KEY, Shot, Size, Slide, SlideContent, Word, annotationCueIssues, exceedsLimits } from "./deck.js";
6
- import { Board, BoardItem, BoardLabel, BoardSection } from "./board.js";
6
+ import { Board, BoardItem, BoardLabel, BoardSection, GridCell } from "./board.js";
7
7
  import { NoteMarker, NumberSystem, formatNumber, noteMarker } from "./numbering.js";
8
8
  import { MARK_GAP_MM, MARK_LENGTH_MM, MM_PER_PT, NAMED_PAPERS, PAPER_MM, PX_PER_MM, Paper, printBoxes, printPadding, printProperties, roundMillimeters, sheetMm, toMillimeters } from "./paper.js";
9
9
  import { Book, BookContents, BookFrontMatterEntry, BookMode, BookPage, BookSection, BookShape, BookView, Chapter, isBookContents, isHebrew, noteMarkerIssues } from "./book.js";
@@ -11,7 +11,9 @@ import { Lexicon, PronunciationEntry, lexiconVersion } from "./lexicon.js";
11
11
  import { MIN_BOARD_ITEM_WIDTH, addItem, moveItems, moveSection, removeItems, resizeItem } from "./board-edit.js";
12
12
  import { MAX_BOARD_ZOOM, MIN_BOARD_ZOOM, arrange, boardBounds, boardToScreen, itemAt, itemHeight, revealView, screenToBoard, visibleItems, zoomAt, zoomToFit } from "./board-geometry.js";
13
13
  import { boardListing, boardListingIsEmpty, foldForSearch, nameMatches } from "./board-listing.js";
14
- import { MAKE_ROOM_LIMIT, SECTION_GAP, SECTION_MIN_SIZE, SECTION_PADDING, SECTION_TITLE_ROOM, assignMembership, fitSection, fitTouchedSections, makeRoom, settleBoard } from "./board-sections.js";
14
+ import { MAKE_ROOM_LIMIT, SECTION_GAP, SECTION_MIN_SIZE, SECTION_PADDING, SECTION_TITLE_ROOM } from "./board-section-constants.js";
15
+ import { deriveSectionCells, gridDropMarkerBox, gridDropTarget, insertIntoGrid, layoutSection, removeFromGrid } from "./board-section-grid.js";
16
+ import { assignMembership, fitSection, fitTouchedSections, makeRoom, settleBoard } from "./board-sections.js";
15
17
  import { deckFromSlides } from "./deck-from-slides.js";
16
18
  import { fitPage, impose, mirrorPlacement, pagePlacementLayout } from "./imposition.js";
17
19
  import { shotsUsed } from "./shots-used.js";
@@ -157,4 +159,4 @@ function jsonSchemas() {
157
159
  };
158
160
  }
159
161
  //#endregion
160
- export { Annotation, Board, BoardItem, BoardLabel, BoardSection, Book, BookContents, BookFrontMatterEntry, BookMode, BookPage, BookSection, BookView, Box, Chapter, Cue, DECK_SIZE, DEFAULT_TRANSITION_DURATION_MS, Deck, Direction, ImageFit, Imposition, Lexicon, MAKE_ROOM_LIMIT, MARK_GAP_MM, MARK_LENGTH_MM, MAX_BOARD_ZOOM, MAX_IMPOSITION_CELLS, MAX_SLIDE_TIMING_MS, MAX_TRANSITION_DURATION_MS, MIN_BOARD_ITEM_WIDTH, MIN_BOARD_ZOOM, MIN_TRANSITION_DURATION_MS, MM_PER_PT, Manifest, Margin, NAMED_PAPERS, Narration, NoteMarker, NumberSystem, PAPER_MM, PX_PER_MM, Page, PageSize, Paper, Point, Print, PronunciationEntry, PublishedShot, SECTION_GAP, SECTION_MIN_SIZE, SECTION_PADDING, SECTION_TITLE_ROOM, SHOT_KEY, Scripts, Shot, Size, Slide, SlideContent, TextDirection, Transition, Word, addItem, annotationCueIssues, arrange, assignMembership, boardBounds, boardListing, boardListingIsEmpty, boardToScreen, deckFromSlides, exceedsLimits, firstStrongDirection, fitPage, fitSection, fitTouchedSections, foldForSearch, formatNumber, impose, isBookContents, isHebrew, itemAt, itemHeight, jsonSchemas, languageDirection, lexiconVersion, makeRoom, mirrorPlacement, moveItems, moveSection, nameMatches, noteMarker, noteMarkerIssues, pagePlacementLayout, printBoxes, printPadding, printProperties, removeItems, resizeItem, resolveDirection, revealView, roundMillimeters, screenToBoard, settleBoard, sheetMm, shotsUsed, toMillimeters, visibleItems, zoomAt, zoomToFit };
162
+ export { Annotation, Board, BoardItem, BoardLabel, BoardSection, Book, BookContents, BookFrontMatterEntry, BookMode, BookPage, BookSection, BookView, Box, Chapter, Cue, DECK_SIZE, DEFAULT_TRANSITION_DURATION_MS, Deck, Direction, GridCell, ImageFit, Imposition, Lexicon, MAKE_ROOM_LIMIT, MARK_GAP_MM, MARK_LENGTH_MM, MAX_BOARD_ZOOM, MAX_IMPOSITION_CELLS, MAX_SLIDE_TIMING_MS, MAX_TRANSITION_DURATION_MS, MIN_BOARD_ITEM_WIDTH, MIN_BOARD_ZOOM, MIN_TRANSITION_DURATION_MS, MM_PER_PT, Manifest, Margin, NAMED_PAPERS, Narration, NoteMarker, NumberSystem, PAPER_MM, PX_PER_MM, Page, PageSize, Paper, Point, Print, PronunciationEntry, PublishedShot, SECTION_GAP, SECTION_MIN_SIZE, SECTION_PADDING, SECTION_TITLE_ROOM, SHOT_KEY, Scripts, Shot, Size, Slide, SlideContent, TextDirection, Transition, Word, addItem, annotationCueIssues, arrange, assignMembership, boardBounds, boardListing, boardListingIsEmpty, boardToScreen, deckFromSlides, deriveSectionCells, exceedsLimits, firstStrongDirection, fitPage, fitSection, fitTouchedSections, foldForSearch, formatNumber, gridDropMarkerBox, gridDropTarget, impose, insertIntoGrid, isBookContents, isHebrew, itemAt, itemHeight, jsonSchemas, languageDirection, layoutSection, lexiconVersion, makeRoom, mirrorPlacement, moveItems, moveSection, nameMatches, noteMarker, noteMarkerIssues, pagePlacementLayout, printBoxes, printPadding, printProperties, removeFromGrid, removeItems, resizeItem, resolveDirection, revealView, roundMillimeters, screenToBoard, settleBoard, sheetMm, shotsUsed, toMillimeters, visibleItems, zoomAt, zoomToFit };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@segnavia/format",
3
- "version": "0.15.0",
3
+ "version": "0.16.0",
4
4
  "type": "module",
5
5
  "license": "MIT",
6
6
  "repository": {