@cdc/dashboard 4.26.4 → 4.26.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.
Files changed (91) hide show
  1. package/CONFIG.md +77 -30
  2. package/LICENSE +201 -0
  3. package/dist/cdcdashboard.js +49936 -49166
  4. package/examples/dashboard-conditions-filters-incomplete.json +221 -0
  5. package/examples/dashboard-missing-datasets-multi.json +174 -0
  6. package/examples/dashboard-missing-datasets-single.json +121 -0
  7. package/examples/dashboard-multi-dashboard-version-regression.json +146 -0
  8. package/examples/dashboard-shared-filter-row-delete-cleanup.json +186 -0
  9. package/examples/dashboard-stale-dataset-keys.json +181 -0
  10. package/examples/dashboard-tiered-filter-regression.json +190 -0
  11. package/examples/private/cfa-dashboard.json +651 -0
  12. package/examples/private/data-bite-wrap.json +6936 -0
  13. package/examples/private/multi-dash-fix.json +16963 -0
  14. package/examples/private/versions.json +41612 -0
  15. package/examples/us-map-filter-example.json +1074 -0
  16. package/package.json +9 -9
  17. package/src/CdcDashboard.tsx +6 -2
  18. package/src/CdcDashboardComponent.tsx +178 -87
  19. package/src/DashboardCopyPasteContext.test.tsx +33 -0
  20. package/src/DashboardCopyPasteContext.tsx +48 -0
  21. package/src/_stories/Dashboard.EditorRegression.stories.tsx +72 -0
  22. package/src/_stories/Dashboard.Regression.stories.tsx +196 -0
  23. package/src/_stories/Dashboard.Zoom.stories.tsx +88 -0
  24. package/src/_stories/Dashboard.stories.tsx +294 -0
  25. package/src/_stories/FilteredTextMigrationComparison.stories.tsx +87 -0
  26. package/src/components/Column.test.tsx +176 -0
  27. package/src/components/Column.tsx +214 -13
  28. package/src/components/DashboardConditionModal.test.tsx +420 -0
  29. package/src/components/DashboardConditionModal.tsx +367 -0
  30. package/src/components/DashboardConditionSummary.tsx +59 -0
  31. package/src/components/DashboardEditors.tsx +8 -0
  32. package/src/components/DashboardFilters/DashboardFilters.test.tsx +139 -1
  33. package/src/components/DashboardFilters/DashboardFilters.tsx +192 -174
  34. package/src/components/DashboardFilters/DashboardFiltersEditor/DashboardFiltersEditor.test.tsx +164 -0
  35. package/src/components/DashboardFilters/DashboardFiltersEditor/DashboardFiltersEditor.tsx +41 -2
  36. package/src/components/DashboardFilters/DashboardFiltersEditor/components/FilterEditor.test.tsx +180 -3
  37. package/src/components/DashboardFilters/DashboardFiltersEditor/components/FilterEditor.tsx +15 -32
  38. package/src/components/DashboardFilters/DashboardFiltersWrapper.test.tsx +142 -0
  39. package/src/components/DashboardFilters/DashboardFiltersWrapper.tsx +32 -27
  40. package/src/components/DashboardFilters/dashboardfilter.styles.css +42 -27
  41. package/src/components/DataDesignerModal.tsx +2 -1
  42. package/src/components/Grid.tsx +8 -4
  43. package/src/components/Header/Header.tsx +36 -17
  44. package/src/components/Row.test.tsx +228 -0
  45. package/src/components/Row.tsx +93 -18
  46. package/src/components/VisualizationRow.test.tsx +396 -0
  47. package/src/components/VisualizationRow.tsx +110 -35
  48. package/src/components/VisualizationsPanel/VisualizationsPanel.test.tsx +49 -0
  49. package/src/components/VisualizationsPanel/VisualizationsPanel.tsx +14 -13
  50. package/src/components/Widget/Widget.test.tsx +218 -0
  51. package/src/components/Widget/Widget.tsx +119 -17
  52. package/src/components/Widget/widget.styles.css +31 -18
  53. package/src/components/dashboard-condition-modal.css +76 -0
  54. package/src/components/dashboard-condition-summary.css +87 -0
  55. package/src/helpers/addValuesToDashboardFilters.ts +3 -5
  56. package/src/helpers/addVisualization.ts +15 -4
  57. package/src/helpers/cloneDashboardWidget.ts +127 -0
  58. package/src/helpers/dashboardColumnWidgets.ts +99 -0
  59. package/src/helpers/dashboardConditionUi.ts +47 -0
  60. package/src/helpers/dashboardConditions.ts +200 -0
  61. package/src/helpers/dashboardFilterTargets.ts +156 -0
  62. package/src/helpers/filterData.ts +4 -9
  63. package/src/helpers/filterVisibility.ts +20 -0
  64. package/src/helpers/formatConfigBeforeSave.ts +2 -2
  65. package/src/helpers/getFilteredData.ts +18 -5
  66. package/src/helpers/getUpdateConfig.ts +43 -12
  67. package/src/helpers/getVizRowColumnLocator.ts +11 -1
  68. package/src/helpers/iconHash.tsx +9 -3
  69. package/src/helpers/mapDataToConfig.ts +31 -29
  70. package/src/helpers/reloadURLHelpers.ts +25 -5
  71. package/src/helpers/removeDashboardFilter.ts +33 -33
  72. package/src/helpers/tests/addVisualization.test.ts +53 -9
  73. package/src/helpers/tests/cloneDashboardWidget.test.ts +136 -0
  74. package/src/helpers/tests/dashboardColumnWidgets.test.ts +99 -0
  75. package/src/helpers/tests/dashboardConditionUi.test.ts +41 -0
  76. package/src/helpers/tests/dashboardConditions.test.ts +428 -0
  77. package/src/helpers/tests/formatConfigBeforeSave.test.ts +51 -0
  78. package/src/helpers/tests/getFilteredData.test.ts +265 -86
  79. package/src/helpers/tests/getUpdateConfig.test.ts +338 -0
  80. package/src/helpers/tests/reloadURLHelpers.test.ts +394 -238
  81. package/src/index.tsx +6 -3
  82. package/src/scss/grid.scss +249 -20
  83. package/src/scss/main.scss +108 -29
  84. package/src/store/dashboard.actions.ts +17 -4
  85. package/src/store/dashboard.reducer.test.ts +538 -0
  86. package/src/store/dashboard.reducer.ts +135 -22
  87. package/src/test/CdcDashboard.test.tsx +148 -0
  88. package/src/test/CdcDashboardComponent.test.tsx +935 -2
  89. package/src/types/ConfigRow.ts +15 -0
  90. package/src/types/DashboardFilters.ts +4 -0
  91. package/src/types/SharedFilter.ts +1 -0
