@a4ui/core 0.35.0 → 0.35.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/elements.css +3 -0
- package/dist/elements.iife.js +2 -2
- package/dist/elements.js +115 -114
- package/dist/full.css +3 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +4087 -4074
- package/dist/ui/RingProgress.d.ts +2 -0
- package/package.json +1 -1
- package/src/index.ts +1 -1
- package/src/ui/AudioWaveform.tsx +1 -3
- package/src/ui/EventScheduler.tsx +7 -1
- package/src/ui/GanttChart.tsx +9 -1
- package/src/ui/InteractiveMap.tsx +63 -10
- package/src/ui/OnboardingChecklist.tsx +1 -0
- package/src/ui/PivotTable.tsx +0 -0
- package/src/ui/QueryBuilder.tsx +4 -3
- package/src/ui/RingProgress.tsx +3 -0
- package/src/ui/SpreadsheetGrid.tsx +16 -13
|
@@ -7,6 +7,8 @@ export interface RingProgressProps {
|
|
|
7
7
|
thickness?: number;
|
|
8
8
|
/** Content centered over the ring. Defaults to the rounded percent. */
|
|
9
9
|
label?: JSX.Element;
|
|
10
|
+
/** Accessible name for the `role="progressbar"` element. */
|
|
11
|
+
'aria-label'?: string;
|
|
10
12
|
class?: string;
|
|
11
13
|
}
|
|
12
14
|
/**
|
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
// import '@a4ui/core/styles.css'
|
|
9
9
|
// import { Button, Card, Modal } from '@a4ui/core'
|
|
10
10
|
|
|
11
|
-
export const A4UI_VERSION = '0.35.
|
|
11
|
+
export const A4UI_VERSION = '0.35.2'
|
|
12
12
|
|
|
13
13
|
// Helpers (src/lib) — generic, framework-level utilities.
|
|
14
14
|
export { cn } from './lib/cn'
|
package/src/ui/AudioWaveform.tsx
CHANGED
|
@@ -167,10 +167,8 @@ export function AudioWaveform(props: AudioWaveformProps): JSX.Element {
|
|
|
167
167
|
{(peak, index) => {
|
|
168
168
|
const played = createMemo(() => index() / bars().length <= progress())
|
|
169
169
|
return (
|
|
170
|
-
<
|
|
171
|
-
type="button"
|
|
170
|
+
<div
|
|
172
171
|
aria-hidden="true"
|
|
173
|
-
tabIndex={-1}
|
|
174
172
|
onClick={(event) => {
|
|
175
173
|
event.stopPropagation()
|
|
176
174
|
handleBarClick(index())
|
|
@@ -203,7 +203,13 @@ export function EventScheduler(props: EventSchedulerProps): JSX.Element {
|
|
|
203
203
|
</div>
|
|
204
204
|
|
|
205
205
|
{/* Body: scrolls vertically; gutter of hour labels + day columns */}
|
|
206
|
-
<div
|
|
206
|
+
<div
|
|
207
|
+
role="group"
|
|
208
|
+
aria-label="Schedule grid"
|
|
209
|
+
tabIndex={0}
|
|
210
|
+
class="flex overflow-y-auto outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-primary"
|
|
211
|
+
style={{ 'max-height': '32rem' }}
|
|
212
|
+
>
|
|
207
213
|
<div class="w-14 shrink-0" style={{ height: `${gridHeight()}px` }}>
|
|
208
214
|
<For each={hours()}>
|
|
209
215
|
{(h) => (
|
package/src/ui/GanttChart.tsx
CHANGED
|
@@ -155,7 +155,15 @@ export function GanttChart(props: GanttChartProps): JSX.Element {
|
|
|
155
155
|
})
|
|
156
156
|
|
|
157
157
|
return (
|
|
158
|
-
<div
|
|
158
|
+
<div
|
|
159
|
+
role="group"
|
|
160
|
+
aria-label="Gantt chart"
|
|
161
|
+
tabIndex={0}
|
|
162
|
+
class={cn(
|
|
163
|
+
'overflow-x-auto rounded-lg border border-border bg-card outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-primary',
|
|
164
|
+
props.class,
|
|
165
|
+
)}
|
|
166
|
+
>
|
|
159
167
|
<Show
|
|
160
168
|
when={props.tasks.length > 0}
|
|
161
169
|
fallback={<p class="p-4 text-sm text-muted-foreground">No tasks to display.</p>}
|
|
@@ -51,10 +51,21 @@ interface WorldPixel {
|
|
|
51
51
|
y: number
|
|
52
52
|
}
|
|
53
53
|
|
|
54
|
+
/**
|
|
55
|
+
* A tile's IDENTITY, with no position in it.
|
|
56
|
+
*
|
|
57
|
+
* `id` and `src` are both pure functions of z/x/y, so a given tile's
|
|
58
|
+
* descriptor never changes while it is on screen — only where it sits does.
|
|
59
|
+
* Keeping position out of this type is what lets the memo below hand `<For>`
|
|
60
|
+
* the same object across a pan, so the `<img>` survives instead of being torn
|
|
61
|
+
* down and rebuilt.
|
|
62
|
+
*/
|
|
54
63
|
interface TileDescriptor {
|
|
55
64
|
id: string
|
|
56
|
-
|
|
57
|
-
|
|
65
|
+
/** Tile column, unwrapped — multiplied by TILE_SIZE to place the tile. */
|
|
66
|
+
x: number
|
|
67
|
+
/** Tile row. */
|
|
68
|
+
y: number
|
|
58
69
|
src: string
|
|
59
70
|
}
|
|
60
71
|
|
|
@@ -174,9 +185,33 @@ export function InteractiveMap(props: InteractiveMapProps): JSX.Element {
|
|
|
174
185
|
return { x: c.x - w / 2, y: c.y - h / 2 }
|
|
175
186
|
})
|
|
176
187
|
|
|
188
|
+
/**
|
|
189
|
+
* Live tiles, keyed by id so identity survives a pan.
|
|
190
|
+
*
|
|
191
|
+
* `<For>` in Solid keys by REFERENCE. This memo used to build fresh
|
|
192
|
+
* descriptor objects on every recompute, so every pointer move made every
|
|
193
|
+
* tile a "new" item: the whole grid was removed from the DOM and rebuilt,
|
|
194
|
+
* even though the same tiles were still on screen. Measured on a 20-tile
|
|
195
|
+
* map, one ~500 ms drag produced 340 element insertions and 344 removals.
|
|
196
|
+
*
|
|
197
|
+
* The fade-in below turned that churn into a visible strobe. A rebuilt
|
|
198
|
+
* `<img>` starts at `opacity-0` and only becomes visible when its `load`
|
|
199
|
+
* event re-adds `a4ui-tile-loaded`, so during a pan every tile blinked out
|
|
200
|
+
* and back roughly seventeen times a second — the file header has always
|
|
201
|
+
* claimed tiles are "repositioned (not reloaded) as the user pans", and now
|
|
202
|
+
* they are.
|
|
203
|
+
*
|
|
204
|
+
* Reusing entries also means the cache holds only what is on screen: ids
|
|
205
|
+
* that fall out of range are dropped on the same pass that adds new ones.
|
|
206
|
+
*/
|
|
207
|
+
const tileCache = new Map<string, TileDescriptor>()
|
|
208
|
+
|
|
177
209
|
const tiles = createMemo<TileDescriptor[]>(() => {
|
|
178
210
|
const { w, h } = size()
|
|
179
|
-
if (w <= 0 || h <= 0)
|
|
211
|
+
if (w <= 0 || h <= 0) {
|
|
212
|
+
tileCache.clear()
|
|
213
|
+
return []
|
|
214
|
+
}
|
|
180
215
|
const z = zoom()
|
|
181
216
|
const numTiles = 2 ** z
|
|
182
217
|
const tl = topLeftPx()
|
|
@@ -188,17 +223,29 @@ export function InteractiveMap(props: InteractiveMapProps): JSX.Element {
|
|
|
188
223
|
const maxY = Math.min(numTiles - 1, Math.floor((tl.y + h) / TILE_SIZE) + 1)
|
|
189
224
|
|
|
190
225
|
const list: TileDescriptor[] = []
|
|
226
|
+
const live = new Set<string>()
|
|
227
|
+
|
|
191
228
|
for (let x = minX; x <= maxX; x++) {
|
|
192
229
|
const wrappedX = ((x % numTiles) + numTiles) % numTiles
|
|
193
230
|
for (let y = minY; y <= maxY; y++) {
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
231
|
+
const id = `${z}/${x}/${y}`
|
|
232
|
+
live.add(id)
|
|
233
|
+
let tile = tileCache.get(id)
|
|
234
|
+
// The url prop can change at runtime, so a cached tile is only
|
|
235
|
+
// reusable while its src still matches.
|
|
236
|
+
const src = buildTileUrl(url, z, wrappedX, y)
|
|
237
|
+
if (!tile || tile.src !== src) {
|
|
238
|
+
tile = { id, x, y, src }
|
|
239
|
+
tileCache.set(id, tile)
|
|
240
|
+
}
|
|
241
|
+
list.push(tile)
|
|
200
242
|
}
|
|
201
243
|
}
|
|
244
|
+
|
|
245
|
+
for (const id of tileCache.keys()) {
|
|
246
|
+
if (!live.has(id)) tileCache.delete(id)
|
|
247
|
+
}
|
|
248
|
+
|
|
202
249
|
return list
|
|
203
250
|
})
|
|
204
251
|
|
|
@@ -309,7 +356,13 @@ export function InteractiveMap(props: InteractiveMapProps): JSX.Element {
|
|
|
309
356
|
!motionReduced() &&
|
|
310
357
|
'opacity-0 transition-opacity duration-150 [&.a4ui-tile-loaded]:opacity-100',
|
|
311
358
|
)}
|
|
312
|
-
|
|
359
|
+
// Read from the reactive origin rather than from the tile, so
|
|
360
|
+
// panning updates two style properties on an element that stays
|
|
361
|
+
// put instead of replacing it.
|
|
362
|
+
style={{
|
|
363
|
+
left: `${tile.x * TILE_SIZE - topLeftPx().x}px`,
|
|
364
|
+
top: `${tile.y * TILE_SIZE - topLeftPx().y}px`,
|
|
365
|
+
}}
|
|
313
366
|
onLoad={(event) => event.currentTarget.classList.add('a4ui-tile-loaded')}
|
|
314
367
|
/>
|
|
315
368
|
)}
|
package/src/ui/PivotTable.tsx
CHANGED
|
Binary file
|
package/src/ui/QueryBuilder.tsx
CHANGED
|
@@ -243,10 +243,10 @@ function RuleRow(props: RuleRowProps): JSX.Element {
|
|
|
243
243
|
|
|
244
244
|
return (
|
|
245
245
|
<div class="flex flex-wrap items-center gap-2">
|
|
246
|
-
<Select class="w-40" value={props.rule.field} onChange={setField}>
|
|
246
|
+
<Select class="w-40" value={props.rule.field} onChange={setField} aria-label="Field">
|
|
247
247
|
<For each={props.fields}>{(f) => <option value={f.name}>{f.label}</option>}</For>
|
|
248
248
|
</Select>
|
|
249
|
-
<Select class="w-32" value={props.rule.operator} onChange={setOperator}>
|
|
249
|
+
<Select class="w-32" value={props.rule.operator} onChange={setOperator} aria-label="Operator">
|
|
250
250
|
<For each={props.operatorsFor(props.rule.field)}>{(op) => <option value={op}>{op}</option>}</For>
|
|
251
251
|
</Select>
|
|
252
252
|
<Show
|
|
@@ -257,10 +257,11 @@ function RuleRow(props: RuleRowProps): JSX.Element {
|
|
|
257
257
|
type={field()?.type === 'number' ? 'number' : 'text'}
|
|
258
258
|
value={props.rule.value}
|
|
259
259
|
onInput={setValue}
|
|
260
|
+
aria-label="Value"
|
|
260
261
|
/>
|
|
261
262
|
}
|
|
262
263
|
>
|
|
263
|
-
<Select class="w-40" value={props.rule.value} onChange={setValue}>
|
|
264
|
+
<Select class="w-40" value={props.rule.value} onChange={setValue} aria-label="Value">
|
|
264
265
|
<option value="" disabled>
|
|
265
266
|
Select…
|
|
266
267
|
</option>
|
package/src/ui/RingProgress.tsx
CHANGED
|
@@ -12,6 +12,8 @@ export interface RingProgressProps {
|
|
|
12
12
|
thickness?: number
|
|
13
13
|
/** Content centered over the ring. Defaults to the rounded percent. */
|
|
14
14
|
label?: JSX.Element
|
|
15
|
+
/** Accessible name for the `role="progressbar"` element. */
|
|
16
|
+
'aria-label'?: string
|
|
15
17
|
class?: string
|
|
16
18
|
}
|
|
17
19
|
|
|
@@ -37,6 +39,7 @@ export function RingProgress(props: RingProgressProps): JSX.Element {
|
|
|
37
39
|
return (
|
|
38
40
|
<div
|
|
39
41
|
role="progressbar"
|
|
42
|
+
aria-label={props['aria-label']}
|
|
40
43
|
aria-valuenow={clamped()}
|
|
41
44
|
aria-valuemin={0}
|
|
42
45
|
aria-valuemax={100}
|
|
@@ -235,6 +235,7 @@ export function SpreadsheetGrid(props: SpreadsheetGridProps): JSX.Element {
|
|
|
235
235
|
ref={containerRef}
|
|
236
236
|
tabIndex={0}
|
|
237
237
|
role="grid"
|
|
238
|
+
aria-label="Spreadsheet"
|
|
238
239
|
aria-rowcount={props.rows}
|
|
239
240
|
aria-colcount={props.cols}
|
|
240
241
|
class={cn(
|
|
@@ -244,20 +245,22 @@ export function SpreadsheetGrid(props: SpreadsheetGridProps): JSX.Element {
|
|
|
244
245
|
onKeyDown={handleKeyDown}
|
|
245
246
|
>
|
|
246
247
|
<div class="grid" style={{ 'grid-template-columns': `48px repeat(${props.cols}, minmax(80px, 1fr))` }}>
|
|
247
|
-
<div
|
|
248
|
-
|
|
249
|
-
{(
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
248
|
+
<div role="row" class="contents">
|
|
249
|
+
<div aria-hidden="true" class="border-b border-r border-border bg-muted" />
|
|
250
|
+
<For each={headers()}>
|
|
251
|
+
{(h) => (
|
|
252
|
+
<div
|
|
253
|
+
role="columnheader"
|
|
254
|
+
class="border-b border-r border-border bg-muted px-2 py-1 text-center text-xs font-semibold text-muted-foreground"
|
|
255
|
+
>
|
|
256
|
+
{h}
|
|
257
|
+
</div>
|
|
258
|
+
)}
|
|
259
|
+
</For>
|
|
260
|
+
</div>
|
|
258
261
|
<For each={rowIndices()}>
|
|
259
262
|
{(r) => (
|
|
260
|
-
|
|
263
|
+
<div role="row" class="contents">
|
|
261
264
|
<div
|
|
262
265
|
role="rowheader"
|
|
263
266
|
class="border-b border-r border-border bg-muted px-2 py-1 text-center text-xs text-muted-foreground"
|
|
@@ -295,7 +298,7 @@ export function SpreadsheetGrid(props: SpreadsheetGridProps): JSX.Element {
|
|
|
295
298
|
</div>
|
|
296
299
|
)}
|
|
297
300
|
</For>
|
|
298
|
-
|
|
301
|
+
</div>
|
|
299
302
|
)}
|
|
300
303
|
</For>
|
|
301
304
|
</div>
|