@astronox/core 0.4.3 → 0.4.5

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@astronox/core",
3
- "version": "0.4.3",
3
+ "version": "0.4.5",
4
4
  "description": "Reusable Astro components, patterns, styles, and infrastructure for AstroNox sites.",
5
5
  "license": "MIT",
6
6
  "keywords": [
@@ -103,6 +103,31 @@
103
103
  grid-template-columns: minmax(0, 7fr) minmax(0, 3fr);
104
104
  }
105
105
 
106
+ .grid[data-layout='content-sidebar']:where(
107
+ :has(> [data-composition='grid-item'][data-position='sticky'])
108
+ ) {
109
+ --grid-sticky-viewport-block-size: calc(
110
+ 100vh - var(--nox-site-header-height) - var(--nox-layout-gap) * 2
111
+ );
112
+ --grid-sticky-viewport-block-size: calc(
113
+ 100dvh - var(--nox-site-header-height) - var(--nox-layout-gap) * 2
114
+ );
115
+ --grid-sticky-peer-position: sticky;
116
+ --grid-sticky-peer-inset-block-start: var(--nox-layout-gap);
117
+ --table-progressive-runway-block-size: var(--grid-sticky-viewport-block-size);
118
+
119
+ min-block-size: var(--grid-sticky-viewport-block-size);
120
+ }
121
+
122
+ .grid[data-layout='content-sidebar']:where(
123
+ :has(> [data-composition='grid-item'][data-position='sticky'])
124
+ )
125
+ > [data-composition='grid-item'][data-position='static']
126
+ > .grid-item__content {
127
+ position: var(--grid-sticky-peer-position);
128
+ inset-block-start: var(--grid-sticky-peer-inset-block-start);
129
+ }
130
+
106
131
  .grid[data-rows] {
107
132
  grid-template-rows: repeat(var(--grid-rows), minmax(0, 1fr));
108
133
  }
@@ -112,6 +137,9 @@
112
137
  [data-layout='fixed'],
113
138
  [data-layout='content-sidebar']
114
139
  )[data-collapse-at='sm'] {
140
+ --grid-sticky-viewport-block-size: 0px;
141
+ --grid-sticky-peer-position: static;
142
+ --grid-sticky-peer-inset-block-start: auto;
115
143
  grid-template-columns: 1fr;
116
144
  }
117
145
 
@@ -164,6 +192,9 @@
164
192
  [data-layout='fixed'],
165
193
  [data-layout='content-sidebar']
166
194
  )[data-collapse-at='md'] {
195
+ --grid-sticky-viewport-block-size: 0px;
196
+ --grid-sticky-peer-position: static;
197
+ --grid-sticky-peer-inset-block-start: auto;
167
198
  grid-template-columns: 1fr;
168
199
  }
169
200
 
@@ -216,6 +247,9 @@
216
247
  [data-layout='fixed'],
217
248
  [data-layout='content-sidebar']
218
249
  )[data-collapse-at='lg'] {
250
+ --grid-sticky-viewport-block-size: 0px;
251
+ --grid-sticky-peer-position: static;
252
+ --grid-sticky-peer-inset-block-start: auto;
219
253
  grid-template-columns: 1fr;
220
254
  }
221
255
 
@@ -268,6 +302,9 @@
268
302
  [data-layout='fixed'],
269
303
  [data-layout='content-sidebar']
270
304
  )[data-collapse-at='xl'] {
305
+ --grid-sticky-viewport-block-size: 0px;
306
+ --grid-sticky-peer-position: static;
307
+ --grid-sticky-peer-inset-block-start: auto;
271
308
  grid-template-columns: 1fr;
272
309
  }
273
310
 
@@ -49,10 +49,7 @@
49
49
 
50
50
  .table[data-table-sticky-header]:has([data-sticky-surface])
51
51
  [data-table-header] {
52
- top: var(
53
- --surface-group-sticky-occlusion-size,
54
- calc(var(--nox-layout-gap) + var(--nox-section-responsive-block-size))
55
- );
52
+ top: calc(var(--nox-layout-gap) + var(--nox-section-responsive-block-size));
56
53
  }
57
54
 
58
55
  @container nox-table (width < 32rem) {
@@ -63,10 +60,7 @@
63
60
 
64
61
  .table[data-table-sticky-header]:has([data-sticky-surface])
65
62
  [data-table-header] {
66
- top: var(
67
- --surface-group-sticky-occlusion-size,
68
- calc(var(--nox-layout-gap) + var(--nox-section-compact-block-size))
69
- );
63
+ top: calc(var(--nox-layout-gap) + var(--nox-section-compact-block-size));
70
64
  }
71
65
  }