package/CONFIG.md CHANGED
@@ -30,12 +30,24 @@ Use the example in [README.md](./README.md) for the copy-pasteable minimum confi
30
30
  | Field | Type | Required | Default | Description | Allowed values / Notes |
31
31
  | --- | --- | --- | --- | --- | --- |
32
32
  | `type` | `string` | Yes | None | Identifies the package. | Must be `dashboard`. |
33
- | `version` | `string` | No | None | Saved COVE version for migration logic. | Use a semantic version string. |
33
+ | `version` | `string` | No | None | Saved COVE version for migration logic. | Use a semantic COVE version for authored configs so migrations can reason about saved-version order. |
34
+ | `locale` | `string` | No | `en-US` after migration/runtime preparation | Locale used by dashboard-level table date and number formatting and copied into child visualizations by migration. | Any valid `Intl` locale is accepted. |
34
35
  | `data` | `object[]` | No | `[]` | Legacy inline data used to seed the dashboard when named datasets are not provided. | An empty array is enough for a dashboard that only renders markup or chrome. |
35
36
  | `dataUrl` | `string` | No | None | Remote data source for legacy single-dataset dashboards. | When present, the loader fetches data at runtime. |
36
37
  | `datasets` | `Record<string, DataSet>` | No | None | Named datasets used by the dashboard and its child visualizations. | Each child viz points at a dataset with `dataKey`. |
38
+ | `datasets.*.loadQueryParam` | `string` | No | None | Browser query-string parameter appended to that dataset's `dataUrl` at load time. | Used for dataset URLs that need to be varied by the embedding page's query string. |
37
39
 
