@altertable/data-app 0.64.0 → 0.66.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/AGENTS.md +40 -0
- package/CONTRIBUTING.md +37 -3
- package/README.md +2 -2
- package/dist/chunks/{contract-mwe7gnmh.js → contract-13j5zb3c.js} +110 -216
- package/dist/chunks/contract-13j5zb3c.js.map +13 -0
- package/dist/chunks/contract-19ckn3n7.js +78 -0
- package/dist/chunks/contract-19ckn3n7.js.map +10 -0
- package/dist/chunks/contract-awa2d5b1.js +272 -0
- package/dist/chunks/contract-awa2d5b1.js.map +12 -0
- package/dist/chunks/{contract-xtxza1fy.js → contract-e11m4y7f.js} +35 -71
- package/dist/chunks/contract-e11m4y7f.js.map +12 -0
- package/dist/chunks/{contract-zr3jd7mr.js → contract-h8a6f559.js} +100 -75
- package/dist/chunks/contract-h8a6f559.js.map +12 -0
- package/dist/chunks/contract-havnjmdr.js +13011 -0
- package/dist/chunks/contract-havnjmdr.js.map +99 -0
- package/dist/chunks/{contract-txjy0en2.js → contract-m6n8ctfc.js} +64 -72
- package/dist/chunks/contract-m6n8ctfc.js.map +10 -0
- package/dist/chunks/contract-pk603qj7.js +146 -0
- package/dist/chunks/contract-pk603qj7.js.map +10 -0
- package/dist/chunks/{contract-yxbjea23.js → contract-rfbakka4.js} +179 -2
- package/dist/chunks/contract-rfbakka4.js.map +13 -0
- package/dist/client/index.js +11 -6
- package/dist/client/index.js.map +1 -1
- package/dist/core/appearance.js +1 -1
- package/dist/core/contract.js +13 -3
- package/dist/core/contract.js.map +1 -1
- package/dist/embed/index.js +25 -13
- package/dist/embed/index.js.map +3 -3
- package/dist/local.js +1 -1
- package/dist/local.js.map +2 -2
- package/dist/react/embed/index.js +5 -2
- package/dist/react/embed/index.js.map +3 -3
- package/dist/react/index.js +2532 -11273
- package/dist/react/index.js.map +16 -88
- package/dist/react/ui/index.js +1614 -0
- package/dist/react/ui/index.js.map +22 -0
- package/dist/server.js +1 -1
- package/dist/server.js.map +2 -2
- package/dist/types/client/annotations.d.ts +23 -0
- package/dist/types/client/iframe.d.ts +2 -0
- package/dist/types/client/index.d.ts +2 -0
- package/dist/types/client/logger.d.ts +6 -0
- package/dist/types/core/annotations.d.ts +92 -0
- package/dist/types/core/appearance.d.ts +2 -2
- package/dist/types/core/bridge-endpoint.d.ts +21 -0
- package/dist/types/core/bridge.d.ts +137 -16
- package/dist/types/core/contract.d.ts +2 -0
- package/dist/types/core/logger.d.ts +12 -0
- package/dist/types/core/presentation.d.ts +2 -0
- package/dist/types/core/variables.d.ts +2 -5
- package/dist/types/embed/host.d.ts +6 -2
- package/dist/types/embed/index.d.ts +1 -0
- package/dist/types/embed/source.d.ts +1 -1
- package/dist/types/react/annotations/AnnotationBar.d.ts +24 -0
- package/dist/types/react/annotations/AnnotationControls.d.ts +10 -0
- package/dist/types/react/annotations/AnnotationEditor.d.ts +15 -0
- package/dist/types/react/annotations/AnnotationMarkers.d.ts +19 -0
- package/dist/types/react/annotations/AnnotationSelectionLayer.d.ts +12 -0
- package/dist/types/react/annotations/AnnotationTarget.d.ts +9 -0
- package/dist/types/react/annotations/AnnotationTooltip.d.ts +9 -0
- package/dist/types/react/annotations/AnnotationTrigger.d.ts +10 -0
- package/dist/types/react/annotations/annotation-editor-state.d.ts +56 -0
- package/dist/types/react/annotations/annotation-screenshot.d.ts +4 -0
- package/dist/types/react/annotations/annotation-targets.d.ts +33 -0
- package/dist/types/react/annotations/getAnnotationProps.d.ts +15 -0
- package/dist/types/react/annotations/styles.d.ts +3 -0
- package/dist/types/react/annotations/useAnnotationGeometry.d.ts +22 -0
- package/dist/types/react/annotations/useDataAppAnnotations.d.ts +19 -0
- package/dist/types/react/bindings.d.ts +110 -0
- package/dist/types/react/content.d.ts +24 -11
- package/dist/types/react/embed/bridge.d.ts +1 -1
- package/dist/types/react/hooks.d.ts +29 -787
- package/dist/types/react/index.d.ts +30 -104
- package/dist/types/react/source-owner.d.ts +2 -0
- package/dist/types/react/style-contract.d.ts +60 -0
- package/dist/types/react/style-validation.d.ts +2 -0
- package/dist/types/react/ui/AboutData.d.ts +9 -1
- package/dist/types/react/ui/AppHeader.d.ts +1 -2
- package/dist/types/react/ui/AppLayout.d.ts +3 -9
- package/dist/types/react/ui/AppToolbar.d.ts +6 -7
- package/dist/types/react/ui/AreaChart.d.ts +7 -0
- package/dist/types/react/ui/BarChart.d.ts +6 -0
- package/dist/types/react/ui/{ComparisonVisual.d.ts → Comparison.d.ts} +3 -3
- package/dist/types/react/ui/ContentSkeleton.d.ts +2 -0
- package/dist/types/react/ui/DataApp.d.ts +17 -53
- package/dist/types/react/ui/DataAppFrame.d.ts +42 -0
- package/dist/types/react/ui/DataBoundary.d.ts +4 -5
- package/dist/types/react/ui/DataSection.d.ts +6 -18
- package/dist/types/react/ui/DataSectionBoundary.d.ts +30 -0
- package/dist/types/react/ui/DataValue.d.ts +9 -0
- package/dist/types/react/ui/DataWidget.d.ts +2 -1
- package/dist/types/react/ui/DateTimeTooltip.d.ts +1 -1
- package/dist/types/react/ui/ExportControl.d.ts +4 -0
- package/dist/types/react/ui/HelpPopover.d.ts +3 -4
- package/dist/types/react/ui/InspectionContext.d.ts +2 -0
- package/dist/types/react/ui/InspectionProvider.d.ts +29 -0
- package/dist/types/react/ui/LineChart.d.ts +7 -0
- package/dist/types/react/ui/MetricWidget.d.ts +4 -3
- package/dist/types/react/ui/PieChart.d.ts +8 -0
- package/dist/types/react/ui/PresentStory.d.ts +2 -5
- package/dist/types/react/ui/RefreshControl.d.ts +1 -2
- package/dist/types/react/ui/ScatterChart.d.ts +17 -0
- package/dist/types/react/ui/SearchField.d.ts +1 -2
- package/dist/types/react/ui/SearchInput.d.ts +3 -1
- package/dist/types/react/ui/Sheet.d.ts +2 -1
- package/dist/types/react/ui/Skeleton.d.ts +5 -2
- package/dist/types/react/ui/StaticDataApp.d.ts +4 -0
- package/dist/types/react/ui/TableWidget.d.ts +19 -16
- package/dist/types/react/ui/Tabs.d.ts +6 -2
- package/dist/types/react/ui/TextWidget.d.ts +2 -1
- package/dist/types/react/ui/Toast.d.ts +8 -0
- package/dist/types/react/ui/Tooltip.d.ts +4 -3
- package/dist/types/react/ui/TrendChart.d.ts +5 -0
- package/dist/types/react/ui/UpdatedAt.d.ts +1 -1
- package/dist/types/react/ui/VisualizationWidget.d.ts +12 -13
- package/dist/types/react/ui/WidgetViewTabs.d.ts +1 -1
- package/dist/types/react/ui/chart-data.d.ts +25 -0
- package/dist/types/react/ui/csv-export.d.ts +19 -0
- package/dist/types/react/ui/data-context.d.ts +18 -7
- package/dist/types/react/ui/icons.d.ts +2 -0
- package/dist/types/react/ui/index.d.ts +74 -0
- package/dist/types/react/ui/metric.d.ts +1 -1
- package/dist/types/react/ui/presentation.d.ts +5 -1
- package/dist/types/react/ui/shortcuts.d.ts +7 -1
- package/dist/types/react/view-runtime.d.ts +29 -0
- package/dist/types/react/view.d.ts +21 -4
- package/dist/types/react/widgets.d.ts +79 -0
- package/dist/worker.js +381 -79
- package/docs/app-authoring.md +64 -30
- package/docs/data-context.md +89 -0
- package/docs/embed.md +1 -1
- package/docs/formatting-and-appearance.md +52 -0
- package/docs/hosted-apps.md +2 -15
- package/docs/layout.md +33 -0
- package/docs/local-data-apps.md +1 -1
- package/docs/react-embed.md +2 -2
- package/docs/react.md +20 -254
- package/docs/stories-and-export.md +52 -0
- package/docs/styling.md +55 -0
- package/docs/ui-quality.md +20 -0
- package/docs/ui.md +41 -0
- package/docs/variables.md +98 -0
- package/docs/views.md +127 -0
- package/docs/widgets.md +142 -0
- package/examples/starter-data-app/index.tsx +89 -44
- package/package.json +13 -4
- package/dist/chunks/contract-cxr9t12b.js +0 -18
- package/dist/chunks/contract-cxr9t12b.js.map +0 -10
- package/dist/chunks/contract-mwe7gnmh.js.map +0 -13
- package/dist/chunks/contract-txjy0en2.js.map +0 -10
- package/dist/chunks/contract-xtxza1fy.js.map +0 -12
- package/dist/chunks/contract-yxbjea23.js.map +0 -12
- package/dist/chunks/contract-zr3jd7mr.js.map +0 -12
- package/dist/types/react/ui/RefreshRegion.d.ts +0 -9
- package/dist/types/react/ui/SelectableBarChart.d.ts +0 -15
- package/docs/releasing.md +0 -79
- /package/dist/types/react/ui/{comparison.d.ts → metric-comparison.d.ts} +0 -0
package/docs/ui.md
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# Direct UI composition
|
|
2
|
+
|
|
3
|
+
Import from `@altertable/data-app/react/ui` for setup/static screens, custom
|
|
4
|
+
controls, or a deliberately custom shell. Standard data apps use declared views
|
|
5
|
+
and bound widgets from `/react`.
|
|
6
|
+
|
|
7
|
+
`<DataApp>` here renders a static screen and accepts optional direct CSV data.
|
|
8
|
+
Direct widget forms accept local values or rows. `<DataWidget>` supplies a generic
|
|
9
|
+
frame for deliberately custom content. Use `<GettingStarted>` for
|
|
10
|
+
connection setup. Fetched data belongs to a declared view; avoid inventing
|
|
11
|
+
request states to populate a shell.
|
|
12
|
+
|
|
13
|
+
Direct `<DateRangePicker>`, `<DimensionPicker>`, and `useAppVariables()` support
|
|
14
|
+
custom control ownership. The caller supplies values and change handlers.
|
|
15
|
+
Custom tables use `<DataTable>` and its cell helpers; controls and overlays
|
|
16
|
+
include `<SearchField>`, `<Combobox>`, `<Tabs>`, `<Sheet>`, and `<HelpPopover>`.
|
|
17
|
+
|
|
18
|
+
Standalone `<AboutData>`, glossary components, and `<PresentStory>` support
|
|
19
|
+
custom inspection and presentation. Supply registered context and evidence, and
|
|
20
|
+
derive findings from the displayed data. Standard widgets and `<DataApp>`
|
|
21
|
+
already own these experiences.
|
|
22
|
+
|
|
23
|
+
For framework mounting, `<DataAppProvider>` supplies shared requests and one inspection sheet. Wrap custom shells in it.
|
|
24
|
+
Call `injectDataAppStyles()` before mounting either entry; imports do not install
|
|
25
|
+
styles.
|
|
26
|
+
|
|
27
|
+
## Charts
|
|
28
|
+
|
|
29
|
+
Choose a chart using the [visualization guide](widgets.md#choose-a-visualization).
|
|
30
|
+
Import `<BarChart>`, `<LineChart>`, `<AreaChart>`, `<PieChart>`, or `<ScatterChart>` from
|
|
31
|
+
`/react/ui`. Compose the visual inside `<VisualizationWidget dataset={dataset} source={result}>`
|
|
32
|
+
so rows, loading state, evidence, and inspection come from that dataset.
|
|
33
|
+
Pass ordered items with unique, nonblank IDs and finite numbers. Bar and pie
|
|
34
|
+
values must be nonnegative. Use `formatValue` for domain formatting.
|
|
35
|
+
|
|
36
|
+
Line and area charts show equally spaced samples; include missing periods in the
|
|
37
|
+
input. These charts space items evenly, so they do not represent irregular time
|
|
38
|
+
intervals. Distinguish a missing observation from measured zero when preparing
|
|
39
|
+
the samples. Pie slices represent mutually exclusive parts of one total; shares
|
|
40
|
+
use the sum of supplied items, so include Other when showing a subset of the
|
|
41
|
+
whole. Scatter points represent independent X/Y observations.
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
# Variables and filters
|
|
2
|
+
|
|
3
|
+
## Time views and field filters
|
|
4
|
+
|
|
5
|
+
`createDataHooks(client).defineTimeView()` owns the `period` variable, calendar
|
|
6
|
+
controls, and displayed-period label. Declare `time: { contract, defaultValue }`,
|
|
7
|
+
an operation, `isEmpty` (a predicate), and `emptyFallback` (a title and optional
|
|
8
|
+
description for an empty result). With no additional variables, its default
|
|
9
|
+
input is the calendar request. With additional variables, it is `{ period, ...variables }`.
|
|
10
|
+
Supply an `input` mapper for a different operation shape and `bindings` to extract
|
|
11
|
+
nested period or field-filter inputs. Mappings must preserve the selected values.
|
|
12
|
+
|
|
13
|
+
`dimensionFilter()` from `/contract` requires exactly one option source: fixed `options` or a `facet`.
|
|
14
|
+
Use `defineFacetFilter()` to bind a facet operation and its typed input. `<DimensionPicker>` offers missing values separately and keeps selected values
|
|
15
|
+
available when they have zero matches.
|
|
16
|
+
|
|
17
|
+
## Declare controls once
|
|
18
|
+
|
|
19
|
+
Use `textVariable()`, `selectVariable()`, and `dateRangeVariable()` in a view's
|
|
20
|
+
`variables`. `<DataApp view={view}>` generates their controls and keeps values in the URL.
|
|
21
|
+
Keep local search out of the operation input when it only filters loaded rows.
|
|
22
|
+
|
|
23
|
+
The [date range contract](contract.md#shared-date-ranges) owns source coverage,
|
|
24
|
+
maximum range, time zone, and comparison rules. `defineTimeView()` is the compact
|
|
25
|
+
path for a view whose primary input is a period. Use `defineDataView()` for fixed
|
|
26
|
+
snapshots or other input shapes.
|
|
27
|
+
|
|
28
|
+
```ts
|
|
29
|
+
const activity = defineTimeView({
|
|
30
|
+
dataContext,
|
|
31
|
+
operation: 'activity',
|
|
32
|
+
time: { contract: calendar, defaultValue: { kind: 'preset', id: 'last-7' } },
|
|
33
|
+
variables: { region: textVariable({ key: 'region' }) },
|
|
34
|
+
input: values => ({
|
|
35
|
+
filters: { period: values.period, region: values.region },
|
|
36
|
+
}),
|
|
37
|
+
bindings: {
|
|
38
|
+
period: input => input.filters.period,
|
|
39
|
+
region: input => input.filters.region,
|
|
40
|
+
},
|
|
41
|
+
isEmpty: data => data.rows.length === 0,
|
|
42
|
+
emptyFallback: { title: 'No activity' },
|
|
43
|
+
});
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
The operation, calendar, and row shape are app-owned. Bindings must extract
|
|
47
|
+
selected values unchanged.
|
|
48
|
+
|
|
49
|
+
## URL state and custom controls
|
|
50
|
+
|
|
51
|
+
Relative date presets remain relative. Explicit dates use `start`, `end`, and
|
|
52
|
+
`compare` for the `period` variable; other date variables derive these keys from
|
|
53
|
+
their own key. Invalid URL values fall back to validated defaults. Do not use
|
|
54
|
+
reserved inspection, presentation, or navigation keys for app variables.
|
|
55
|
+
|
|
56
|
+
Choose push history for meaningful selections and replace history for typing.
|
|
57
|
+
Back/Forward restores the same values and controls. Use generated controls for
|
|
58
|
+
standard data apps. Direct pickers and standalone URL state belong to
|
|
59
|
+
[direct UI composition](ui.md).
|
|
60
|
+
|
|
61
|
+
## Fixed options and query-backed facets
|
|
62
|
+
|
|
63
|
+
Declare fixed choices with `dimensionFilter()` from `/contract`:
|
|
64
|
+
|
|
65
|
+
```ts
|
|
66
|
+
const region = dimensionFilter({
|
|
67
|
+
key: 'region',
|
|
68
|
+
label: 'Region',
|
|
69
|
+
valueType: 'string',
|
|
70
|
+
selection: 'multiple',
|
|
71
|
+
options: [{ value: 'Europe', label: 'Europe' }],
|
|
72
|
+
});
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
For query-backed choices use the hooks factory's `defineFacetFilter()`. The
|
|
76
|
+
facet operation returns parsed dimension options. Its input mapper reads current
|
|
77
|
+
resolved variables, including other filters:
|
|
78
|
+
|
|
79
|
+
```ts
|
|
80
|
+
const { defineFacetFilter, defineTimeView } = createDataHooks(client);
|
|
81
|
+
const region = defineFacetFilter({
|
|
82
|
+
key: 'region',
|
|
83
|
+
label: 'Region',
|
|
84
|
+
valueType: 'string',
|
|
85
|
+
selection: 'multiple',
|
|
86
|
+
facet: {
|
|
87
|
+
operation: 'regions',
|
|
88
|
+
input: values => ({ period: values.period as DateRangeRequest }),
|
|
89
|
+
},
|
|
90
|
+
});
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
The client registry defines the `regions` operation and its typed period input;
|
|
94
|
+
import `DateRangeRequest` from `/contract`. Put `region` in the view's `variables`
|
|
95
|
+
alongside its period. Use `parseFacetOptions()` to validate facet results, and
|
|
96
|
+
`dimensionPredicate()` to build a bounded SQL filter from parsed selections.
|
|
97
|
+
For the operation's input parser, use `parseDimensionSelection(value, region)` from `/contract`.
|
|
98
|
+
A dimension without an explicit selection represents all members.
|
package/docs/views.md
ADDED
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
# Views and displayed results
|
|
2
|
+
|
|
3
|
+
## Bind a view
|
|
4
|
+
|
|
5
|
+
```tsx
|
|
6
|
+
import { createDataClient } from '@altertable/data-app/client';
|
|
7
|
+
import {
|
|
8
|
+
createDataHooks,
|
|
9
|
+
DataApp,
|
|
10
|
+
DataSection,
|
|
11
|
+
} from '@altertable/data-app/react';
|
|
12
|
+
import type { operations } from '#app/operations.ts';
|
|
13
|
+
|
|
14
|
+
const client = createDataClient<typeof operations>();
|
|
15
|
+
const { defineDataView } = createDataHooks(client);
|
|
16
|
+
const activityView = defineDataView({
|
|
17
|
+
dataContext,
|
|
18
|
+
operation: 'activity',
|
|
19
|
+
describeInput: () => 'all activity',
|
|
20
|
+
isEmpty: data => data.rows.length === 0,
|
|
21
|
+
emptyFallback: { title: 'No activity' },
|
|
22
|
+
});
|
|
23
|
+
const content = activityView.content(result => (
|
|
24
|
+
<ActivityWidgets source={result} />
|
|
25
|
+
));
|
|
26
|
+
|
|
27
|
+
function App() {
|
|
28
|
+
return (
|
|
29
|
+
<DataApp
|
|
30
|
+
config={config}
|
|
31
|
+
view={activityView}
|
|
32
|
+
story={story}
|
|
33
|
+
datasets={[activityDataset]}
|
|
34
|
+
>
|
|
35
|
+
<DataSection content={content} />
|
|
36
|
+
</DataApp>
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Omit `variables` when there are no controls. Omit `input` when the resolved
|
|
42
|
+
variables match the operation input; nested or different inputs need a mapper.
|
|
43
|
+
The operation and `ActivityWidgets` are app-owned. The bound widgets derive
|
|
44
|
+
loading content from their source; see [widgets](widgets.md) and the
|
|
45
|
+
[complete starter](../examples/starter-data-app/index.tsx). Define
|
|
46
|
+
[filters](variables.md), [context and evidence](data-context.md), and
|
|
47
|
+
[story and datasets](stories-and-export.md) for the app's question.
|
|
48
|
+
|
|
49
|
+
Render static text immediately; skeletonize only dynamic content. Keep section
|
|
50
|
+
introductions outside request boundaries so they remain visible on empty and
|
|
51
|
+
error states.
|
|
52
|
+
|
|
53
|
+
Use `<DataSection>` for each independently fetched subtree and `view.content()`
|
|
54
|
+
to share its loading and ready layout. Refreshes retain displayed content.
|
|
55
|
+
|
|
56
|
+
Use `<DataValue>` for a dynamic value within static prose:
|
|
57
|
+
|
|
58
|
+
```tsx
|
|
59
|
+
<p>
|
|
60
|
+
Orders in the last 7 days: <DataValue metric={weeklyOrders} source={result} />
|
|
61
|
+
</p>
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
Bind the whole sentence with `<TextWidget>` when its wording depends on the result.
|
|
65
|
+
Use `<DataValue scope={result.scope} />` for the displayed scope label.
|
|
66
|
+
|
|
67
|
+
Use the same [date range contract](contract.md#shared-date-ranges) for the
|
|
68
|
+
operation and its view. For nested inputs, bind the range with
|
|
69
|
+
`input: input => input.period`.
|
|
70
|
+
|
|
71
|
+
Use `result.scope` inside `view.content()` as a reading for scope text. In stories and exports,
|
|
72
|
+
`view.scope(snapshot)` returns the same label from the displayed input, using
|
|
73
|
+
`describeInput` or the view's date label.
|
|
74
|
+
|
|
75
|
+
Declare standard controls in the view's [variables](variables.md). Keep
|
|
76
|
+
local-only filters out of the operation's `input`.
|
|
77
|
+
|
|
78
|
+
For large tables, use query-backed pagination with a stable sort and total
|
|
79
|
+
count. Client pagination and search cover only the rows already returned.
|
|
80
|
+
|
|
81
|
+
Declare evidence references on `view.dataset()` for charts and tables. `<MetricWidget>` uses
|
|
82
|
+
its metric definition for the label, format, and evidence; keep view-specific
|
|
83
|
+
descriptions on the widget.
|
|
84
|
+
|
|
85
|
+
`<MetricWidget>` and `<Comparison>` share a metric reading. Comparisons
|
|
86
|
+
follow the displayed result's range.
|
|
87
|
+
|
|
88
|
+
Use the [format helpers](formatting-and-appearance.md) for metric formats and values in tables,
|
|
89
|
+
charts, and custom views.
|
|
90
|
+
|
|
91
|
+
## Preserve displayed results
|
|
92
|
+
|
|
93
|
+
The callback in `view.content()` receives a displayed source with `loading`,
|
|
94
|
+
`data`, `input`, and `scope`. Bind widgets to that source. Its input remains the
|
|
95
|
+
one that produced the visible data during refreshes and failures.
|
|
96
|
+
|
|
97
|
+
Keep the client stable across renders; create it outside the component.
|
|
98
|
+
|
|
99
|
+
## Request ownership
|
|
100
|
+
|
|
101
|
+
`<DataApp>` owns the toolbar, controls, inspection, and page feedback for its
|
|
102
|
+
primary `view`. It always renders children, so introductions and static
|
|
103
|
+
context remain visible while a section loads. It requires both
|
|
104
|
+
[story and CSV export](stories-and-export.md) for a data request.
|
|
105
|
+
|
|
106
|
+
Place `<DataSection content={content}>` around each independently fetched subtree.
|
|
107
|
+
Declare its shared loading and ready layout with `view.content()`. The section
|
|
108
|
+
inherits empty copy from its view, with an optional local override, and handles
|
|
109
|
+
initial errors and retries. Primary content shares the app's displayed result;
|
|
110
|
+
independent content uses its own context and executed queries.
|
|
111
|
+
|
|
112
|
+
Keep related datasets in one view so export and story share a coherent snapshot.
|
|
113
|
+
|
|
114
|
+
| State | Display |
|
|
115
|
+
| --------------- | --------------------------------------------------------- |
|
|
116
|
+
| Initial loading | The authored loading fallback; selectors do not run |
|
|
117
|
+
| Initial error | A source-aware error with retry when recovery is possible |
|
|
118
|
+
| Empty result | The authored empty fallback |
|
|
119
|
+
| Ready | Data and the input that produced it |
|
|
120
|
+
| Updating | The prior result and its original input, fully readable |
|
|
121
|
+
| Failed refresh | The prior result and its evidence, with recovery feedback |
|
|
122
|
+
|
|
123
|
+
`isEmpty` is app-owned. A measured zero is valid data unless the analysis explicitly
|
|
124
|
+
says otherwise. Use the displayed input in content, export, and story callbacks for scope labels.
|
|
125
|
+
|
|
126
|
+
`<DataApp>` owns request execution, refresh, and cancellation. Keep raw request
|
|
127
|
+
state out of app code.
|
package/docs/widgets.md
ADDED
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
# Widgets and narrative
|
|
2
|
+
|
|
3
|
+
## Choose a widget
|
|
4
|
+
|
|
5
|
+
Metrics, tables, and visualizations from `/react` consume view bindings and a source. For static displays, use
|
|
6
|
+
[direct UI composition](ui.md).
|
|
7
|
+
Manage requests in the [view and section](views.md); show skeletons for loading
|
|
8
|
+
readings without inventing values. Use `isEmpty` for the predicate and
|
|
9
|
+
`emptyFallback` for empty-result copy.
|
|
10
|
+
|
|
11
|
+
| Need | Component |
|
|
12
|
+
| ------------------------------------------------ | ----------------------- |
|
|
13
|
+
| Metric with formatting, comparison, and evidence | `<MetricWidget>` |
|
|
14
|
+
| Custom chart or alternate chart views | `<VisualizationWidget>` |
|
|
15
|
+
| Defined columns, local search, and pagination | `<TableWidget>` |
|
|
16
|
+
| Narrative panel | `<TextWidget>` |
|
|
17
|
+
| Borderless explanatory prose | `<TextContent>` |
|
|
18
|
+
| One data-dependent phrase within static prose | `<DataValue>` |
|
|
19
|
+
|
|
20
|
+
Use [layout](layout.md) for `<Stack>`, `<Grid>`, and responsive `<GridItem>` spans.
|
|
21
|
+
`<Skeleton>` supports custom fallback content; `/react/ui` supplies
|
|
22
|
+
`<DataAppSkeleton>` for iframe startup.
|
|
23
|
+
|
|
24
|
+
## Choose a visualization
|
|
25
|
+
|
|
26
|
+
Choose the component by the reader's question. Compose dataset visuals inside
|
|
27
|
+
`<VisualizationWidget>`; use `<MetricWidget>` for a headline value. Built-in
|
|
28
|
+
charts come from `/react/ui`; `<Ranking>`, `<Breakdown>`, and `<Comparison>`
|
|
29
|
+
are also available from `/react`.
|
|
30
|
+
|
|
31
|
+
| Question or need | Component | Guidance |
|
|
32
|
+
| --------------------------------------------------------- | ---------------- | ------------------------------------------------------------------------------------------------------------------ |
|
|
33
|
+
| How do categories compare? | `<BarChart>` | Compare nonnegative category values; order by value for rank or by a meaningful category order. |
|
|
34
|
+
| Which items lead? | `<Ranking>` | Use a compact ordered list with values and optional detail. Tracks scale to the largest visible item, not a total. |
|
|
35
|
+
| How does a measure change over time? | `<LineChart>` | Use ordered, equally spaced samples to emphasize the trend. |
|
|
36
|
+
| How large is the measure over time? | `<AreaChart>` | Use the same samples as a line chart when filled magnitude relative to zero helps answer the question. |
|
|
37
|
+
| What makes up the whole? | `<PieChart>` | Use a few mutually exclusive parts of one total; include the remainder as Other. |
|
|
38
|
+
| What share of an observed total does each part represent? | `<Breakdown>` | Use a compact display of values and shares with an explicit total, including when only some parts are shown. |
|
|
39
|
+
| How are two numeric measures related? | `<ScatterChart>` | Use independent X/Y observations to explore relationships, clusters, and outliers. |
|
|
40
|
+
| How did one metric change between periods? | `<Comparison>` | Use the metric reading and its displayed comparison period. |
|
|
41
|
+
| What are the exact values or row details? | `<TableWidget>` | Use a bound table for lookup, search, and precise comparisons. |
|
|
42
|
+
|
|
43
|
+
Prefer bars or a ranking when readers need to compare similarly sized categories;
|
|
44
|
+
use a pie for a simple composition question. Prefer a line when the trend is
|
|
45
|
+
enough; use area when the fill adds meaning. Keep exact values available in a
|
|
46
|
+
table when they matter. Follow the [chart input constraints](ui.md#charts).
|
|
47
|
+
|
|
48
|
+
Use widget `views` when a chart and another representation answer the same
|
|
49
|
+
question from the same dataset.
|
|
50
|
+
|
|
51
|
+
## Declare datasets and metrics
|
|
52
|
+
|
|
53
|
+
Bind reusable selections to the view. Declare evidence references directly;
|
|
54
|
+
the view validates them against its own context. A dataset's columns supply both formatted
|
|
55
|
+
table cells and raw CSV values. Widgets derive their readings, evidence, and empty fallback from the binding.
|
|
56
|
+
|
|
57
|
+
```tsx
|
|
58
|
+
const countries = activityView.dataset({
|
|
59
|
+
name: 'Countries',
|
|
60
|
+
select: data => data.countries,
|
|
61
|
+
rowKey: row => row.country,
|
|
62
|
+
evidence: { id: 'countries', glossaryIds: ['identities'] },
|
|
63
|
+
columns: {
|
|
64
|
+
country: { value: row => row.country },
|
|
65
|
+
count: { value: row => row.count, format: { kind: 'count' } },
|
|
66
|
+
},
|
|
67
|
+
});
|
|
68
|
+
const total = activityView.metric(
|
|
69
|
+
{
|
|
70
|
+
id: 'tracked-identities',
|
|
71
|
+
glossaryId: 'identities',
|
|
72
|
+
format: { kind: 'count' },
|
|
73
|
+
},
|
|
74
|
+
data => ({
|
|
75
|
+
current: data.count,
|
|
76
|
+
previous: data.previousCount,
|
|
77
|
+
})
|
|
78
|
+
);
|
|
79
|
+
const content = activityView.content(result => (
|
|
80
|
+
<Grid columns={2}>
|
|
81
|
+
<MetricWidget metric={total} source={result} />
|
|
82
|
+
<TableWidget dataset={countries} source={result} />
|
|
83
|
+
<VisualizationWidget dataset={countries} source={result}>
|
|
84
|
+
{rows => <CountryChart rows={rows} />}
|
|
85
|
+
</VisualizationWidget>
|
|
86
|
+
</Grid>
|
|
87
|
+
));
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
Column keys supply unique IDs and readable default labels. Use `label` for a
|
|
91
|
+
specific display/export header. Declare selectors, raw value accessors, and
|
|
92
|
+
row-key callbacks explicitly; row keys must be stable and unique.
|
|
93
|
+
Metric comparisons require a
|
|
94
|
+
view date binding. `read()` returns a loading-aware value for custom prose.
|
|
95
|
+
Pass the binding and `source={snapshot}` to reuse a widget in a story. Use the
|
|
96
|
+
source supplied by that binding's own view; reconstructed or foreign sources
|
|
97
|
+
are rejected before selection.
|
|
98
|
+
Selectors do not run during loading. Table search, pagination, descriptions, and
|
|
99
|
+
actions remain local choices. For custom cells, use `format: row => ...`; CSV
|
|
100
|
+
still uses the raw `value` accessor. Datasets default to “No results”; supply
|
|
101
|
+
`emptyFallback` for specific copy. See [export](stories-and-export.md).
|
|
102
|
+
|
|
103
|
+
## Narrative text
|
|
104
|
+
|
|
105
|
+
Use `<TextContent>` for prose within a page or custom layout, and `<TextWidget>`
|
|
106
|
+
when the explanation belongs in a titled panel alongside other widgets.
|
|
107
|
+
|
|
108
|
+
Give text a purpose: frame the question, explain how to interpret a comparison,
|
|
109
|
+
qualify a finding, or suggest what to explore next. Choose the content for the
|
|
110
|
+
reader's question.
|
|
111
|
+
|
|
112
|
+
For data-dependent text, pass `metric` or `dataset` and `source` to `<TextWidget>`.
|
|
113
|
+
It derives its title and evidence. A metric formats its current value by default;
|
|
114
|
+
use a child renderer for authored prose. Dataset renderers receive displayed rows,
|
|
115
|
+
including an empty selection, so they can explain zero activity.
|
|
116
|
+
|
|
117
|
+
Use `<DataValue metric={total} source={result} />` for a formatted value inside
|
|
118
|
+
static prose, or pass a dataset and row renderer. Its default loading fallback is
|
|
119
|
+
an inline skeleton. Use `<DataValue scope={result.scope} />` for a scope label.
|
|
120
|
+
All selectors use the displayed source; loading selectors do not run.
|
|
121
|
+
Use `<TextContent>` for static instructions; local filters should feed the same
|
|
122
|
+
filtered data to the text and its related visualization.
|
|
123
|
+
|
|
124
|
+
## Custom visuals and controls
|
|
125
|
+
|
|
126
|
+
`<Ranking>`, `<Breakdown>`, and `<Comparison>`
|
|
127
|
+
compose inside authored widgets. Use [built-in charts](ui.md#charts) from `/react/ui`
|
|
128
|
+
inside `<VisualizationWidget dataset={dataset} source={result}>` for displayed rows.
|
|
129
|
+
The dataset owns evidence and empty copy; an empty row selection shows that fallback. A visualization's `views` declaration keeps
|
|
130
|
+
alternate-view selection shared with its inspection sheet. Give views stable IDs
|
|
131
|
+
and keep interactive state above the widget, since inspection may render it again.
|
|
132
|
+
|
|
133
|
+
`<TableWidget>` searches and paginates only the supplied rows. Use query-backed
|
|
134
|
+
pagination with a stable sort and total count for larger datasets. The `limit`
|
|
135
|
+
mode shows a bounded preview without local pagination. Custom tables can use
|
|
136
|
+
`<DataTable>`, `<DataTableEmptyRow>`, `<DataTableTimestamp>`, and `<DataTableShare>`.
|
|
137
|
+
|
|
138
|
+
For custom controls, tables, and overlays, see [direct UI composition](ui.md).
|
|
139
|
+
For embedded theme and chrome, see [parent presentation](embed.md#parent-presentation).
|
|
140
|
+
|
|
141
|
+
`<TooltipProvider>` shares hover timing outside `<DataApp>`; the app already
|
|
142
|
+
provides it.
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { formatMetric } from '@altertable/data-app/format';
|
|
1
2
|
import { createDataClient } from '@altertable/data-app/client';
|
|
2
3
|
import type { DataAppConfig } from '@altertable/data-app/config';
|
|
3
4
|
import {
|
|
@@ -9,6 +10,12 @@ import {
|
|
|
9
10
|
createDataContext,
|
|
10
11
|
createDataHooks,
|
|
11
12
|
DataApp,
|
|
13
|
+
DataSection,
|
|
14
|
+
DataValue,
|
|
15
|
+
TableWidget,
|
|
16
|
+
Grid,
|
|
17
|
+
Stack,
|
|
18
|
+
TextContent,
|
|
12
19
|
injectDataAppStyles,
|
|
13
20
|
mountDataApp,
|
|
14
21
|
MetricWidget,
|
|
@@ -90,68 +97,106 @@ const sampleDataContext = createDataContext(queryNames)({
|
|
|
90
97
|
},
|
|
91
98
|
},
|
|
92
99
|
});
|
|
93
|
-
const { defineDataView
|
|
94
|
-
createDataClient({ operations })
|
|
95
|
-
);
|
|
100
|
+
const { defineDataView } = createDataHooks(createDataClient({ operations }));
|
|
96
101
|
const sampleCountsView = defineDataView({
|
|
102
|
+
dataContext: sampleDataContext,
|
|
97
103
|
operation: 'sampleCountsByGroup',
|
|
98
104
|
variables: {
|
|
99
105
|
groupName: textVariable({ key: 'group', label: 'Group', defaultValue: '' }),
|
|
100
106
|
},
|
|
101
|
-
input: ({ groupName }) => ({ groupName }),
|
|
102
107
|
describeInput: ({ groupName }) =>
|
|
103
108
|
groupName ? `group ${groupName}` : 'all groups',
|
|
104
109
|
isEmpty: sampleCounts => sampleCounts.length === 0,
|
|
105
|
-
|
|
110
|
+
emptyFallback: {
|
|
106
111
|
title: 'No matching groups',
|
|
107
112
|
description: 'Try Alpha, Beta, or clear the group filter.',
|
|
108
113
|
},
|
|
109
114
|
});
|
|
115
|
+
const sampleCounts = sampleCountsView.dataset({
|
|
116
|
+
name: 'Sample counts',
|
|
117
|
+
select: rows => rows,
|
|
118
|
+
rowKey: row => row.groupName,
|
|
119
|
+
columns: {
|
|
120
|
+
groupName: { label: 'Group', value: row => row.groupName },
|
|
121
|
+
sampleCount: { value: row => row.sampleCount, format: { kind: 'count' } },
|
|
122
|
+
},
|
|
123
|
+
evidence: {
|
|
124
|
+
id: 'counts-by-group',
|
|
125
|
+
glossaryIds: ['sampleCount'],
|
|
126
|
+
},
|
|
127
|
+
});
|
|
128
|
+
const totalSamples = sampleCountsView.metric(
|
|
129
|
+
{
|
|
130
|
+
id: 'total-samples',
|
|
131
|
+
glossaryId: 'sampleCount',
|
|
132
|
+
label: 'Total samples',
|
|
133
|
+
format: { kind: 'count' },
|
|
134
|
+
},
|
|
135
|
+
rows => ({ current: rows.reduce((sum, row) => sum + row.sampleCount, 0) })
|
|
136
|
+
);
|
|
137
|
+
const sampleContent = sampleCountsView.content(result => (
|
|
138
|
+
<Stack aria-label="Sample results">
|
|
139
|
+
<TextContent>
|
|
140
|
+
<p>
|
|
141
|
+
Showing <DataValue scope={result.scope} />
|
|
142
|
+
</p>
|
|
143
|
+
</TextContent>
|
|
144
|
+
<Grid columns={2}>
|
|
145
|
+
<MetricWidget
|
|
146
|
+
metric={totalSamples}
|
|
147
|
+
source={result}
|
|
148
|
+
description="Sum of the fixture counts in the selected groups."
|
|
149
|
+
/>
|
|
150
|
+
<TableWidget
|
|
151
|
+
dataset={sampleCounts}
|
|
152
|
+
source={result}
|
|
153
|
+
title="Counts by group"
|
|
154
|
+
description="Alpha and Beta demonstrate measured values, including zero."
|
|
155
|
+
pagination={false}
|
|
156
|
+
/>
|
|
157
|
+
</Grid>
|
|
158
|
+
</Stack>
|
|
159
|
+
));
|
|
110
160
|
function App() {
|
|
111
|
-
const sampleCountsRequest = useView(sampleCountsView);
|
|
112
161
|
return (
|
|
113
162
|
<DataApp
|
|
114
163
|
config={appConfig}
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
story={
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
164
|
+
view={sampleCountsView}
|
|
165
|
+
datasets={[sampleCounts]}
|
|
166
|
+
story={snapshot => {
|
|
167
|
+
const total = totalSamples.read(snapshot);
|
|
168
|
+
const scope = sampleCountsView.scope(snapshot);
|
|
169
|
+
return [
|
|
170
|
+
{
|
|
171
|
+
id: 'total-samples',
|
|
172
|
+
headline: `Total samples: ${formatMetric(total.value.current, totalSamples.definition.format)}`,
|
|
173
|
+
context: `Demonstration values for ${scope}.`,
|
|
174
|
+
visual: <MetricWidget metric={totalSamples} source={snapshot} />,
|
|
175
|
+
visualKind: 'metric',
|
|
176
|
+
evidence: totalSamples,
|
|
177
|
+
},
|
|
178
|
+
{
|
|
179
|
+
id: 'counts-by-group',
|
|
180
|
+
headline: 'Counts by group',
|
|
181
|
+
context: `Demonstration values for ${scope}.`,
|
|
182
|
+
visual: (
|
|
183
|
+
<TableWidget
|
|
184
|
+
dataset={sampleCounts}
|
|
185
|
+
source={snapshot}
|
|
186
|
+
pagination={false}
|
|
187
|
+
/>
|
|
188
|
+
),
|
|
189
|
+
evidence: sampleCounts,
|
|
190
|
+
},
|
|
191
|
+
];
|
|
192
|
+
}}
|
|
141
193
|
>
|
|
142
|
-
|
|
143
|
-
<
|
|
194
|
+
<Stack>
|
|
195
|
+
<TextContent>
|
|
144
196
|
<h2>Sample counts</h2>
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
<li key={groupName}>
|
|
149
|
-
{groupName}: {sampleCount}
|
|
150
|
-
</li>
|
|
151
|
-
))}
|
|
152
|
-
</ul>
|
|
153
|
-
</section>
|
|
154
|
-
)}
|
|
197
|
+
</TextContent>
|
|
198
|
+
<DataSection content={sampleContent} />
|
|
199
|
+
</Stack>
|
|
155
200
|
</DataApp>
|
|
156
201
|
);
|
|
157
202
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@altertable/data-app",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.66.0",
|
|
4
4
|
"description": "Contracts, transport, and React UI for Altertable data apps",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"altertable",
|
|
@@ -78,6 +78,10 @@
|
|
|
78
78
|
"./react/embed": {
|
|
79
79
|
"types": "./dist/types/react/embed/index.d.ts",
|
|
80
80
|
"import": "./dist/react/embed/index.js"
|
|
81
|
+
},
|
|
82
|
+
"./react/ui": {
|
|
83
|
+
"types": "./dist/types/react/ui/index.d.ts",
|
|
84
|
+
"import": "./dist/react/ui/index.js"
|
|
81
85
|
}
|
|
82
86
|
},
|
|
83
87
|
"publishConfig": {
|
|
@@ -86,12 +90,13 @@
|
|
|
86
90
|
},
|
|
87
91
|
"scripts": {
|
|
88
92
|
"build": "bun run scripts/build.ts && tsc -p tsconfig.build.json && bun run scripts/build-declarations.ts",
|
|
93
|
+
"dev": "bun run scripts/dev.ts",
|
|
89
94
|
"typecheck": "bun install --frozen-lockfile && tsc --noEmit",
|
|
90
|
-
"lint": "oxlint --type-aware src tests scripts browser-tests examples/starter-data-app",
|
|
95
|
+
"lint": "oxlint --type-aware src tests scripts browser-tests dev examples/starter-data-app",
|
|
91
96
|
"format": "oxfmt .",
|
|
92
97
|
"format:check": "oxfmt --check .",
|
|
93
98
|
"test": "bun test ./tests",
|
|
94
|
-
"check": "bun run build && bun run typecheck && bun run lint && bun run lint:md && bun run check:links && bun run format:check && bun run test && bun run test:package && bun run test:starter",
|
|
99
|
+
"check": "bun run build && bun run typecheck && bun run lint && bun run check:styles && bun run lint:md && bun run check:links && bun run format:check && bun run test && bun run test:package && bun run test:starter",
|
|
95
100
|
"test:package": "bun run scripts/check-package.ts",
|
|
96
101
|
"check:workflows": "bash scripts/check-workflows.sh",
|
|
97
102
|
"test:browser": "playwright test --config browser-tests/playwright.config.ts",
|
|
@@ -99,13 +104,16 @@
|
|
|
99
104
|
"test:starter": "bun run scripts/check-starter.ts",
|
|
100
105
|
"lint:md": "markdownlint-cli2",
|
|
101
106
|
"check:links": "remark . --ext md --rc-path .remarkrc.json --frail --no-stdout",
|
|
102
|
-
"check:links:external": "remark . --ext md --rc-path .remarkrc-external.json --frail --no-stdout"
|
|
107
|
+
"check:links:external": "remark . --ext md --rc-path .remarkrc-external.json --frail --no-stdout",
|
|
108
|
+
"check:styles": "bun run scripts/check-app-styles.ts"
|
|
103
109
|
},
|
|
104
110
|
"dependencies": {
|
|
105
111
|
"@floating-ui/react": "0.27.20",
|
|
106
112
|
"@internationalized/date": "3.12.4",
|
|
107
113
|
"@tanstack/react-query": "5.104.0",
|
|
114
|
+
"fflate": "0.8.3",
|
|
108
115
|
"fuzzysort": "4.0.2",
|
|
116
|
+
"html-to-image": "1.11.13",
|
|
109
117
|
"lucide-react": "1.48.0",
|
|
110
118
|
"react-aria-components": "1.21.1"
|
|
111
119
|
},
|
|
@@ -124,6 +132,7 @@
|
|
|
124
132
|
"remark-gfm": "4.0.1",
|
|
125
133
|
"remark-lint-no-dead-urls": "2.0.1",
|
|
126
134
|
"remark-validate-links": "13.1.0",
|
|
135
|
+
"testcontainers": "12.2.0",
|
|
127
136
|
"typescript": "7.0.2"
|
|
128
137
|
},
|
|
129
138
|
"peerDependencies": {
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
// src/core/bridge.ts
|
|
2
|
-
var BRIDGE = "altertable:data-app";
|
|
3
|
-
var PARENT_PARAM = "__altertable_parent";
|
|
4
|
-
var MAX_PENDING = 128;
|
|
5
|
-
var REQUEST_TIMEOUT_MS = 60000;
|
|
6
|
-
function isBridgeMessage(value) {
|
|
7
|
-
if (!value || typeof value !== "object")
|
|
8
|
-
return false;
|
|
9
|
-
const message = value;
|
|
10
|
-
return message.channel === BRIDGE && message.version === 1 && typeof message.type === "string" && typeof message.documentId === "string" && message.documentId.length > 0 && message.documentId.length <= 128;
|
|
11
|
-
}
|
|
12
|
-
function validId(value) {
|
|
13
|
-
return typeof value === "string" && /^[a-zA-Z0-9_-]{1,128}$/.test(value);
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
export { BRIDGE, PARENT_PARAM, MAX_PENDING, REQUEST_TIMEOUT_MS, isBridgeMessage, validId };
|
|
17
|
-
|
|
18
|
-
//# debugId=416EADDA2C8ACEA064756E2164756E21
|