72
66
 
@@ -55,23 +55,6 @@ const stateMessage =
55
55
  data-tooltip-boundary
56
56
  data-tone={dataTone}
57
57
  >
58
- <div
59
- class="table-body__initial-skeleton"
60
- data-table-initial-skeleton
61
- aria-hidden="true"
62
- >
63
- {
64
- columns.map((column) => (
65
- <div
66
- class="table-skeleton__cell"
67
- data-align={getTableColumnAlignment(column)}
68
- data-table-column-role={column.role}
69
- >
70
- <span class="table-skeleton__placeholder" />
71
- </div>
72
- ))
73
- }
74
- </div>
75
58
  <table class="table-body__table">
76
59
  <caption class="table-body__visually-hidden">{caption}</caption>
77
60
  <thead class="table-body__visually-hidden">
@@ -49,13 +49,19 @@
49
49
  position: relative;
50
50
  display: block;
51
51
  overflow: visible;
52
- block-size: 0;
53
- min-block-size: 0;
52
+ block-size: var(--table-progressive-runway-block-size, 0px);
53
+ min-block-size: var(--table-progressive-runway-block-size, 0px);
54
54
  border: 0;
55
55
  border-radius: 0;
56
56
  background: transparent;
57
57
  }
58
58
 
59
+ .table-pagination[data-table-progressive-enhanced='true']
60
+ .table-pagination__sentinel {
61
+ inset-block-start: 0;
62
+ inset-block-end: auto;
63
+ }
64
+
59
65
  .table-pagination[data-table-progressive-enhanced='true']
60
66
  .table-pagination__content {
61
67
  block-size: 0;
@@ -76,7 +82,8 @@
76
82
  .table-pagination__link:focus-visible {
77
83
  z-index: 1;
78
84
  inset-inline: 0;
79
- inset-block-end: 0;
85
+ inset-block-start: 0;
86
+ inset-block-end: auto;
80
87
  display: grid;
81
88
  overflow: visible;
82
89
  width: auto;
@@ -7,23 +7,6 @@
7
7
  border-block-start: 0;
8
8
  }
9
9
 
10
- .table-body__initial-skeleton {
11
- display: none;
12
- min-width: 0;
13
- min-height: var(--table-row-min-block-size);
14
- grid-template-columns: var(--table-column-template);
15
- }
16
-
17
- [data-nox-table-enhanced]
18
- .table:is(
19
- [data-table-loading-state='pending'],
20
- [data-table-loading-state='refreshing']
21
- )
22
- .table-body:not(:has(.table-skeleton__row))
23
- > .table-body__initial-skeleton {
24
- display: grid;
25
- }
26
-
27
10
  .table-skeleton__row + .table-skeleton__row,
28
11
  [data-nox-table-enhanced]
29
12
  .table[data-table-loading-state='appending']
@@ -34,6 +17,9 @@
34
17
 
35
18
  [data-nox-table-enhanced]
36
19
  .table[data-table-loading-state='pending']
20
+ .table-body__row[data-table-row],
21
+ [data-nox-table-enhanced]
22
+ .table[data-table-loading-state='refreshing']
37
23
  .table-body__row[data-table-row] {
38
24
  display: none;
39
25
  }
@@ -97,11 +83,6 @@
97
83
  min-height: var(--table-index-row-block-size);
98
84
  }
99
85
 