38
- Rows, datasets, and child visualizations also use the shared `ConfigureData` fields described in the core reference.
40
+ Rows, datasets, and child visualizations also use the shared [`ConfigureData`](https://github.com/CDCgov/cdc-open-viz/blob/main/packages/core/CONFIG.md#configuredata) fields described in the core reference. Named dataset entries use shared [`DataSet`](https://github.com/CDCgov/cdc-open-viz/blob/main/packages/core/CONFIG.md#dataset).
41
+
42
+ ### Legacy Single-Dataset Fields
43
+
44
+ Older dashboard configs may store single-dataset metadata at the top level instead of under `datasets`.
45
+
46
+ | Field | Type | Required | Default | Description | Allowed values / Notes |
47
+ | --- | --- | --- | --- | --- | --- |
48
+ | `dataDescription` | `Partial<DataDescription>` | No | None | Shared data-shaping metadata for legacy top-level `data` or `dataUrl`. | See shared [`DataDescription`](https://github.com/CDCgov/cdc-open-viz/blob/main/packages/core/CONFIG.md#datadescription). Prefer `datasets.*.dataDescription` in new configs. |
49
+
50
+ During load and save cleanup, current dashboard flows prefer named `datasets`. Legacy top-level single-dataset file/runtime fields such as `dataFileName`, `dataFileSourceType`, and `formattedData` may still appear in old configs, but they are listed in `Fields You Can Ignore` rather than treated as authorable dashboard fields.
39
51
 
40
52
  ## Dashboard Shell
41
53
 
@@ -44,23 +56,25 @@ Rows, datasets, and child visualizations also use the shared `ConfigureData` fie
44
56
  | `dashboard.title` | `string` | No | `''` | Title shown in the dashboard header and table anchors. | Hidden when empty. |
45
57
  | `dashboard.description` | `string` | No | `''` | Optional dashboard description rendered under the title. | Accepts HTML when the consuming app renders trusted markup. |
46
58
  | `dashboard.theme` | `string` | No | `theme-blue` | Shared theme token for the dashboard shell. | See shared theme values in `@cdc/core`. |
47
- | `dashboard.titleStyle` | `string` | No | `small` | Header size/style for the dashboard title. | `legacy`, `large`, `small` |
59
+ | `dashboard.titleStyle` | `string` | No | `small` when the full dashboard initial state is used | Header size/style for the dashboard title. | `legacy`, `large`, `small`. If a partial `dashboard` object is shallow-merged without `titleStyle`, the field may remain undefined instead of receiving the initial-state value. |
48
60
  | `dashboard.sharedFilters` | `SharedFilter[]` | No | `[]` | Dashboard-level filters that can drive multiple visualizations. | See the Shared Filters section below. |
49
- | `dashboard.filters` | `any` | No | Deprecated | Legacy filter field accepted during migration. | The loader copies these into `dashboard.sharedFilters` when needed. |
50
61
 
51
62
  ## Layout And Visualization Placement
52
63
 
53
64
  | Field | Type | Required | Default | Description | Allowed values / Notes |
54
65
  | --- | --- | --- | --- | --- | --- |
55
66
  | `rows` | `ConfigRow[]` | Yes | `[{ columns: [{ width: 12 }] }]` in practice | Layout rows that place widgets on the page. | The dashboard can render without explicit row chrome, but it needs at least one row for visible widgets. |
56
- | `rows[].columns[]` | `object` | Yes | None | Column slots inside each row. | A column is populated when `widget` points to a visualization key. |
57
- | `rows[].columns[].width` | `number \| null` | Yes | `null` on empty placeholders | Column width in the dashboard grid. | `12` is a full-width column. |
58
- | `rows[].columns[].widget` | `string` | No | None | Visualization key rendered in that slot. | Must match a key in `visualizations`. |
67
+ | `rows[].columns[]` | `object` | Yes | None | Column slots inside each row. | A column is populated when `widget` points to a visualization key, or when `conditionalWidgets` holds one or more candidate widgets. |
68
+ | `rows[].columns[].width` | `number` | Yes for visible columns | None | Column width in the dashboard grid. | `12` is a full-width column. Empty placeholder columns may omit `width` and are ignored by rendering. |
69
+ | `rows[].columns[].widget` | `string` | No | None | Visualization key rendered in that slot in simple mode. | Must match a key in `visualizations`. Ignored when `conditionalWidgets.length > 0`. |
70
+ | `rows[].columns[].toggleName` | `string` | No | Visualization type label | User-visible label for a widget in toggle rows. | Only meaningful when the parent row has `toggle: true`. When omitted, runtime falls back to labels such as `chart`, `map`, or `table`, not the child widget title. |
71
+ | `rows[].columns[].conditionalWidgets` | `ConditionalWidget[]` | No | None | Ordered candidate widgets for one column slot. | First matching entry wins. If no entry matches, the column renders empty. |
72
+ | `rows[].dashboardCondition` | `DashboardCondition` | No | None | Optional post-filter visibility rule for the entire row. | Evaluated after shared dashboard filtering; unresolved inputs hide the row. Not supported on toggle or multi-viz rows in v1. |
59
73
  | `rows[].toggle` | `boolean` | No | `false` | Turns a row into a toggle row. | Only one column is shown at a time. |
60
74
  | `rows[].equalHeight` | `boolean` | No | `false` | Forces equal-height cards within the row. | TP5 layouts may also trigger equalization automatically. |
61
75
  | `rows[].multiVizColumn` | `string` | No | None | Column used to split one visualization into multiple cards. | Used by multi-viz dashboard flows. |
62
76
  | `rows[].expandCollapseAllButtons` | `boolean` | No | `false` | Adds expand/collapse-all controls to multi-viz rows. | Only meaningful when `multiVizColumn` is set. |
63
- | `visualizations` | `Record<string, AnyVisualization>` | Yes | `{}` in practice | Child visualizations rendered by the dashboard. | Each nested config follows the child package contract for `chart`, `map`, `data-bite`, `waffle-chart`, `markup-include`, `filtered-text`, `table`, or `dashboardFilters`. |
77
+ | `visualizations` | `Record<string, AnyVisualization>` | Yes | `{}` in practice | Child visualizations rendered by the dashboard. | Each nested config follows the child package contract for `chart`, `map`, `data-bite`, `waffle-chart`, `markup-include`, `table`, or `dashboardFilters`. Legacy saved `filtered-text` configs migrate to `markup-include` in Phase 1; new authored text should use `markup-include`. |
64
78
 
65
79
  Rows also accept the shared `ConfigureData` fields from the core reference, which the editor uses when a row owns its own dataset.
66
80
 
@@ -71,16 +85,21 @@ The dashboard package includes a `dashboardFilters` visualization that renders s
71
85
  | Field | Type | Required | Default | Description | Allowed values / Notes |
72
86
  | --- | --- | --- | --- | --- | --- |
73
87
  | `visualizations.*.type` | `string` | Yes | None | Must identify the widget as a dashboard filter panel. | Must be `dashboardFilters`. |
88
+ | `visualizations.*.visualizationType` | `string` | No | Usually `dashboardFilters` | Visualization identifier used by some dashboard-filter load paths. | Keep this aligned with `type` for dashboard filter widgets. |
74
89
  | `visualizations.*.sharedFilterIndexes` | `number[]` | Yes | None | Indexes into `dashboard.sharedFilters` that this widget renders. | Order matters and matches the visual order in the panel. |
75
90
  | `visualizations.*.filterBehavior` | `FilterBehavior` | No | `Filter Change` | Controls whether selections apply immediately or wait for confirmation. | `Apply Button`, `Filter Change` |
91
+ | `visualizations.*.filterIntro` | `string` | No | `''` | Helper text shown above this dashboard filter widget's controls. | Supports the same parsed HTML treatment used by visualization filter intro text. |
76
92
  | `visualizations.*.applyFiltersButtonText` | `string` | No | `GO!` | Label for the apply button when apply-button behavior is enabled. | Only used when `filterBehavior` is `Apply Button`. |
77
- | `visualizations.*.autoLoad` | `boolean` | No | `false` | Lets the widget auto-apply URL-filter changes without waiting for the apply button. | Only relevant when the dashboard includes apply-button behavior elsewhere. |
93
+ | `visualizations.*.autoLoad` | `boolean` | No | `false` | Auto-selects and loads API-backed URL filter values for this dashboard-filter widget. | Mainly used with `Filter Change` widgets after parent-filter updates or query-parameter seeding; it is not limited to apply-button dashboards. |
78
94
  | `visualizations.*.showClearButton` | `boolean` | No | `true` | Shows the clear-filters button in apply-button mode. | Only used when `filterBehavior` is `Apply Button`. |
95
+ | `visualizations.*.visual.grayBackground` | `boolean` | No | `false` | Wraps the rendered dashboard filters in the grey callout background treatment. | Uses the shared callout styling with dashboard-filter background color `#f4f8fa`. |
79
96
 
80
97
  ## Shared Filters
81
98
 
82
99
  Dashboard filters are split between the dashboard shell and the `dashboardFilters` widget that renders them.
83
100
 
101
+ Dashboard `SharedFilter` objects are a distinct dashboard-owned contract. They reuse a few primitive ideas from core filter helpers, but they are not the same shape as core `VizFilter`. Runtime/cache state such as active selections is listed in `Fields You Can Ignore`; data-filter value lists can be authored or preserved as described below.
102
+
84
103
  ### Common Filter Fields
85
104
 
86
105
  | Field | Type | Required | Default | Description | Allowed values / Notes |
@@ -88,29 +107,27 @@ Dashboard filters are split between the dashboard shell and the `dashboardFilter
88
107
  | `dashboard.sharedFilters[].key` | `string` | Yes | None | Human-readable filter label and stable key. | Used as the display label and editor key. |
89
108
  | `dashboard.sharedFilters[].type` | `string` | No | `''` | Filter mode. | `datafilter`, `urlfilter`, or empty while configuring. |
90
109
  | `dashboard.sharedFilters[].filterStyle` | `string` | No | `dropdown` | Filter control style. | `combobox`, `dropdown`, `multi-select`, `nested-dropdown`, `tab-simple` |
91
- | `dashboard.sharedFilters[].showDropdown` | `boolean` | No | `true` | Shows the filter control in the dashboard. | Hiding the dropdown does not hide nested-dropdown or tab-simple renderers. |
92
- | `dashboard.sharedFilters[].values` | `string[]` | No | `[]` | Available filter values. | Usually populated at runtime from data or API results. URL filters commonly omit this when saved. |
93
- | `dashboard.sharedFilters[].active` | `string \| string[]` | No | None | Current filter selection. | Runtime-managed unless the config intentionally seeds a default. |
94
- | `dashboard.sharedFilters[].queuedActive` | `string \| string[]` | No | None | Temporary selection used by apply-button flows. | Only meaningful for apply-button filter behavior. |
95
- | `dashboard.sharedFilters[].order` | `string` | No | `asc` | Sort order for generated filter values. | `cust`, `desc`, `asc`, `column` |
110
+ | `dashboard.sharedFilters[].note` | `string` | No | None | Optional helper text shown under the filter label and above the control. | Parsed as trusted inline HTML. |
111
+ | `dashboard.sharedFilters[].showDropdown` | `boolean` | No | Editor-seeded `true` | Shows the filter control in the dashboard. | The editor usually writes this field, but normal runtime value generation does not default missing `showDropdown` for data filters. Hiding the dropdown does not hide an individual URL filter, nested-dropdown, or tab-simple renderer, but a dashboard filter widget renders nothing when all referenced filters are hidden. |
112
+ | `dashboard.sharedFilters[].order` | `string` | No | `asc` | Sort order for generated filter values. | `cust`, `desc`, `asc`, `column`. `column` is exposed by the editor, but dashboard value generation currently does not apply column-based ordering. |
96
113
  | `dashboard.sharedFilters[].orderedValues` | `string[]` | No | None | Custom display order when `order` is `cust`. | Preserved by editor and runtime sort helpers. |
97
114
  | `dashboard.sharedFilters[].parents` | `string[]` | No | `[]` | Parent filter labels for nested filter chains. | Used by cascading URL and data filters. |
98
- | `dashboard.sharedFilters[].usedBy` | `(string \| number)[]` | No | None | Widgets or rows that consume the filter. | Numbers refer to row indexes; strings refer to visualization keys. |
115
+ | `dashboard.sharedFilters[].usedBy` | `(string \| number)[]` | No | None | Widgets or rows that consume the filter. | Numbers refer to row indexes; strings refer to visualization keys. Dashboard conditions inherit the same row or widget target as the content they control. Missing `usedBy` and `usedBy: []` are unscoped/global for row, visualization, and dashboard-condition filtered-data paths. |
116
+ | `dashboard.sharedFilters[].setByQueryParameter` | `string` | No | None | Query-string parameter used to seed the active value. | Used by both data and URL filters for deep links and parent-child filter flows. |
99
117
  | `dashboard.sharedFilters[].defaultValue` | `string` | No | None | Default selection when no other active value is available. | Used by data and nested-dropdown filters. |
100
118
  | `dashboard.sharedFilters[].resetLabel` | `string` | No | None | Reset option label. | Often shown as `All`, `Reset`, or similar. |
101
119
  | `dashboard.sharedFilters[].labels` | `Record<string, string>` | No | None | Alternate display labels for raw values. | Shared label mapping from `@cdc/core`. |
102
120
  | `dashboard.sharedFilters[].selectLimit` | `number` | No | `5` in multi-select UI | Maximum selections allowed in multi-select mode. | Only used when `filterStyle` is `multi-select`. |
103
- | `dashboard.sharedFilters[].tier` | `number` | No | Runtime-computed | Cascade depth for parent-child filter application. | Assigned automatically from `parents`; not usually authored. |
104
121
  | `dashboard.sharedFilters[].displaySubgroupingOnly` | `boolean` | No | `false` | Shows only subgrouping controls for nested-dropdown filters. | Only used by nested dropdown flows. |
122
+ | `dashboard.sharedFilters[].subGrouping` | `SubGrouping` | No | None | Nested dropdown subgroup state. | Used by data and URL nested-dropdown filters. Dashboard honors `subGrouping.defaultValue`; see `SubGrouping` below for persisted option metadata. |
105
123
 
106
124
  ### Data Filter Fields
107
125
 
108
126
  | Field | Type | Required | Default | Description | Allowed values / Notes |
109
127
  | --- | --- | --- | --- | --- | --- |
110
128
  | `dashboard.sharedFilters[].columnName` | `string` | Yes for `datafilter` | None | Source column used to generate filter values. | Must exist in the active dataset. |
129
+ | `dashboard.sharedFilters[].values` | `string[]` | No | Generated from data when omitted | Values shown by data filters. | Preconfigured non-empty values are preserved; URL-filter values are generated from API state and usually do not need to be authored. |
111
130
  | `dashboard.sharedFilters[].setBy` | `string` | No | None | Visualization key that seeds the filter from its selected datum. | Only used by data filters. |
112
- | `dashboard.sharedFilters[].setByQueryParameter` | `string` | No | None | Query-string parameter used to seed the active value. | Useful for deep links and parent-child filter flows. |
113
- | `dashboard.sharedFilters[].subGrouping` | `SubGrouping` | No | None | Nested dropdown subgroup state. | Used by nested-dropdown filters. |
114
131
 
115
132
  ### URL Filter Fields
116
133
 
@@ -123,38 +140,62 @@ Dashboard filters are split between the dashboard shell and the `dashboardFilter
123
140
  | `dashboard.sharedFilters[].fileName` | `string` | No | None | File-name template for file-name URL filters. | Can include `${query}` as a placeholder for the active filter value. |
124
141
  | `dashboard.sharedFilters[].whitespaceReplacement` | `string` | No | `Keep Spaces` | How spaces are rewritten in file-name filters. | `Keep Spaces`, `Remove Spaces`, `Replace With Underscore` |
125
142
 
143
+ ## Dashboard Conditions
144
+
145
+ Dashboard conditions are optional visibility rules owned by rows and conditional widget entries.
146
+
147
+ | Field | Type | Required | Default | Description | Allowed values / Notes |
148
+ | --- | --- | --- | --- | --- | --- |
149
+ | `rows[].dashboardCondition.id` | `string` | No | Auto-generated | Internal dashboard-condition id used for filtered-data cache keys and stable condition identity. | The editor generates ids like `condition-4fzzzxjy` and preserves existing ids once dashboard-condition authoring is enabled. It is not an author-facing shared-filter target. |
150
+ | `rows[].columns[].conditionalWidgets[].widget` | `string` | Yes in conditional mode | None | Visualization key for one candidate component in the column. | Must match a key in `visualizations`. Author order defines priority. |
151
+ | `rows[].columns[].conditionalWidgets[].dashboardCondition.id` | `string` | No | Auto-generated | Internal dashboard-condition id used for filtered-data cache keys and stable condition identity. | The editor generates ids like `condition-8fzzzbjm` for new conditions and keeps older saved ids unchanged. It is not an author-facing shared-filter target. Scope component conditions with the owning visualization key or row index instead. |
152
+ | `*.dashboardCondition.datasetKey` | `string` | Yes except for `filtersIncomplete` | None | Dataset used to evaluate data-backed dashboard conditions. | May differ from the visualization dataset. Not used by `filtersIncomplete`. |
153
+ | `*.dashboardCondition.operator` | `string` | Yes when a dashboard condition is enabled | None | Dashboard-condition comparison mode. | `hasData`, `hasNoData`, `columnHasAnyValue`, `filtersIncomplete` |
154
+ | `*.dashboardCondition.columnName` | `string` | Only for `columnHasAnyValue` | None | Dataset column inspected by the dashboard condition. | Must exist in the dashboard-condition dataset. |
155
+ | `*.dashboardCondition.values` | `string[]` | Only for `columnHasAnyValue` | `[]` | One or more acceptable values. | Runtime uses loose string coercion so numeric dataset values can match authored strings. |
156
+
157
+ | Behavior | Details |
158
+ | --- | --- |
159
+ | Shared filter application | Conditions apply unscoped filters plus filters targeted to the content they control. Row conditions inherit the row index target. Component conditions inherit the same target their component data would use: the row index when data is configured on the row, otherwise the owning visualization key. Filters whose `columnName` is missing from the condition dataset are ignored for data-backed conditions. |
160
+ | `filtersIncomplete` targeting | `filtersIncomplete` uses the same inherited row or widget target as other dashboard conditions and includes unscoped filters. It passes only when an applicable visible filter is at reset state, and ignores filters scoped to unrelated targets. Use it for static helper content, such as a markup include message. It replaces the default incomplete-filter message but does not make ordinary data-backed rows or widgets render while filters are incomplete. |
161
+ | Unresolved inputs | For data-backed operators, if the condition dataset is unavailable or an applicable filter is still at reset state, the condition resolves as hidden rather than behaving like `hasNoData`. `filtersIncomplete` resolves from filter reset state instead of dataset availability. |
162
+ | Conditional columns | `conditionalWidgets` lets one dashboard column hold multiple candidate components while still rendering as one slot. Runtime uses the first matching entry in author order. |
163
+ | Simple/conditional cleanup | The editor automatically collapses conditional mode back to simple mode when only one unconditioned entry remains after a completed save or delete action. |
164
+ | Row suppression | A false row condition hides the full row. A no-match conditional column hides only that widget slot while preserving grid width. |
165
+ | v1 limitations | Toggle rows and multi-viz rows do not expose condition editing in the editor, and runtime ignores any condition config found there. |
166
+
126
167
  ### `APIFilter`
127
168
 
128
169
  | Field | Type | Required | Default | Description | Allowed values / Notes |
129
170
  | --- | --- | --- | --- | --- | --- |
130
171
  | `apiEndpoint` | `string` | Yes | None | Endpoint used to fetch filter options. | Must resolve to JSON data. |
131
172
  | `valueSelector` | `string` | Yes | None | Field used as the stored filter value. | Required. |
132
- | `textSelector` | `string` | Yes in shape, often blank | `''` | Field used as the visible filter label. | Can be empty when the API returns a single value field. |
133
- | `subgroupValueSelector` | `string` | No | None | Nested subgroup value field for nested dropdowns. | Only used when nested subgrouping is enabled. |
173
+ | `textSelector` | `string` | No | `''` | Field used as the visible filter label. | When omitted or blank, runtime falls back to `valueSelector` for the displayed option text. |
174
+ | `subgroupValueSelector` | `string` | Yes for API-backed nested dropdowns | None | Nested subgroup value field for nested dropdowns. | Required when an API-backed filter uses nested subgrouping. |
134
175
  | `subgroupTextSelector` | `string` | No | None | Nested subgroup label field for nested dropdowns. | Only used when nested subgrouping is enabled. |
135
176
 
136
177
  ### `SubGrouping`
137
178
 
179
+ Dashboard nested-dropdown filters use the shared [`SubGrouping`](https://github.com/CDCgov/cdc-open-viz/blob/main/packages/core/CONFIG.md#subgrouping) structure from core. Dashboard-specific behavior to remember: `subGrouping.defaultValue` can seed the active subgroup when no query parameter or existing active subgroup is available, and `subGrouping.valuesLookup` is persisted option metadata for data-backed nested dropdowns.
180
+
138
181
  | Field | Type | Required | Default | Description | Allowed values / Notes |
139
182
  | --- | --- | --- | --- | --- | --- |
140
- | `columnName` | `string` | No | `''` | Column used for subgroup option generation. | Used by nested dropdown filters. |
141
- | `active` | `string` | No | `''` | Current subgroup selection. | Runtime-managed. |
142
- | `order` | `string` | No | `asc` | Sort order for subgroup values. | `cust`, `desc`, `asc`, `column` |
143
- | `setByQueryParameter` | `string` | No | None | Query string parameter used to seed the subgroup. | Used in URL filter flows. |
144
- | `valuesLookup` | `Record<string, { orderedValues?: string[]; values: string[] }>` | No | `{}` | Cached nested values used by the editor/runtime. | Usually runtime-managed. |
183
+ | `subGrouping.columnName` | `string` | Yes for nested data filters | None | Source column used to compute subgroup values. | Must exist in the dataset for data filters. |
184
+ | `subGrouping.setByQueryParameter` | `string` | No | None | Query-string parameter used to seed the active subgroup value. | Used by dashboard nested-dropdown filters for deep links and query-parameter initialization. |
185
+ | `subGrouping.valuesLookup` | `Record<string, { values: string[]; orderedValues?: string[] }>` | Yes for data nested dropdowns | None | Lookup of subgroup values by parent filter value. | Runtime derives nested options from this object. URL nested dropdowns can receive equivalent option state from API filter loading. |
186
+ | `subGrouping.defaultValue` | `string` | No | None | Default nested selection when no query parameter or active value applies. | Must match a value in the current subgroup. |
145
187
 
146
188
  ## Table And Download Controls
147
189
 
148
- `@cdc/dashboard` uses the shared `Table` structure from the core reference. In this package, the dashboard shell honors the shared table flags plus `table.downloadImageButton` and `table.downloadPdfButton` for the image/PDF download buttons.
190
+ `@cdc/dashboard` uses the shared `Table` structure from the core reference. In this package, the dashboard shell honors the shared table flags plus `table.downloadImageButton` for image downloads. When image downloads are enabled, `table.downloadImageButtonStyle` may be `button` or `link`; omitted values use legacy button styling. `table.downloadPdfButton` can render a PDF action, but the shared media handler currently reports PDF downloads as disabled.
149
191
 
150
- The runtime defaults are the dashboard shell table settings from `packages/dashboard/src/data/initial-state.js`: `show: true`, `showDownloadUrl: false`, `showDownloadLinkBelow: true`, and `showVertical: true`.
192
+ The runtime defaults are the dashboard shell table settings from `packages/dashboard/src/data/initial-state.js`: `label: 'Data Table'`, `show: true`, `showDownloadUrl: false`, `downloadUrlLabel: ''`, `showDownloadLinkBelow: true`, and `showVertical: true`.
151
193
 
152
194
  ## Multi-Dashboard Support
153
195
 
154
196
  | Field | Type | Required | Default | Description | Allowed values / Notes |
155
197
  | --- | --- | --- | --- | --- | --- |
156
198
  | `multiDashboards` | `MultiDashboard[]` | No | None | Optional tabbed dashboard set. | Each entry contains its own `dashboard`, `rows`, `visualizations`, and `label`. |
157
- | `activeDashboard` | `number` | No | `0` when multi-dashboard mode is initialized | Index of the active dashboard tab. | Runtime-managed as the active tab changes. |
158
199
  | `multiDashboards[].label` | `string` | Yes | None | Label shown on the dashboard tab. | Required for each dashboard slot. |
159
200
 
160
201
  ## Fields You Can Ignore
@@ -164,9 +205,15 @@ These fields often appear in saved configs, editor exports, or migration output,
164
205
  | Field or group | Why you can ignore it |
165
206
  | --- | --- |
166
207
  | `runtime.*` | Internal runtime state created during load and render. |
208
+ | Top-level `uuid`, `id`, `category`, `subType`, `orientation`, and `label` | Export/editor metadata that may appear around dashboard configs; these are not required for rendering. |
209
+ | `migrations.*` | Migration bookkeeping that records which update steps have run. |
167
210
  | `visualizations.*.formattedData`, `visualizations.*.originalFormattedData`, `visualizations.*.runtime` | Generated data and runtime artifacts owned by the loader. |
211
+ | `visualizations.*.dashboardFilters` | Runtime helper snapshot of active dashboard shared filters used by markup/filter processing. Author `dashboard.sharedFilters` instead. |
168
212
  | `visualizations.*.editing`, `visualizations.*.showEditorPanel`, `visualizations.*.newViz`, `visualizations.*.openModal` | Editor state, not consumer config. |
169
- | `visualizations.*.uid`, `visualizations.*.visualizationType` | Editor/runtime identifiers that are injected or normalized internally. |
213
+ | `visualizations.*.uid` | Editor/runtime identifier that is injected or normalized internally. |
170
214
  | `datasets.*.dataFileSize`, `datasets.*.dataFileName`, `datasets.*.dataFileFormat`, `datasets.*.dataFileSourceType`, `datasets.*.runtimeDataUrl` | Runtime or file metadata. |
171
- | `rows[].uuid`, `rows[].columns[].hide`, `rows[].columns[].toggleName`, `rows[].columns[].equalHeight`, `rows[].originalMultiVizColumn` | Layout bookkeeping that may be injected or updated by the editor. |
215
+ | Top-level `dataFileName`, `dataFileSourceType`, and `formattedData` | Legacy single-dataset metadata/runtime output. Prefer named `datasets` for new configs. |
216
+ | `rows[].uuid`, `rows[].columns[].hide`, `rows[].columns[].equalHeight`, `rows[].originalMultiVizColumn` | Layout bookkeeping that may be injected or updated by the editor. |
217
+ | `dashboard.sharedFilters[].tier`, `dashboard.sharedFilters[].active`, `dashboard.sharedFilters[].queuedActive`, and `dashboard.sharedFilters[].subGrouping.active` | Runtime/cache filter state. These may be present in saved configs, but consumers usually only author stable filter definitions plus optional defaults. |
172
218
  | `dashboard.filters` | Legacy migration field replaced by `dashboard.sharedFilters`. |
219
+ | `activeDashboard` | Runtime-managed active tab index for multi-dashboard sets. |
package/LICENSE ADDED
@@ -0,0 +1,201 @@
1
+ Apache License
2
+ Version 2.0, January 2004
3
+ http://www.apache.org/licenses/
4
+
5
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6
+
7
+ 1. Definitions.
8
+
9
+ "License" shall mean the terms and conditions for use, reproduction,
10
+ and distribution as defined by Sections 1 through 9 of this document.
11
+
12
+ "Licensor" shall mean the copyright owner or entity authorized by
13
+ the copyright owner that is granting the License.
14
+
15
+ "Legal Entity" shall mean the union of the acting entity and all
16
+ other entities that control, are controlled by, or are under common
17
+ control with that entity. For the purposes of this definition,
18
+ "control" means (i) the power, direct or indirect, to cause the
19
+ direction or management of such entity, whether by contract or
20
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
21
+ outstanding shares, or (iii) beneficial ownership of such entity.
22
+
23
+ "You" (or "Your") shall mean an individual or Legal Entity
24
+ exercising permissions granted by this License.
25
+
26
+ "Source" form shall mean the preferred form for making modifications,
27
+ including but not limited to software source code, documentation
28
+ source, and configuration files.
29
+
30
+ "Object" form shall mean any form resulting from mechanical
31
+ transformation or translation of a Source form, including but
32
+ not limited to compiled object code, generated documentation,
33
+ and conversions to other media types.
34
+
35
+ "Work" shall mean the work of authorship, whether in Source or
36
+ Object form, made available under the License, as indicated by a
37
+ copyright notice that is included in or attached to the work
38
+ (an example is provided in the Appendix below).
39
+
40
+ "Derivative Works" shall mean any work, whether in Source or Object
41
+ form, that is based on (or derived from) the Work and for which the
42
+ editorial revisions, annotations, elaborations, or other modifications
43
+ represent, as a whole, an original work of authorship. For the purposes
44
+ of this License, Derivative Works shall not include works that remain
45
+ separable from, or merely link (or bind by name) to the interfaces of,
46
+ the Work and Derivative Works thereof.
47
+
48
+ "Contribution" shall mean any work of authorship, including
49
+ the original version of the Work and any modifications or additions
50
+ to that Work or Derivative Works thereof, that is intentionally
51
+ submitted to Licensor for inclusion in the Work by the copyright owner
52
+ or by an individual or Legal Entity authorized to submit on behalf of
53
+ the copyright owner. For the purposes of this definition, "submitted"
54
+ means any form of electronic, verbal, or written communication sent
55
+ to the Licensor or its representatives, including but not limited to
56
+ communication on electronic mailing lists, source code control systems,
57
+ and issue tracking systems that are managed by, or on behalf of, the
58
+ Licensor for the purpose of discussing and improving the Work, but
59
+ excluding communication that is conspicuously marked or otherwise
60
+ designated in writing by the copyright owner as "Not a Contribution."
61
+
62
+ "Contributor" shall mean Licensor and any individual or Legal Entity
63
+ on behalf of whom a Contribution has been received by Licensor and
64
+ subsequently incorporated within the Work.
65
+
66
+ 2. Grant of Copyright License. Subject to the terms and conditions of
67
+ this License, each Contributor hereby grants to You a perpetual,
68
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69
+ copyright license to reproduce, prepare Derivative Works of,
70
+ publicly display, publicly perform, sublicense, and distribute the
71
+ Work and such Derivative Works in Source or Object form.
72
+
73
+ 3. Grant of Patent License. Subject to the terms and conditions of
74
+ this License, each Contributor hereby grants to You a perpetual,
75
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76
+ (except as stated in this section) patent license to make, have made,
77
+ use, offer to sell, sell, import, and otherwise transfer the Work,
78
+ where such license applies only to those patent claims licensable
79
+ by such Contributor that are necessarily infringed by their
80
+ Contribution(s) alone or by combination of their Contribution(s)
81
+ with the Work to which such Contribution(s) was submitted. If You
82
+ institute patent litigation against any entity (including a
83
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
84
+ or a Contribution incorporated within the Work constitutes direct
85
+ or contributory patent infringement, then any patent licenses
86
+ granted to You under this License for that Work shall terminate
87
+ as of the date such litigation is filed.
88
+
89
+ 4. Redistribution. You may reproduce and distribute copies of the
90
+ Work or Derivative Works thereof in any medium, with or without
91
+ modifications, and in Source or Object form, provided that You
92
+ meet the following conditions:
93
+
94
+ (a) You must give any other recipients of the Work or
95
+ Derivative Works a copy of this License; and
96
+
97
+ (b) You must cause any modified files to carry prominent notices
98
+ stating that You changed the files; and
99
+
100
+ (c) You must retain, in the Source form of any Derivative Works
101
+ that You distribute, all copyright, patent, trademark, and
102
+ attribution notices from the Source form of the Work,
103
+ excluding those notices that do not pertain to any part of
104
+ the Derivative Works; and
105
+
106
+ (d) If the Work includes a "NOTICE" text file as part of its
107
+ distribution, then any Derivative Works that You distribute must
108
+ include a readable copy of the attribution notices contained
109
+ within such NOTICE file, excluding those notices that do not
110
+ pertain to any part of the Derivative Works, in at least one
111
+ of the following places: within a NOTICE text file distributed
112
+ as part of the Derivative Works; within the Source form or
113
+ documentation, if provided along with the Derivative Works; or,
114
+ within a display generated by the Derivative Works, if and
115
+ wherever such third-party notices normally appear. The contents
116
+ of the NOTICE file are for informational purposes only and
117
+ do not modify the License. You may add Your own attribution
118
+ notices within Derivative Works that You distribute, alongside
119
+ or as an addendum to the NOTICE text from the Work, provided
120
+ that such additional attribution notices cannot be construed
121
+ as modifying the License.
122
+
123
+ You may add Your own copyright statement to Your modifications and
124
+ may provide additional or different license terms and conditions
125
+ for use, reproduction, or distribution of Your modifications, or
126
+ for any such Derivative Works as a whole, provided Your use,
127
+ reproduction, and distribution of the Work otherwise complies with
128
+ the conditions stated in this License.
129
+
130
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
131
+ any Contribution intentionally submitted for inclusion in the Work
132
+ by You to the Licensor shall be under the terms and conditions of
133
+ this License, without any additional terms or conditions.
134
+ Notwithstanding the above, nothing herein shall supersede or modify
135
+ the terms of any separate license agreement you may have executed
136
+ with Licensor regarding such Contributions.
137
+
138
+ 6. Trademarks. This License does not grant permission to use the trade
139
+ names, trademarks, service marks, or product names of the Licensor,
140
+ except as required for reasonable and customary use in describing the
141
+ origin of the Work and reproducing the content of the NOTICE file.
142
+
143
+ 7. Disclaimer of Warranty. Unless required by applicable law or
144
+ agreed to in writing, Licensor provides the Work (and each
145
+ Contributor provides its Contributions) on an "AS IS" BASIS,
146
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147
+ implied, including, without limitation, any warranties or conditions
148
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149
+ PARTICULAR PURPOSE. You are solely responsible for determining the
150
+ appropriateness of using or redistributing the Work and assume any
151
+ risks associated with Your exercise of permissions under this License.
152
+
153
+ 8. Limitation of Liability. In no event and under no legal theory,
154
+ whether in tort (including negligence), contract, or otherwise,
155
+ unless required by applicable law (such as deliberate and grossly
156
+ negligent acts) or agreed to in writing, shall any Contributor be
157
+ liable to You for damages, including any direct, indirect, special,
158
+ incidental, or consequential damages of any character arising as a
159
+ result of this License or out of the use or inability to use the
160
+ Work (including but not limited to damages for loss of goodwill,
161
+ work stoppage, computer failure or malfunction, or any and all
162
+ other commercial damages or losses), even if such Contributor
163
+ has been advised of the possibility of such damages.
164
+
165
+ 9. Accepting Warranty or Additional Liability. While redistributing
166
+ the Work or Derivative Works thereof, You may choose to offer,
167
+ and charge a fee for, acceptance of support, warranty, indemnity,
168
+ or other liability obligations and/or rights consistent with this
169
+ License. However, in accepting such obligations, You may act only
170
+ on Your own behalf and on Your sole responsibility, not on behalf
171
+ of any other Contributor, and only if You agree to indemnify,
172
+ defend, and hold each Contributor harmless for any liability
173
+ incurred by, or claims asserted against, such Contributor by reason
174
+ of your accepting any such warranty or additional liability.
175
+
176
+ END OF TERMS AND CONDITIONS
177
+
178
+ APPENDIX: How to apply the Apache License to your work.
179
+
180
+ To apply the Apache License to your work, attach the following
181
+ boilerplate notice, with the fields enclosed by brackets "[]"
182
+ replaced with your own identifying information. (Don't include
183
+ the brackets!) The text should be enclosed in the appropriate
184
+ comment syntax for the file format. We also recommend that a
185
+ file or class name and description of purpose be included on the
186
+ same "printed page" as the copyright notice for easier
187
+ identification within third-party archives.
188
+
189
+ Copyright [yyyy] [name of copyright owner]
190
+
191
+ Licensed under the Apache License, Version 2.0 (the "License");
192
+ you may not use this file except in compliance with the License.
193
+ You may obtain a copy of the License at
194
+
195
+ http://www.apache.org/licenses/LICENSE-2.0
196
+
197
+ Unless required by applicable law or agreed to in writing, software
198
+ distributed under the License is distributed on an "AS IS" BASIS,
199
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200
+ See the License for the specific language governing permissions and
201
+ limitations under the License.