@deck.gl-community/infovis-layers 9.3.2 → 9.3.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (129) hide show
  1. package/dist/index.cjs +2998 -114
  2. package/dist/index.cjs.map +4 -4
  3. package/dist/index.d.ts +11 -2
  4. package/dist/index.d.ts.map +1 -1
  5. package/dist/index.js +9 -1
  6. package/dist/index.js.map +1 -1
  7. package/dist/layers/animation-layer/animation-layer.d.ts +30 -0
  8. package/dist/layers/animation-layer/animation-layer.d.ts.map +1 -0
  9. package/dist/layers/animation-layer/animation-layer.js +53 -0
  10. package/dist/layers/animation-layer/animation-layer.js.map +1 -0
  11. package/dist/layers/animation-layer/animation.d.ts +86 -0
  12. package/dist/layers/animation-layer/animation.d.ts.map +1 -0
  13. package/dist/layers/animation-layer/animation.js +214 -0
  14. package/dist/layers/animation-layer/animation.js.map +1 -0
  15. package/dist/layers/block-layer/block-layer-fragment.glsl.d.ts +3 -0
  16. package/dist/layers/block-layer/block-layer-fragment.glsl.d.ts.map +1 -0
  17. package/dist/layers/block-layer/block-layer-fragment.glsl.js +41 -0
  18. package/dist/layers/block-layer/block-layer-fragment.glsl.js.map +1 -0
  19. package/dist/layers/block-layer/block-layer-uniforms.d.ts +27 -0
  20. package/dist/layers/block-layer/block-layer-uniforms.d.ts.map +1 -0
  21. package/dist/layers/block-layer/block-layer-uniforms.js +26 -0
  22. package/dist/layers/block-layer/block-layer-uniforms.js.map +1 -0
  23. package/dist/layers/block-layer/block-layer-vertex.glsl.d.ts +3 -0
  24. package/dist/layers/block-layer/block-layer-vertex.glsl.d.ts.map +1 -0
  25. package/dist/layers/block-layer/block-layer-vertex.glsl.js +67 -0
  26. package/dist/layers/block-layer/block-layer-vertex.glsl.js.map +1 -0
  27. package/dist/layers/block-layer/block-layer.d.ts +71 -0
  28. package/dist/layers/block-layer/block-layer.d.ts.map +1 -0
  29. package/dist/layers/block-layer/block-layer.js +108 -0
  30. package/dist/layers/block-layer/block-layer.js.map +1 -0
  31. package/dist/layers/fast-text-layer/fast-text-layer.d.ts +148 -0
  32. package/dist/layers/fast-text-layer/fast-text-layer.d.ts.map +1 -0
  33. package/dist/layers/fast-text-layer/fast-text-layer.js +862 -0
  34. package/dist/layers/fast-text-layer/fast-text-layer.js.map +1 -0
  35. package/dist/layers/fast-text-layer/fast-text-layer.test.d.ts +2 -0
  36. package/dist/layers/fast-text-layer/fast-text-layer.test.d.ts.map +1 -0
  37. package/dist/layers/fast-text-layer/fast-text-layer.test.js +300 -0
  38. package/dist/layers/fast-text-layer/fast-text-layer.test.js.map +1 -0
  39. package/dist/layers/fast-text-layer/fast-text-layout.d.ts +179 -0
  40. package/dist/layers/fast-text-layer/fast-text-layout.d.ts.map +1 -0
  41. package/dist/layers/fast-text-layer/fast-text-layout.js +665 -0
  42. package/dist/layers/fast-text-layer/fast-text-layout.js.map +1 -0
  43. package/dist/layers/fast-text-layer/font-atlas.d.ts +98 -0
  44. package/dist/layers/fast-text-layer/font-atlas.d.ts.map +1 -0
  45. package/dist/layers/fast-text-layer/font-atlas.js +337 -0
  46. package/dist/layers/fast-text-layer/font-atlas.js.map +1 -0
  47. package/dist/layers/fast-text-layer/index.d.ts +4 -0
  48. package/dist/layers/fast-text-layer/index.d.ts.map +1 -0
  49. package/dist/layers/fast-text-layer/index.js +4 -0
  50. package/dist/layers/fast-text-layer/index.js.map +1 -0
  51. package/dist/layers/time-delta-layer.d.ts +22 -60
  52. package/dist/layers/time-delta-layer.d.ts.map +1 -1
  53. package/dist/layers/time-delta-layer.js +31 -53
  54. package/dist/layers/time-delta-layer.js.map +1 -1
  55. package/dist/synchronized-views/synchronized-views.js +1 -1
  56. package/dist/synchronized-views/synchronized-views.js.map +1 -1
  57. package/dist/utils/format-utils.d.ts +27 -3
  58. package/dist/utils/format-utils.d.ts.map +1 -1
  59. package/dist/utils/format-utils.js +72 -40
  60. package/dist/utils/format-utils.js.map +1 -1
  61. package/dist/utils/utf8-string-view.d.ts +53 -0
  62. package/dist/utils/utf8-string-view.d.ts.map +1 -0
  63. package/dist/utils/utf8-string-view.js +169 -0
  64. package/dist/utils/utf8-string-view.js.map +1 -0
  65. package/dist/utils/utf8-string-view.test.d.ts +2 -0
  66. package/dist/utils/utf8-string-view.test.d.ts.map +1 -0
  67. package/dist/utils/utf8-string-view.test.js +88 -0
  68. package/dist/utils/utf8-string-view.test.js.map +1 -0
  69. package/dist/views/bounds-utils.d.ts +23 -0
  70. package/dist/views/bounds-utils.d.ts.map +1 -0
  71. package/dist/views/bounds-utils.js +65 -0
  72. package/dist/views/bounds-utils.js.map +1 -0
  73. package/dist/views/layer-bounds-filter.d.ts +48 -0
  74. package/dist/views/layer-bounds-filter.d.ts.map +1 -0
  75. package/dist/views/layer-bounds-filter.js +99 -0
  76. package/dist/views/layer-bounds-filter.js.map +1 -0
  77. package/dist/views/layer-filter.js +4 -4
  78. package/dist/views/layer-filter.js.map +1 -1
  79. package/dist/views/orthographic-utils.d.ts +30 -2
  80. package/dist/views/orthographic-utils.d.ts.map +1 -1
  81. package/dist/views/orthographic-utils.js +33 -14
  82. package/dist/views/orthographic-utils.js.map +1 -1
  83. package/dist/views/view-layout/build-views-from-view-layout.d.ts +37 -0
  84. package/dist/views/view-layout/build-views-from-view-layout.d.ts.map +1 -0
  85. package/dist/views/view-layout/build-views-from-view-layout.js +413 -0
  86. package/dist/views/view-layout/build-views-from-view-layout.js.map +1 -0
  87. package/dist/views/view-layout/index.d.ts +3 -0
  88. package/dist/views/view-layout/index.d.ts.map +1 -0
  89. package/dist/views/view-layout/index.js +3 -0
  90. package/dist/views/view-layout/index.js.map +1 -0
  91. package/dist/views/view-layout/view-layout-item.d.ts +74 -0
  92. package/dist/views/view-layout/view-layout-item.d.ts.map +1 -0
  93. package/dist/views/view-layout/view-layout-item.js +48 -0
  94. package/dist/views/view-layout/view-layout-item.js.map +1 -0
  95. package/dist/views/view-layout/view-layout.test.d.ts +2 -0
  96. package/dist/views/view-layout/view-layout.test.d.ts.map +1 -0
  97. package/dist/views/view-layout/view-layout.test.js +112 -0
  98. package/dist/views/view-layout/view-layout.test.js.map +1 -0
  99. package/dist/views/viewport-bounds-utils.d.ts +55 -0
  100. package/dist/views/viewport-bounds-utils.d.ts.map +1 -0
  101. package/dist/views/viewport-bounds-utils.js +70 -0
  102. package/dist/views/viewport-bounds-utils.js.map +1 -0
  103. package/package.json +15 -5
  104. package/src/index.ts +91 -2
  105. package/src/layers/animation-layer/animation-layer.ts +92 -0
  106. package/src/layers/animation-layer/animation.ts +328 -0
  107. package/src/layers/block-layer/block-layer-fragment.glsl.ts +41 -0
  108. package/src/layers/block-layer/block-layer-uniforms.ts +43 -0
  109. package/src/layers/block-layer/block-layer-vertex.glsl.ts +67 -0
  110. package/src/layers/block-layer/block-layer.ts +199 -0
  111. package/src/layers/fast-text-layer/fast-text-layer.test.ts +359 -0
  112. package/src/layers/fast-text-layer/fast-text-layer.ts +1208 -0
  113. package/src/layers/fast-text-layer/fast-text-layout.ts +1124 -0
  114. package/src/layers/fast-text-layer/font-atlas.ts +574 -0
  115. package/src/layers/fast-text-layer/index.ts +37 -0
  116. package/src/layers/time-delta-layer.ts +55 -70
  117. package/src/synchronized-views/synchronized-views.ts +1 -1
  118. package/src/utils/format-utils.ts +90 -40
  119. package/src/utils/utf8-string-view.test.ts +119 -0
  120. package/src/utils/utf8-string-view.ts +273 -0
  121. package/src/views/bounds-utils.ts +88 -0
  122. package/src/views/layer-bounds-filter.ts +160 -0
  123. package/src/views/layer-filter.ts +4 -4
  124. package/src/views/orthographic-utils.ts +65 -18
  125. package/src/views/view-layout/build-views-from-view-layout.ts +547 -0
  126. package/src/views/view-layout/index.ts +18 -0
  127. package/src/views/view-layout/view-layout-item.ts +125 -0
  128. package/src/views/view-layout/view-layout.test.ts +129 -0
  129. package/src/views/viewport-bounds-utils.ts +104 -0