100
- .table[data-table-layout='index'] .table-body__initial-skeleton {
101
- block-size: var(--table-index-row-block-size);
102
- min-height: var(--table-index-row-block-size);
103
- }
104
-
105
86
  @media (prefers-reduced-motion: no-preference) {
106
87
  .table-skeleton__placeholder {
107
88
  animation: table-skeleton-pulse 1.4s ease-in-out infinite;
@@ -119,8 +100,7 @@
119
100
  }
120
101
 
121
102
  @container (width < 36rem) {
122
- .table-skeleton__row,
123
- .table-body__initial-skeleton {
103
+ .table-skeleton__row {
124
104
  min-height: var(--table-row-narrow-min-block-size);
125
105
  }
126
106
  }
@@ -3,7 +3,6 @@ import type {
3
3
  TableProgressiveModel,
4
4
  } from '../../display/contracts';
5
5
  import { tableProgressiveBatchSize } from '../../display/tableProgressive';
6
- import { mutateTablePreservingScroll } from './tableScrollPosition';
7
6
 
8
7
  const initializedAttribute = 'data-table-progressive-initialized';
9
8
 
@@ -152,8 +151,6 @@ export function initTableProgressive(root: HTMLElement): () => void {
152
151
  let disposed = false;
153
152
  let generation = 0;
154
153
  let prepared = false;
155
- let retainedWindowSize = Math.max(batchSize, getRows(activeRowsRoot).length);
156
- let scheduledRenderFrame: number | null = null;
157
154
 
158
155
  registerRows(getRows(activeRowsRoot));
159
156
 
@@ -184,11 +181,8 @@ export function initTableProgressive(root: HTMLElement): () => void {
184
181
  activeSkeletonTemplate.content.querySelectorAll<HTMLElement>(
185
182
  '[data-table-skeleton-row]',
186
183
  ),
187
- );
188
- if (candidates.length === 0) return;
189
- skeletonRows = Array.from({ length: count }, (_, index) =>
190
- document.importNode(candidates[index % candidates.length]!, true),
191
- );
184
+ ).slice(0, count);
185
+ skeletonRows = candidates.map((row) => document.importNode(row, true));
192
186
  insertBeforeEmpty(activeRowsRoot, empty, skeletonRows);
193
187
  }
194
188
 
@@ -210,21 +204,6 @@ export function initTableProgressive(root: HTMLElement): () => void {
210
204
  );
211
205
  }
212
206
 
213
- function replaceRows(rows: readonly HTMLElement[]): void {
214
- for (const row of rows) row.hidden = false;
215
- mutateTablePreservingScroll(root, () => {
216
- removeSkeleton();
217
- detachRenderedRows();
218
- insertBeforeEmpty(activeRowsRoot, empty, rows);
219
- });
220
- root.dispatchEvent(
221
- new CustomEvent<TableRowsAppendedDetail>('table:rows-appended', {
222
- bubbles: true,
223
- detail: { rows },
224
- }),
225
- );
226
- }
227
-
228
207
  function updateEmptyState(): void {
229
208
  if (empty) empty.hidden = results.length > 0;
230
209
  }
