@framesquared/layout 0.1.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 John Carbone
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,496 @@
1
+ import { Component } from '@framesquared/component';
2
+
3
+ /**
4
+ * @framesquared/layout – LayoutContext
5
+ *
6
+ * Holds layout state during a layout run. Caches element measurements
7
+ * to avoid layout thrashing (repeated reads/writes to the DOM).
8
+ */
9
+ declare class LayoutContext {
10
+ /** Generic property cache (layout-level state). */
11
+ private props;
12
+ /** Per-element DOM measurement cache: element → (propName → value). */
13
+ private domCache;
14
+ /**
15
+ * Gets a layout-level property value.
16
+ */
17
+ getProp(name: string): number | undefined;
18
+ /**
19
+ * Sets a layout-level property value.
20
+ */
21
+ setProp(name: string, value: number): void;
22
+ /**
23
+ * Gets a cached DOM measurement, or reads it via `readFn` and caches the result.
24
+ * This prevents repeated layout-triggering reads on the same element.
25
+ */
26
+ getDomProp(name: string, element: Element, readFn: () => number): number;
27
+ /**
28
+ * Returns true if a DOM measurement has been cached for the given element+prop.
29
+ */
30
+ hasDomProp(name: string, element: Element): boolean;
31
+ /**
32
+ * Clears all cached data. Called at the start of a new layout run.
33
+ */
34
+ flush(): void;
35
+ }
36
+
37
+ /**
38
+ * @framesquared/layout – Layout (base class)
39
+ *
40
+ * Abstract base for all layout managers. Manages the lifecycle
41
+ * (beginLayout → calculate → completeLayout → afterLayout), size
42
+ * policy determination, and item rendering into a target element.
43
+ */
44
+
45
+ interface SizePolicy {
46
+ width: 'shrinkWrap' | 'configured' | 'natural';
47
+ height: 'shrinkWrap' | 'configured' | 'natural';
48
+ }
49
+ interface LayoutConfig {
50
+ type?: string;
51
+ [key: string]: unknown;
52
+ }
53
+ declare class Layout {
54
+ readonly type: string;
55
+ owner: Component | null;
56
+ isRunning: boolean;
57
+ needsLayout: boolean;
58
+ constructor(config?: LayoutConfig);
59
+ /**
60
+ * Sets the Container that owns this layout.
61
+ */
62
+ setOwner(owner: Component): void;
63
+ /**
64
+ * Called at the start of a layout run. Sets isRunning flag.
65
+ * Override to gather initial measurements.
66
+ */
67
+ beginLayout(_context: LayoutContext): void;
68
+ /**
69
+ * Main calculation phase. Override in subclasses to compute
70
+ * child sizes and positions.
71
+ */
72
+ calculate(_context: LayoutContext): void;
73
+ /**
74
+ * Called after calculation is complete. Clears flags.
75
+ * Override to finalize DOM writes.
76
+ */
77
+ completeLayout(_context: LayoutContext): void;
78
+ /**
79
+ * Called after the entire layout pass is done.
80
+ * Override for post-layout work (e.g., firing events).
81
+ */
82
+ afterLayout(): void;
83
+ /**
84
+ * Returns the size policy for a child item.
85
+ * 'configured' = the item has an explicit width/height config.
86
+ * 'natural' = the item uses its natural content size.
87
+ * 'shrinkWrap' = the item wraps its content.
88
+ */
89
+ getItemSizePolicy(item: Component): SizePolicy;
90
+ /**
91
+ * Renders child components into the target element.
92
+ * Base implementation renders each item sequentially.
93
+ */
94
+ renderItems(items: Component[], target: Element): void;
95
+ }
96
+
97
+ /**
98
+ * @framesquared/layout – AutoLayout
99
+ *
100
+ * The default layout. Renders items in DOM order with no special
101
+ * positioning — items use their natural or configured sizes and
102
+ * rely on normal CSS flow.
103
+ *
104
+ * Alias: 'auto'
105
+ */
106
+
107
+ declare class AutoLayout extends Layout {
108
+ constructor(config?: LayoutConfig);
109
+ /**
110
+ * Auto layout: no calculation needed — items use natural CSS flow.
111
+ */
112
+ calculate(_context: LayoutContext): void;
113
+ /**
114
+ * All items use natural sizing in auto layout.
115
+ */
116
+ getItemSizePolicy(_item: Component): SizePolicy;
117
+ }
118
+
119
+ /**
120
+ * @framesquared/layout – LayoutRunner
121
+ *
122
+ * Singleton that orchestrates layout runs. Collects invalidation
123
+ * requests and processes them in batch. Detects and breaks layout
124
+ * cycles to prevent infinite loops.
125
+ *
126
+ * Integrates with ResizeObserver to automatically invalidate
127
+ * components when their container elements resize.
128
+ */
129
+
130
+ declare class LayoutRunner {
131
+ private static instance;
132
+ /** Set of components that need a layout pass. */
133
+ private pending;
134
+ /** Whether a run is currently scheduled. */
135
+ private scheduled;
136
+ /** Map of element → { observer, component } for ResizeObserver tracking. */
137
+ private observed;
138
+ /** Component → ResizeObserver for cleanup. */
139
+ private componentObservers;
140
+ private constructor();
141
+ static getInstance(): LayoutRunner;
142
+ /**
143
+ * Marks a component for re-layout.
144
+ */
145
+ invalidate(component: Component): void;
146
+ /**
147
+ * Returns true if the component is in the pending queue.
148
+ */
149
+ isPending(component: Component): boolean;
150
+ /**
151
+ * Performs a complete layout pass on all pending components.
152
+ * Uses iterative processing with cycle detection.
153
+ */
154
+ run(): void;
155
+ /**
156
+ * Schedules a layout run on the next animation frame.
157
+ * Multiple calls before the frame coalesce into a single run.
158
+ */
159
+ scheduleRun(): void;
160
+ /**
161
+ * Clears all pending items without running them.
162
+ */
163
+ clear(): void;
164
+ /**
165
+ * Starts watching an element for size changes.
166
+ * When the element resizes, the associated component is invalidated.
167
+ */
168
+ observe(component: Component, element: Element): void;
169
+ /**
170
+ * Stops watching an element for size changes.
171
+ */
172
+ unobserve(component: Component, element: Element): void;
173
+ private getLayout;
174
+ }
175
+
176
+ /**
177
+ * @framesquared/layout – BoxLayout (abstract base)
178
+ *
179
+ * Abstract base for HBox and VBox layouts. Uses CSS Flexbox under
180
+ * the hood: sets display:flex on the container and translates
181
+ * align/pack/gap/reverse/overflow to flex properties. Child items
182
+ * with `flex` config get flex-grow; fixed-size items get flex-shrink:0.
183
+ */
184
+
185
+ type BoxAlign = 'start' | 'center' | 'end' | 'stretch' | 'stretchmax';
186
+ type BoxPack = 'start' | 'center' | 'end' | 'space-between' | 'space-around' | 'space-evenly';
187
+ type BoxOverflow = 'visible' | 'hidden' | 'scroll' | 'wrap';
188
+ interface BoxLayoutConfig extends LayoutConfig {
189
+ align?: BoxAlign;
190
+ pack?: BoxPack;
191
+ gap?: number;
192
+ overflow?: BoxOverflow;
193
+ reverse?: boolean;
194
+ }
195
+ declare abstract class BoxLayout extends Layout {
196
+ protected align: BoxAlign;
197
+ protected pack: BoxPack;
198
+ protected gap: number;
199
+ protected overflow: BoxOverflow;
200
+ protected reverse: boolean;
201
+ constructor(config?: BoxLayoutConfig);
202
+ /**
203
+ * The CSS flex-direction value. 'row' for HBox, 'column' for VBox.
204
+ * Subclasses must implement this.
205
+ */
206
+ protected abstract getDirection(): string;
207
+ /**
208
+ * Returns the primary axis config name for checking fixed size.
209
+ * 'width' for HBox, 'height' for VBox.
210
+ */
211
+ protected abstract getPrimaryAxisProp(): string;
212
+ /**
213
+ * Applies CSS Flexbox properties to the container element.
214
+ */
215
+ configureContainer(el: HTMLElement): void;
216
+ /**
217
+ * Applies flex CSS properties to each child item based on its config.
218
+ */
219
+ applyItemStyles(items: Component[], _target: Element): void;
220
+ renderItems(items: Component[], target: Element): void;
221
+ calculate(_context: LayoutContext): void;
222
+ getItemSizePolicy(item: Component): SizePolicy;
223
+ }
224
+
225
+ /**
226
+ * @framesquared/layout – HBoxLayout
227
+ *
228
+ * Horizontal box layout. Children are arranged left-to-right
229
+ * (or right-to-left when reverse=true). Uses CSS Flexbox with
230
+ * flex-direction: row.
231
+ *
232
+ * Alias: 'hbox'
233
+ */
234
+
235
+ declare class HBoxLayout extends BoxLayout {
236
+ constructor(config?: BoxLayoutConfig);
237
+ protected getDirection(): string;
238
+ protected getPrimaryAxisProp(): string;
239
+ }
240
+
241
+ /**
242
+ * @framesquared/layout – VBoxLayout
243
+ *
244
+ * Vertical box layout. Children are arranged top-to-bottom
245
+ * (or bottom-to-top when reverse=true). Uses CSS Flexbox with
246
+ * flex-direction: column.
247
+ *
248
+ * Alias: 'vbox'
249
+ */
250
+
251
+ declare class VBoxLayout extends BoxLayout {
252
+ constructor(config?: BoxLayoutConfig);
253
+ protected getDirection(): string;
254
+ protected getPrimaryAxisProp(): string;
255
+ }
256
+
257
+ /**
258
+ * @framesquared/layout – FitLayout
259
+ *
260
+ * Single child fills the entire container. If multiple children
261
+ * are present, only the first is visible.
262
+ *
263
+ * Alias: 'fit'
264
+ */
265
+
266
+ declare class FitLayout extends Layout {
267
+ constructor(config?: LayoutConfig);
268
+ configureContainer(el: HTMLElement): void;
269
+ applyItemStyles(items: Component[], _target: Element): void;
270
+ renderItems(items: Component[], target: Element): void;
271
+ }
272
+
273
+ /**
274
+ * @framesquared/layout – CardLayout
275
+ *
276
+ * Like FitLayout but supports multiple children — only one is visible
277
+ * at a time. Provides next/prev navigation and fires activate/deactivate.
278
+ *
279
+ * Alias: 'card'
280
+ */
281
+
282
+ interface CardLayoutConfig extends LayoutConfig {
283
+ activeItem?: number;
284
+ }
285
+ declare class CardLayout extends Layout {
286
+ private activeIndex;
287
+ private listeners;
288
+ constructor(config?: CardLayoutConfig);
289
+ /** Subscribe to events. */
290
+ on(event: string, fn: Function): void;
291
+ /** Unsubscribe. */
292
+ un(event: string, fn: Function): void;
293
+ private fire;
294
+ configureContainer(el: HTMLElement): void;
295
+ applyItemStyles(items: Component[], _target: Element): void;
296
+ renderItems(items: Component[], target: Element): void;
297
+ getActiveItem(): Component | undefined;
298
+ setActiveItem(item: number | Component): void;
299
+ next(): Component;
300
+ prev(): Component;
301
+ private getOwnerItems;
302
+ }
303
+
304
+ /**
305
+ * @framesquared/layout – AnchorLayout
306
+ *
307
+ * Children sized relative to container using anchor strings:
308
+ * '100% 50%' → 100% width, 50% height
309
+ * '-50 -100' → calc(100% - 50px), calc(100% - 100px)
310
+ * '-50' → width offset only
311
+ *
312
+ * Alias: 'anchor'
313
+ */
314
+
315
+ declare class AnchorLayout extends Layout {
316
+ constructor(config?: LayoutConfig);
317
+ configureContainer(el: HTMLElement): void;
318
+ applyItemStyles(items: Component[], _target: Element): void;
319
+ renderItems(items: Component[], target: Element): void;
320
+ }
321
+
322
+ /**
323
+ * @framesquared/layout – BorderLayout
324
+ *
325
+ * Classic border layout with 5 regions: north, south, east, west, center.
326
+ * Uses CSS Grid under the hood. Center is required and fills remaining space.
327
+ *
328
+ * Alias: 'border'
329
+ */
330
+
331
+ interface BorderLayoutConfig extends LayoutConfig {
332
+ split?: boolean;
333
+ }
334
+ declare class BorderLayout extends Layout {
335
+ constructor(config?: BorderLayoutConfig);
336
+ configureContainer(el: HTMLElement): void;
337
+ applyItemStyles(items: Component[], target: Element): void;
338
+ renderItems(items: Component[], target: Element): void;
339
+ }
340
+
341
+ /**
342
+ * @framesquared/layout – ColumnLayout
343
+ *
344
+ * Arranges children in columns. Items with `columnWidth` (0–1)
345
+ * get a percentage of the container width. Fixed-width items
346
+ * keep their configured width. Uses CSS Flexbox with wrap.
347
+ *
348
+ * Alias: 'column'
349
+ */
350
+
351
+ declare class ColumnLayout extends Layout {
352
+ constructor(config?: LayoutConfig);
353
+ configureContainer(el: HTMLElement): void;
354
+ applyItemStyles(items: Component[], _target: Element): void;
355
+ renderItems(items: Component[], target: Element): void;
356
+ }
357
+
358
+ /**
359
+ * @framesquared/layout – TableLayout
360
+ *
361
+ * Renders children in a grid structure using CSS Grid.
362
+ * Supports `columns`, `colspan`, and `rowspan` on items.
363
+ *
364
+ * Alias: 'table'
365
+ */
366
+
367
+ interface TableLayoutConfig extends LayoutConfig {
368
+ columns?: number;
369
+ }
370
+ declare class TableLayout extends Layout {
371
+ private columns;
372
+ constructor(config?: TableLayoutConfig);
373
+ configureContainer(el: HTMLElement): void;
374
+ applyItemStyles(items: Component[], _target: Element): void;
375
+ renderItems(items: Component[], target: Element): void;
376
+ }
377
+
378
+ /**
379
+ * @framesquared/layout – AbsoluteLayout
380
+ *
381
+ * Positions children absolutely within the container using x/y configs.
382
+ *
383
+ * Alias: 'absolute'
384
+ */
385
+
386
+ declare class AbsoluteLayout extends Layout {
387
+ constructor(config?: LayoutConfig);
388
+ configureContainer(el: HTMLElement): void;
389
+ applyItemStyles(items: Component[], _target: Element): void;
390
+ renderItems(items: Component[], target: Element): void;
391
+ }
392
+
393
+ /**
394
+ * @framesquared/layout – AccordionLayout
395
+ *
396
+ * Vertical stack where only one item is expanded at a time
397
+ * (or multiple with multi:true). Expanded items get flex:1
398
+ * when fill:true. Collapsed items show only their header.
399
+ *
400
+ * Alias: 'accordion'
401
+ */
402
+
403
+ interface AccordionLayoutConfig extends LayoutConfig {
404
+ multi?: boolean;
405
+ fill?: boolean;
406
+ animate?: boolean;
407
+ }
408
+ declare class AccordionLayout extends Layout {
409
+ private multi;
410
+ private fill;
411
+ private expandedSet;
412
+ constructor(config?: AccordionLayoutConfig);
413
+ configureContainer(el: HTMLElement): void;
414
+ applyItemStyles(items: Component[], _target: Element): void;
415
+ renderItems(items: Component[], target: Element): void;
416
+ isExpanded(item: Component): boolean;
417
+ expand(item: Component): void;
418
+ collapse(item: Component): void;
419
+ toggle(item: Component): void;
420
+ private applyExpansionState;
421
+ private getOwnerItems;
422
+ }
423
+
424
+ /**
425
+ * @framesquared/layout – ResponsivePlugin
426
+ *
427
+ * Monitors viewport size using window.matchMedia() and applies
428
+ * different component configs at different breakpoints.
429
+ *
430
+ * Supports both standard media queries and shorthand expressions:
431
+ * '(max-width: 599px)' → standard CSS media query
432
+ * 'width < 600' → parsed to (max-width: 599px)
433
+ * 'width >= 600 && width < 1024' → parsed to (min-width: 600px) and (max-width: 1023px)
434
+ * 'width >= 1024' → parsed to (min-width: 1024px)
435
+ */
436
+
437
+ interface ResponsiveConfig {
438
+ responsiveConfig: Record<string, Record<string, unknown>>;
439
+ }
440
+ declare class ResponsivePlugin {
441
+ private config;
442
+ private owner;
443
+ private entries;
444
+ private activeConfigs;
445
+ constructor(config: ResponsiveConfig);
446
+ getOwner(): Component | null;
447
+ /**
448
+ * Initialise the plugin with its owner component.
449
+ * Sets up matchMedia listeners and applies initial state.
450
+ */
451
+ init(owner: Component): void;
452
+ /**
453
+ * Clean up all media query listeners.
454
+ */
455
+ destroy(): void;
456
+ }
457
+
458
+ /**
459
+ * @framesquared/layout – ResponsiveColumnLayout
460
+ *
461
+ * Columns that automatically reflow based on container width.
462
+ * Uses CSS Grid with auto-fill and minmax() for responsive columns
463
+ * without JavaScript resize calculation.
464
+ *
465
+ * Config:
466
+ * minColumnWidth — minimum width of each column
467
+ * gap — spacing between items
468
+ * maxColumns — optional maximum number of columns
469
+ *
470
+ * Items with `columnSpan: N` span multiple grid columns.
471
+ *
472
+ * Alias: 'responsivecolumn'
473
+ */
474
+
475
+ interface ResponsiveColumnLayoutConfig extends LayoutConfig {
476
+ minColumnWidth: number;
477
+ gap?: number;
478
+ maxColumns?: number;
479
+ }
480
+ declare class ResponsiveColumnLayout extends Layout {
481
+ private minColumnWidth;
482
+ private gap;
483
+ private maxColumns;
484
+ constructor(config: ResponsiveColumnLayoutConfig);
485
+ /**
486
+ * Configures the container element with CSS Grid responsive columns.
487
+ */
488
+ configureContainer(el: HTMLElement): void;
489
+ /**
490
+ * Applies columnSpan to items that need to span multiple columns.
491
+ */
492
+ applyItemStyles(items: Component[], _target: Element): void;
493
+ renderItems(items: Component[], target: Element): void;
494
+ }
495
+
496
+ export { AbsoluteLayout, AccordionLayout, type AccordionLayoutConfig, AnchorLayout, AutoLayout, BorderLayout, type BorderLayoutConfig, type BoxAlign, BoxLayout, type BoxLayoutConfig, type BoxOverflow, type BoxPack, CardLayout, type CardLayoutConfig, ColumnLayout, FitLayout, HBoxLayout, Layout, type LayoutConfig, LayoutContext, LayoutRunner, ResponsiveColumnLayout, type ResponsiveColumnLayoutConfig, type ResponsiveConfig, ResponsivePlugin, type SizePolicy, TableLayout, type TableLayoutConfig, VBoxLayout };