@@ -0,0 +1,547 @@
1
+ import {View} from '@deck.gl/core';
2
+
3
+ import {ViewLayoutItem} from './view-layout-item';
4
+
5
+ import type {ViewLayoutChild, ViewLayoutInsets, ViewLayoutLength} from './view-layout-item';
6
+
7
+ /** Rectangle resolved by the layout compiler for one view id. */
8
+ export type ViewLayoutRect = {
9
+ /** Left coordinate in deck pixels. */
10
+ x: number;
11
+ /** Top coordinate in deck pixels. */
12
+ y: number;
13
+ /** Width in deck pixels. */
14
+ width: number;
15
+ /** Height in deck pixels. */
16
+ height: number;
17
+ };
18
+
19
+ /** Concrete views plus resolved rectangles produced by `buildViewsFromViewLayout`. */
20
+ export type CompiledDeckViews = {
21
+ /** Concrete deck.gl views with numeric bounds resolved for the current canvas size. */
22
+ views: View[];
23
+ /** Resolved rectangles keyed by deck view id. */
24
+ rectsById: Record<string, ViewLayoutRect>;
25
+ };
26
+
27
+ /** Minimal deck.gl view props shape needed by the layout compiler. */
28
+ type LayoutAwareViewProps = {
29
+ /** Stable deck.gl view id. */
30
+ id?: string;
31
+ /** Left offset relative to the current parent bounds. */
32
+ x?: ViewLayoutLength;
33
+ /** Top offset relative to the current parent bounds. */
34
+ y?: ViewLayoutLength;
35
+ /** Width expression resolved against the current parent bounds. */
36
+ width?: ViewLayoutLength;
37
+ /** Height expression resolved against the current parent bounds. */
38
+ height?: ViewLayoutLength;
39
+ } & Record<string, unknown>;
40
+
41
+ /**
42
+ * Compiles one `ViewLayoutItem` tree into concrete deck.gl views and resolved rectangles.
43
+ *
44
+ * @param args - Root layout plus the current canvas size and optional previous compilation.
45
+ * @returns Concrete views with numeric bounds plus resolved rectangles keyed by view id.
46
+ */
47
+ export function buildViewsFromViewLayout(args: {
48
+ /** Root layout tree to compile. */
49
+ layout: ViewLayoutItem;
50
+ /** Current deck width in pixels. */
51
+ width: number;
52
+ /** Current deck height in pixels. */
53
+ height: number;
54
+ /** Optional previous compilation used for structural view reuse. */
55
+ previous?: CompiledDeckViews;
56
+ }): CompiledDeckViews {
57
+ const rootRect: ViewLayoutRect = {
58
+ x: 0,
59
+ y: 0,
60
+ width: Math.max(1, args.width),
61
+ height: Math.max(1, args.height)
62
+ };
63
+ const previousViewsById = new Map(
64
+ (args.previous?.views ?? [])
65
+ .map(view => [getViewId(view), view] as const)
66
+ .filter((entry): entry is [string, View] => Boolean(entry[0]))
67
+ );
68
+ const outputViews: View[] = [];
69
+ const rectsById: Record<string, ViewLayoutRect> = {};
70
+
71
+ compileLayoutItem({
72
+ item: args.layout,
73
+ rect: rootRect,
74
+ outputViews,
75
+ rectsById,
76
+ previousViewsById
77
+ });
78
+
79
+ return {
80
+ views: outputViews,
81
+ rectsById
82
+ };
83
+ }
84
+
85
+ /**
86
+ * Compiles one validated layout node inside the given parent rectangle.
87
+ *
88
+ * @param args - Layout item, allocated bounds, and compilation accumulators.
89
+ */
90
+ function compileLayoutItem(args: {
91
+ item: ViewLayoutItem;
92
+ rect: ViewLayoutRect;
93
+ outputViews: View[];
94
+ rectsById: Record<string, ViewLayoutRect>;
95
+ previousViewsById: Map<string, View>;
96
+ }): void {
97
+ const {item} = args;
98
+
99
+ switch (item.props.type) {
100
+ case 'row':
101
+ compileStackLayout({...args, orientation: 'row', children: item.props.children});
102
+ return;
103
+ case 'column':
104
+ compileStackLayout({...args, orientation: 'column', children: item.props.children});
105
+ return;
106
+ case 'overlay':
107
+ compileOverlayLayout({...args, children: item.props.children});
108
+ return;
109
+ case 'view':
110
+ compileResolvedView({
111
+ view: item.props.view,
112
+ rect: applyInsets(args.rect, item.props.inset),
113
+ outputViews: args.outputViews,
114
+ rectsById: args.rectsById,
115
+ previousViewsById: args.previousViewsById
116
+ });
117
+ return;
118
+ case 'spacer':
119
+ return;
120
+ }
121
+ }
122
+
123
+ /**
124
+ * Compiles one stack container by splitting the stack axis across its children.
125
+ *
126
+ * @param args - Stack orientation, children, and compilation accumulators.
127
+ */
128
+ function compileStackLayout(args: {
129
+ item: ViewLayoutItem;
130
+ rect: ViewLayoutRect;
131
+ orientation: 'row' | 'column';
132
+ children: readonly ViewLayoutChild[];
133
+ outputViews: View[];
134
+ rectsById: Record<string, ViewLayoutRect>;
135
+ previousViewsById: Map<string, View>;
136
+ }): void {
137
+ const containerRect = applyInsets(args.rect, args.item.props.inset);
138
+ const children = args.children.filter(isPresentChild);
139
+ if (children.length === 0) {
140
+ return;
141
+ }
142
+
143
+ const containerAxisLength =
144
+ args.orientation === 'row' ? containerRect.width : containerRect.height;
145
+ const explicitLengths = children.map(child =>
146
+ resolveStackChildAxisLength(child, args.orientation, containerAxisLength)
147
+ );
148
+ const fixedLength = explicitLengths.reduce<number>((sum, length) => sum + (length ?? 0), 0);
149
+ const flexibleChildCount = explicitLengths.filter(length => length === undefined).length;
150
+ const flexibleLength =
151
+ flexibleChildCount > 0
152
+ ? Math.max(containerAxisLength - fixedLength, 0) / flexibleChildCount
153
+ : 0;
154
+
155
+ let cursor = args.orientation === 'row' ? containerRect.x : containerRect.y;
156
+ children.forEach((child, index) => {
157
+ const childAxisLength = explicitLengths[index] ?? flexibleLength;
158
+ const childRect =
159
+ args.orientation === 'row'
160
+ ? {
161
+ x: cursor,
162
+ y: containerRect.y,
163
+ width: childAxisLength,
164
+ height: containerRect.height
165
+ }
166
+ : {
167
+ x: containerRect.x,
168
+ y: cursor,
169
+ width: containerRect.width,
170
+ height: childAxisLength
171
+ };
172
+ cursor += childAxisLength;
173
+ compileLayoutChild({
174
+ child,
175
+ rect: childRect,
176
+ outputViews: args.outputViews,
177
+ rectsById: args.rectsById,
178
+ previousViewsById: args.previousViewsById
179
+ });
180
+ });
181
+ }
182
+
183
+ /**
184
+ * Compiles one overlay container by giving every child the same parent rectangle.
185
+ *
186
+ * @param args - Overlay children, parent rectangle, and compilation accumulators.
187
+ */
188
+ function compileOverlayLayout(args: {
189
+ item: ViewLayoutItem;
190
+ rect: ViewLayoutRect;
191
+ children: readonly ViewLayoutChild[];
192
+ outputViews: View[];
193
+ rectsById: Record<string, ViewLayoutRect>;
194
+ previousViewsById: Map<string, View>;
195
+ }): void {
196
+ const containerRect = applyInsets(args.rect, args.item.props.inset);
197
+ args.children.filter(isPresentChild).forEach(child =>
198
+ compileLayoutChild({
199
+ child,
200
+ rect: containerRect,
201
+ outputViews: args.outputViews,
202
+ rectsById: args.rectsById,
203
+ previousViewsById: args.previousViewsById
204
+ })
205
+ );
206
+ }
207
+
208
+ /**
209
+ * Compiles one child entry, which may be a nested layout item or a raw deck view.
210
+ *
211
+ * @param args - Child node plus the rectangle allocated for it.
212
+ */
213
+ function compileLayoutChild(args: {
214
+ child: Exclude<ViewLayoutChild, null | false | undefined>;
215
+ rect: ViewLayoutRect;
216
+ outputViews: View[];
217
+ rectsById: Record<string, ViewLayoutRect>;
218
+ previousViewsById: Map<string, View>;
219
+ }): void {
220
+ if (args.child instanceof ViewLayoutItem) {
221
+ compileLayoutItem({
222
+ item: args.child,
223
+ rect: args.rect,
224
+ outputViews: args.outputViews,
225
+ rectsById: args.rectsById,
226
+ previousViewsById: args.previousViewsById
227
+ });
228
+ return;
229
+ }
230
+
231
+ compileResolvedView({
232
+ view: args.child,
233
+ rect: args.rect,
234
+ outputViews: args.outputViews,
235
+ rectsById: args.rectsById,
236
+ previousViewsById: args.previousViewsById
237
+ });
238
+ }
239
+
240
+ /**
241
+ * Resolves one raw deck view against a parent rectangle and reuses a previous instance when equal.
242
+ *
243
+ * @param args - View instance, allocated rectangle, and compilation accumulators.
244
+ */
245
+ function compileResolvedView(args: {
246
+ view: View;
247
+ rect: ViewLayoutRect;
248
+ outputViews: View[];
249
+ rectsById: Record<string, ViewLayoutRect>;
250
+ previousViewsById: Map<string, View>;
251
+ }): void {
252
+ const resolvedRect = resolveViewRectWithinParent(args.view, args.rect);
253
+ const viewId = getRequiredViewId(args.view);
254
+ args.rectsById[viewId] = resolvedRect;
255
+ const nextProps = {
256
+ ...args.view.props,
257
+ x: resolvedRect.x,
258
+ y: resolvedRect.y,
259
+ width: resolvedRect.width,
260
+ height: resolvedRect.height
261
+ };
262
+ const previousView = args.previousViewsById.get(viewId);
263
+ const nextView =
264
+ previousView &&
265
+ previousView.constructor === args.view.constructor &&
266
+ arePropsEqual(previousView.props as Record<string, unknown>, nextProps)
267
+ ? previousView
268
+ : instantiateView(args.view, nextProps);
269
+ args.outputViews.push(nextView);
270
+ }
271
+
272
+ /**
273
+ * Resolves one stack child length in the active stack axis, if it declares one.
274
+ *
275
+ * @param child - Stack child to inspect.
276
+ * @param orientation - Active stack orientation.
277
+ * @param parentAxisLength - Parent length in the active stack axis.
278
+ * @returns Numeric child length, or `undefined` when the child should share remaining space.
279
+ */
280
+ function resolveStackChildAxisLength(
281
+ child: Exclude<ViewLayoutChild, null | false | undefined>,
282
+ orientation: 'row' | 'column',
283
+ parentAxisLength: number
284
+ ): number | undefined {
285
+ const axisValue = getAxisLengthValue(child, orientation);
286
+ if (axisValue === undefined) {
287
+ return undefined;
288
+ }
289
+ return resolveLength(axisValue, parentAxisLength);
290
+ }
291
+
292
+ /**
293
+ * Resolves one raw deck view rectangle within the given parent rectangle.
294
+ *
295
+ * @param view - Raw deck view instance to position.
296
+ * @param parentRect - Parent rectangle allocated by the layout compiler.
297
+ * @returns Concrete rectangle for the compiled view.
298
+ */
299
+ function resolveViewRectWithinParent(view: View, parentRect: ViewLayoutRect): ViewLayoutRect {
300
+ const props = view.props as LayoutAwareViewProps;
301
+ const x = parentRect.x + resolveOffset(props.x, parentRect.width);
302
+ const y = parentRect.y + resolveOffset(props.y, parentRect.height);
303
+ const width = resolveDimension(props.width, parentRect.width, parentRect.width);
304
+ const height = resolveDimension(props.height, parentRect.height, parentRect.height);
305
+ return {
306
+ x,
307
+ y,
308
+ width,
309
+ height
310
+ };
311
+ }
312
+
313
+ /**
314
+ * Re-instantiates one deck view with a new resolved props object.
315
+ *
316
+ * @param view - Existing deck view instance whose constructor should be reused.
317
+ * @param props - Resolved props for the compiled view.
318
+ * @returns Fresh deck view instance with resolved numeric bounds.
319
+ */
320
+ function instantiateView(view: View, props: Record<string, unknown>): View {
321
+ const ViewConstructor = view.constructor as new (viewProps: Record<string, unknown>) => View;
322
+ return new ViewConstructor(props);
323
+ }
324
+
325
+ /**
326
+ * Reads the stack-axis length expression from one child, whether nested or raw.
327
+ *
328
+ * @param child - Candidate child entry.
329
+ * @param orientation - Active stack orientation.
330
+ * @returns Width for rows, height for columns, or `undefined` for flexible children.
331
+ */
332
+ function getAxisLengthValue(
333
+ child: Exclude<ViewLayoutChild, null | false | undefined>,
334
+ orientation: 'row' | 'column'
335
+ ): ViewLayoutLength | undefined {
336
+ if (child instanceof ViewLayoutItem) {
337
+ return orientation === 'row' ? child.props.width : child.props.height;
338
+ }
339
+ const props = child.props as LayoutAwareViewProps;
340
+ return orientation === 'row'
341
+ ? (props.width as ViewLayoutLength | undefined)
342
+ : (props.height as ViewLayoutLength | undefined);
343
+ }
344
+
345
+ /**
346
+ * Shrinks one rectangle by optional insets.
347
+ *
348
+ * @param rect - Rectangle allocated by the parent layout step.
349
+ * @param inset - Insets to apply inside that rectangle.
350
+ * @returns Inset rectangle clamped to non-negative size.
351
+ */
352
+ function applyInsets(rect: ViewLayoutRect, inset: ViewLayoutInsets | undefined): ViewLayoutRect {
353
+ if (!inset) {
354
+ return rect;
355
+ }
356
+ const top = inset.top ?? 0;
357
+ const right = inset.right ?? 0;
358
+ const bottom = inset.bottom ?? 0;
359
+ const left = inset.left ?? 0;
360
+ return {
361
+ x: rect.x + left,
362
+ y: rect.y + top,
363
+ width: Math.max(0, rect.width - left - right),
364
+ height: Math.max(0, rect.height - top - bottom)
365
+ };
366
+ }
367
+
368
+ /**
369
+ * Resolves one optional x/y offset against a parent length.
370
+ *
371
+ * @param value - Authored offset expression.
372
+ * @param parentLength - Parent width or height used for percentage resolution.
373
+ * @returns Numeric offset in deck pixels.
374
+ */
375
+ function resolveOffset(value: unknown, parentLength: number): number {
376
+ if (value === undefined || value === null) {
377
+ return 0;
378
+ }
379
+ return resolveLength(value as ViewLayoutLength, parentLength);
380
+ }
381
+
382
+ /**
383
+ * Resolves one optional width or height against a parent length.
384
+ *
385
+ * @param value - Authored dimension expression.
386
+ * @param parentLength - Parent width or height used for percentage resolution.
387
+ * @param fallback - Fallback dimension used when no explicit expression is provided.
388
+ * @returns Numeric dimension in deck pixels.
389
+ */
390
+ function resolveDimension(value: unknown, parentLength: number, fallback: number): number {
391
+ if (value === undefined || value === null) {
392
+ return fallback;
393
+ }
394
+ return resolveLength(value as ViewLayoutLength, parentLength);
395
+ }
396
+
397
+ /**
398
+ * Resolves one numeric, percentage, or `calc(...)` length expression.
399
+ *
400
+ * @param value - Authored length expression.
401
+ * @param parentLength - Parent length used for percentage resolution.
402
+ * @returns Numeric length in deck pixels.
403
+ */
404
+ function resolveLength(value: ViewLayoutLength, parentLength: number): number {
405
+ if (typeof value === 'number') {
406
+ return value;
407
+ }
408
+
409
+ const normalized = value.trim();
410
+ if (normalized.length === 0) {
411
+ throw new Error('View layout length must not be empty.');
412
+ }
413
+ if (normalized.startsWith('calc(') && normalized.endsWith(')')) {
414
+ return resolveCalcExpression(normalized.slice(5, -1), parentLength);
415
+ }
416
+ return resolveLengthTerm(normalized, parentLength);
417
+ }
418
+
419
+ /**
420
+ * Resolves one simple `calc(...)` expression made of additive length terms.
421
+ *
422
+ * @param expression - Inner `calc(...)` expression without the wrapper.
423
+ * @param parentLength - Parent length used for percentage resolution.
424
+ * @returns Numeric length in deck pixels.
425
+ */
426
+ function resolveCalcExpression(expression: string, parentLength: number): number {
427
+ const sanitized = expression.replace(/\s+/g, '');
428
+ const terms = sanitized.match(/[+-]?[^+-]+/g);
429
+ if (!terms) {
430
+ throw new Error(`Unsupported calc expression: calc(${expression})`);
431
+ }
432
+ return terms.reduce((sum, term) => sum + resolveLengthTerm(term, parentLength), 0);
433
+ }
434
+
435
+ /**
436
+ * Resolves one atomic length term such as `10`, `10px`, or `50%`.
437
+ *
438
+ * @param term - One atomic length term.
439
+ * @param parentLength - Parent length used for percentage resolution.
440
+ * @returns Numeric length in deck pixels.
441
+ */
442
+ function resolveLengthTerm(term: string, parentLength: number): number {
443
+ if (term.endsWith('%')) {
444
+ return (Number.parseFloat(term.slice(0, -1)) / 100) * parentLength;
445
+ }
446
+ if (term.endsWith('px')) {
447
+ return Number.parseFloat(term.slice(0, -2));
448
+ }
449
+ return Number.parseFloat(term);
450
+ }
451
+
452
+ /**
453
+ * Reads the string id from one deck view when present.
454
+ *
455
+ * @param view - Deck view instance to inspect.
456
+ * @returns String id or `null` when absent.
457
+ */
458
+ function getViewId(view: View): string | null {
459
+ const viewId = (view.props as LayoutAwareViewProps).id;
460
+ return typeof viewId === 'string' && viewId.length > 0 ? viewId : null;
461
+ }
462
+
463
+ /**
464
+ * Reads the required id from one deck view, throwing when missing.
465
+ *
466
+ * @param view - Deck view instance to inspect.
467
+ * @returns Non-empty string id.
468
+ */
469
+ function getRequiredViewId(view: View): string {
470
+ const viewId = getViewId(view);
471
+ if (!viewId) {
472
+ throw new Error('Every compiled deck view must have a string id.');
473
+ }
474
+ return viewId;
475
+ }
476
+
477
+ /**
478
+ * Compares two props objects for structural equality.
479
+ *
480
+ * @param left - Previous props object.
481
+ * @param right - Next props object.
482
+ * @returns Whether both props objects are structurally equal.
483
+ */
484
+ function arePropsEqual(left: Record<string, unknown>, right: Record<string, unknown>): boolean {
485
+ if (left === right) {
486
+ return true;
487
+ }
488
+
489
+ const leftKeys = Object.keys(left);
490
+ const rightKeys = Object.keys(right);
491
+ if (leftKeys.length !== rightKeys.length) {
492
+ return false;
493
+ }
494
+ return leftKeys.every(key => areValuesEqual(left[key], right[key]));
495
+ }
496
+
497
+ /**
498
+ * Recursively compares two primitive, array, or plain-object values.
499
+ *
500
+ * @param left - Previous value.
501
+ * @param right - Next value.
502
+ * @returns Whether both values are structurally equal.
503
+ */
504
+ function areValuesEqual(left: unknown, right: unknown): boolean {
505
+ if (Object.is(left, right)) {
506
+ return true;
507
+ }
508
+ if (Array.isArray(left) && Array.isArray(right)) {
509
+ return (
510
+ left.length === right.length &&
511
+ left.every((value, index) => areValuesEqual(value, right[index]))
512
+ );
513
+ }
514
+ if (isPlainObject(left) && isPlainObject(right)) {
515
+ const leftEntries = Object.keys(left);
516
+ const rightEntries = Object.keys(right);
517
+ return (
518
+ leftEntries.length === rightEntries.length &&
519
+ leftEntries.every(key => areValuesEqual(left[key], right[key]))
520
+ );
521
+ }
522
+ return false;
523
+ }
524
+
525
+ /**
526
+ * Detects plain object records used by structural equality.
527
+ *
528
+ * @param value - Candidate value.
529
+ * @returns Whether the value is a plain object record.
530
+ */
531
+ function isPlainObject(value: unknown): value is Record<string, unknown> {
532
+ return (
533
+ Boolean(value) && typeof value === 'object' && Object.getPrototypeOf(value) === Object.prototype
534
+ );
535
+ }
536
+
537
+ /**
538
+ * Removes falsey optional children from authored child arrays.
539
+ *
540
+ * @param child - Candidate authored child entry.
541
+ * @returns Whether the child should be compiled.
542
+ */
543
+ function isPresentChild(
544
+ child: ViewLayoutChild
545
+ ): child is Exclude<ViewLayoutChild, null | false | undefined> {
546
+ return Boolean(child);
547
+ }
@@ -0,0 +1,18 @@
1
+ export {
2
+ buildViewsFromViewLayout,
3
+ type CompiledDeckViews,
4
+ type ViewLayoutRect
5
+ } from './build-views-from-view-layout';
6
+ export {
7
+ ViewLayoutItem,
8
+ type ViewLayoutBaseProps,
9
+ type ViewLayoutChild,
10
+ type ViewLayoutColumnProps,
11
+ type ViewLayoutInsets,
12
+ type ViewLayoutItemProps,
13
+ type ViewLayoutLength,
14
+ type ViewLayoutOverlayProps,
15
+ type ViewLayoutRowProps,
16
+ type ViewLayoutSpacerProps,
17
+ type ViewLayoutViewProps
18
+ } from './view-layout-item';
@@ -0,0 +1,125 @@
1
+ import {View} from '@deck.gl/core';
2
+
3
+ /** CSS-like length accepted by the view layout builder. */
4
+ export type ViewLayoutLength = number | string;
5
+
6
+ /** Insets applied inside an allocated child rectangle before compiling its contents. */
7
+ export type ViewLayoutInsets = {
8
+ /** Top inset in pixels. */
9
+ top?: number;
10
+ /** Right inset in pixels. */
11
+ right?: number;
12
+ /** Bottom inset in pixels. */
13
+ bottom?: number;
14
+ /** Left inset in pixels. */
15
+ left?: number;
16
+ };
17
+
18
+ /** A raw deck.gl view or nested layout item allowed inside `children`. */
19
+ export type ViewLayoutChild = ViewLayoutItem | View | null | false | undefined;
20
+
21
+ /** Common layout metadata shared by every authored layout item. */
22
+ export type ViewLayoutBaseProps = {
23
+ /** Optional width expression resolved against the current parent bounds. */
24
+ width?: ViewLayoutLength;
25
+ /** Optional height expression resolved against the current parent bounds. */
26
+ height?: ViewLayoutLength;
27
+ /** Optional insets applied after parent layout allocation. */
28
+ inset?: ViewLayoutInsets;
29
+ };
30
+
31
+ /** One horizontal layout container that splits width among its children. */
32
+ export type ViewLayoutRowProps = ViewLayoutBaseProps & {
33
+ /** Discriminator for horizontal stack layout. */
34
+ type: 'row';
35
+ /** Ordered child items or raw deck views. */
36
+ children: readonly ViewLayoutChild[];
37
+ };
38
+
39
+ /** One vertical layout container that splits height among its children. */
40
+ export type ViewLayoutColumnProps = ViewLayoutBaseProps & {
41
+ /** Discriminator for vertical stack layout. */
42
+ type: 'column';
43
+ /** Ordered child items or raw deck views. */
44
+ children: readonly ViewLayoutChild[];
45
+ };
46
+
47
+ /** One overlay container that gives each child the same parent bounds. */
48
+ export type ViewLayoutOverlayProps = ViewLayoutBaseProps & {
49
+ /** Discriminator for overlay layout. */
50
+ type: 'overlay';
51
+ /** Ordered child items or raw deck views. */
52
+ children: readonly ViewLayoutChild[];
53
+ };
54
+
55
+ /** One wrapped raw deck view with optional layout-only overrides. */
56
+ export type ViewLayoutViewProps = ViewLayoutBaseProps & {
57
+ /** Discriminator for a wrapped raw deck view. */
58
+ type: 'view';
59
+ /** Underlying deck.gl view instance to compile. */
60
+ view: View;
61
+ };
62
+
63
+ /** One empty slot used to reserve fixed space in a stack. */
64
+ export type ViewLayoutSpacerProps = ViewLayoutBaseProps & {
65
+ /** Discriminator for an empty spacer item. */
66
+ type: 'spacer';
67
+ };
68
+
69
+ /** Discriminated union accepted by the `ViewLayoutItem` constructor. */
70
+ export type ViewLayoutItemProps =
71
+ | ViewLayoutRowProps
72
+ | ViewLayoutColumnProps
73
+ | ViewLayoutOverlayProps
74
+ | ViewLayoutViewProps
75
+ | ViewLayoutSpacerProps;
76
+
77
+ /**
78
+ * Thin runtime wrapper around one discriminated-union layout node.
79
+ */
80
+ export class ViewLayoutItem {
81
+ /** Validated props backing this layout node. */
82
+ readonly props: ViewLayoutItemProps;
83
+
84
+ /**
85
+ * Creates one validated layout item from discriminated-union props.
86
+ *
87
+ * @param props - Layout item props keyed by `type`.
88
+ */
89
+ constructor(props: ViewLayoutItemProps) {
90
+ assertViewLayoutItemProps(props);
91
+ this.props = props;
92
+ }
93
+ }
94
+
95
+ /**
96
+ * Validates one discriminated-union prop object before storing it on a layout item.
97
+ *
98
+ * @param props - Candidate layout props passed to the constructor.
99
+ */
100
+ function assertViewLayoutItemProps(props: ViewLayoutItemProps): void {
101
+ if (!props || typeof props !== 'object') {
102
+ throw new Error('ViewLayoutItem props must be an object.');
103
+ }
104
+
105
+ switch (props.type) {
106
+ case 'row':
107
+ case 'column':
108
+ case 'overlay':
109
+ if (!Array.isArray(props.children)) {
110
+ throw new Error(`ViewLayoutItem "${props.type}" requires a children array.`);
111
+ }
112
+ break;
113
+ case 'view':
114
+ if (!(props.view instanceof View)) {
115
+ throw new Error('ViewLayoutItem "view" requires a deck.gl View instance.');
116
+ }
117
+ break;
118
+ case 'spacer':
119
+ break;
120
+ default: {
121
+ const exhaustiveCheck: never = props;
122
+ throw new Error(`Unsupported view layout item: ${String(exhaustiveCheck)}`);
123
+ }
124
+ }
125
+ }