@@ -307,12 +286,10 @@ export function initTableProgressive(root: HTMLElement): () => void {
307
286
  TableLoadingState,
308
287
  'pending' | 'refreshing' | 'appending'
309
288
  > = 'appending',
310
- requestedCount = batchSize,
311
- replace = false,
312
289
  ): Promise<void> {
313
290
  if (request || disposed || cursor >= results.length) return;
314
291
 
315
- const target = results.slice(cursor, cursor + requestedCount);
292
+ const target = results.slice(cursor, cursor + batchSize);
316
293
  if (target.length === 0) {
317
294
  removeSkeleton();
318
295
  updatePagination();
@@ -328,9 +305,7 @@ export function initTableProgressive(root: HTMLElement): () => void {
328
305
  request = activeRequest;
329
306
  setPaginationState(activePagination, activeStatus, 'loading');
330
307
  updateTableState(loadingState);
331
- if (loadingState === 'appending' || skeletonRows.length === 0) {
332
- addSkeleton(target.length);
333
- }
308
+ addSkeleton(target.length);
334
309
 
335
310
  try {
336
311
  await loadSources(missing, activeRequest.signal);
@@ -361,16 +336,9 @@ export function initTableProgressive(root: HTMLElement): () => void {
361
336
  return;
362
337
  }
363
338
 
364
- if (replace) replaceRows(rows as HTMLElement[]);
365
- else {
366
- removeSkeleton();
367
- appendRows(rows as HTMLElement[]);
368
- }
339
+ removeSkeleton();
340
+ appendRows(rows as HTMLElement[]);
369
341
  cursor += target.length;
370
- retainedWindowSize = Math.max(
371
- retainedWindowSize,
372
- getRows(activeRowsRoot).length,
373
- );
374
342
  updatePagination();
375
343
  updateTableState('ready');
376
344
  }
@@ -435,48 +403,23 @@ export function initTableProgressive(root: HTMLElement): () => void {
435
403
  offset: number,
436
404
  state: Extract<TableLoadingState, 'pending' | 'refreshing'>,
437
405
  ): void {
438
- retainedWindowSize = Math.max(
439
- retainedWindowSize,
440
- getRows(activeRowsRoot).length,
441
- );
442
406
  generation += 1;
443
407
  request?.abort();
444
408
  request = null;
445
- if (scheduledRenderFrame !== null) {
446
- window.cancelAnimationFrame(scheduledRenderFrame);
447
- scheduledRenderFrame = null;
448
- }
449
- observer.disconnect();
409
+ activePagination.hidden = false;
450
410
  updateTableState(state);
451
- setPaginationState(activePagination, activeStatus, 'loading');
452
- if (empty) empty.hidden = true;
453
- mutateTablePreservingScroll(root, () => {
454
- detachRenderedRows();
455
- addSkeleton(retainedWindowSize);
456
- });
457
-
411
+ addSkeleton(batchSize);
412
+ detachRenderedRows();
458
413
  applyQuery();
459
414
  cursor = Math.min(offset, results.length);
460
- const activeGeneration = generation;
461
- scheduledRenderFrame = window.requestAnimationFrame(() => {
462
- scheduledRenderFrame = window.requestAnimationFrame(() => {
463
- scheduledRenderFrame = null;
464
- if (disposed || generation !== activeGeneration) return;
465
-
466
- updateEmptyState();
467
- if (results.length === 0) {
468
- replaceRows([]);
469
- updatePagination();
470
- updateTableState('empty');
471
- return;
472
- }
473
- void renderNextBatch(
474
- state,
475
- Math.min(retainedWindowSize, results.length - cursor),
476
- true,
477
- );
478
- });
479
- });
415
+ updateEmptyState();
416
+ if (results.length === 0) {
417
+ removeSkeleton();
418
+ updatePagination();
419
+ updateTableState('empty');
420
+ return;
421
+ }
422
+ void renderNextBatch(state);
480
423
  }
481
424
 
482
425
  function refresh(): void {
@@ -492,10 +435,6 @@ export function initTableProgressive(root: HTMLElement): () => void {
492
435
  destroy() {
493
436
  disposed = true;
494
437
  request?.abort();
495
- if (scheduledRenderFrame !== null) {
496
- window.cancelAnimationFrame(scheduledRenderFrame);
497
- scheduledRenderFrame = null;
498
- }
499
438
  observer.disconnect();
500
439
  removeSkeleton();
501
440
  activePagination.hidden = !fallbackLink;
@@ -4,7 +4,6 @@ import {
4
4
  setTableProgressiveSort,
5
5
  updateTableProgressiveEntry,
6
6
  } from './tableProgressiveController';
7
- import { mutateTablePreservingScroll } from './tableScrollPosition';
8
7
 
9
8
  type SortDirection = 'ascending' | 'descending';
10
9
  type SortType = 'number' | 'text';
@@ -97,11 +96,9 @@ export function initTableSort(root: HTMLElement): () => void {
97
96
  orderDirty = activeState !== null;
98
97
  if (rows.every((row, index) => row === sortedRows[index])) return;
99
98
 
100
- mutateTablePreservingScroll(root, () => {
101
- const fragment = document.createDocumentFragment();
102
- for (const row of sortedRows) fragment.append(row);
103
- rowsRoot.prepend(fragment);
104
- });
99
+ const fragment = document.createDocumentFragment();
100
+ for (const row of sortedRows) fragment.append(row);
101
+ rowsRoot.prepend(fragment);
105
102
  };
106
103
 
107
104
  const scheduleSort = (): void => {
@@ -1,41 +0,0 @@
1
- type ScrollRestoreState = {
2
- frame: number;
3
- originalOverflowAnchor: string;
4
- scrollRoot: HTMLElement;
5
- scrollTop: number;
6
- };
7
-
8
- const restoreStates = new WeakMap<HTMLElement, ScrollRestoreState>();
9
-
10
- export function mutateTablePreservingScroll(
11
- root: HTMLElement,
12
- mutation: () => void,
13
- ): void {
14
- const scrollRoot = root.closest<HTMLElement>('[data-site-viewport]');
15
- if (!scrollRoot) {
16
- mutation();
17
- return;
18
- }
19
-
20
- const previous = restoreStates.get(root);
21
- if (previous) window.cancelAnimationFrame(previous.frame);
22
- const originalOverflowAnchor =
23
- previous?.originalOverflowAnchor ?? root.style.overflowAnchor;
24
- const scrollTop = scrollRoot.scrollTop;
25
-
26
- root.style.overflowAnchor = 'none';
27
- mutation();
28
- scrollRoot.scrollTop = scrollTop;
29
-
30
- const state: ScrollRestoreState = {
31
- frame: window.requestAnimationFrame(() => {
32
- scrollRoot.scrollTop = scrollTop;
33
- root.style.overflowAnchor = originalOverflowAnchor;
34
- restoreStates.delete(root);
35
- }),
36
- originalOverflowAnchor,
37
- scrollRoot,
38
- scrollTop,
39
- };
40
- restoreStates.set(root, state);
41
- }