@richardmcquiston01/dashboard-widgets-toolkit 0.4.0 → 0.5.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/dist/react.d.cts CHANGED
@@ -88,8 +88,10 @@ declare function GraphWidget({ data, title, }: {
88
88
  /**
89
89
  * Loads a widget's full detail data. Close over your own context (a Prisma
90
90
  * client, a fetch) here; the package never fetches. See `DetailProvider`.
91
+ * Return `undefined` for a widget you have no extra data for: the card's own
92
+ * data is used when it is complete (see `deriveDetailData`).
91
93
  */
92
- type DetailLoader = (definition: WidgetDefinition, options: DetailProviderOptions) => DetailData | Promise<DetailData>;
94
+ type DetailLoader = (definition: WidgetDefinition, options: DetailProviderOptions) => DetailData | undefined | Promise<DetailData | undefined>;
93
95
  interface WidgetDetailProps {
94
96
  /** Table caption: the widget's title. */
95
97
  readonly title: string;
@@ -135,9 +137,12 @@ interface DetailLoadState {
135
137
  /**
136
138
  * Loads a widget's detail data while `enabled`: calls `load` with an abort
137
139
  * signal (aborted on close, retry or unmount), validates the result and
138
- * reports loading, ok or error. `reload` runs it again.
140
+ * reports loading, ok or error. When `load` is missing or returns
141
+ * `undefined`, `fallback` (for example the card's own complete data) is used
142
+ * instead. `reload` runs it again. Keep `load` and `fallback` referentially
143
+ * stable (`useCallback` / `useMemo`): a new value restarts the load.
139
144
  */
140
- declare function useDetailData(definition: WidgetDefinition, load: DetailLoader | undefined, enabled: boolean): {
145
+ declare function useDetailData(definition: WidgetDefinition, load: DetailLoader | undefined, enabled: boolean, fallback?: DetailData): {
141
146
  readonly state: DetailLoadState;
142
147
  readonly reload: () => void;
143
148
  };
@@ -228,9 +233,10 @@ interface DashboardProps extends WidgetSettings {
228
233
  readonly onRetry?: (key: string) => void;
229
234
  /**
230
235
  * Loads the full data for a widget's detail view (widgets whose definition
231
- * sets `detail`). Close over your own context. Without it, TABLE widgets
232
- * with no footer and BAR_LIST widgets show their card data; other widgets
233
- * get no View button.
236
+ * sets `detail`). Close over your own context. Return `undefined` for a
237
+ * widget you have no extra data for: a TABLE with no footer or a BAR_LIST
238
+ * then shows its card data. Without `loadDetail`, those two still work and
239
+ * other widgets get no View button.
234
240
  */
235
241
  readonly loadDetail?: DetailLoader;
236
242
  /**
package/dist/react.d.ts CHANGED
@@ -88,8 +88,10 @@ declare function GraphWidget({ data, title, }: {
88
88
  /**
89
89
  * Loads a widget's full detail data. Close over your own context (a Prisma
90
90
  * client, a fetch) here; the package never fetches. See `DetailProvider`.
91
+ * Return `undefined` for a widget you have no extra data for: the card's own
92
+ * data is used when it is complete (see `deriveDetailData`).
91
93
  */
92
- type DetailLoader = (definition: WidgetDefinition, options: DetailProviderOptions) => DetailData | Promise<DetailData>;
94
+ type DetailLoader = (definition: WidgetDefinition, options: DetailProviderOptions) => DetailData | undefined | Promise<DetailData | undefined>;
93
95
  interface WidgetDetailProps {
94
96
  /** Table caption: the widget's title. */
95
97
  readonly title: string;
@@ -135,9 +137,12 @@ interface DetailLoadState {
135
137
  /**
136
138
  * Loads a widget's detail data while `enabled`: calls `load` with an abort
137
139
  * signal (aborted on close, retry or unmount), validates the result and
138
- * reports loading, ok or error. `reload` runs it again.
140
+ * reports loading, ok or error. When `load` is missing or returns
141
+ * `undefined`, `fallback` (for example the card's own complete data) is used
142
+ * instead. `reload` runs it again. Keep `load` and `fallback` referentially
143
+ * stable (`useCallback` / `useMemo`): a new value restarts the load.
139
144
  */
140
- declare function useDetailData(definition: WidgetDefinition, load: DetailLoader | undefined, enabled: boolean): {
145
+ declare function useDetailData(definition: WidgetDefinition, load: DetailLoader | undefined, enabled: boolean, fallback?: DetailData): {
141
146
  readonly state: DetailLoadState;
142
147
  readonly reload: () => void;
143
148
  };
@@ -228,9 +233,10 @@ interface DashboardProps extends WidgetSettings {
228
233
  readonly onRetry?: (key: string) => void;
229
234
  /**
230
235
  * Loads the full data for a widget's detail view (widgets whose definition
231
- * sets `detail`). Close over your own context. Without it, TABLE widgets
232
- * with no footer and BAR_LIST widgets show their card data; other widgets
233
- * get no View button.
236
+ * sets `detail`). Close over your own context. Return `undefined` for a
237
+ * widget you have no extra data for: a TABLE with no footer or a BAR_LIST
238
+ * then shows its card data. Without `loadDetail`, those two still work and
239
+ * other widgets get no View button.
234
240
  */
235
241
  readonly loadDetail?: DetailLoader;
236
242
  /**
package/dist/react.js CHANGED
@@ -1783,22 +1783,30 @@ function WidgetDetailDialog({
1783
1783
  }
1784
1784
  );
1785
1785
  }
1786
- function useDetailData(definition, load, enabled) {
1786
+ function useDetailData(definition, load, enabled, fallback) {
1787
1787
  const [state, setState] = useState({ status: "loading" });
1788
1788
  const [attempt, setAttempt] = useState(0);
1789
1789
  useEffect(() => {
1790
- if (!enabled || load === void 0) return void 0;
1790
+ if (!enabled) return void 0;
1791
1791
  const controller = new AbortController();
1792
1792
  setState({ status: "loading" });
1793
1793
  Promise.resolve().then(
1794
- () => load(definition, {
1794
+ () => load === void 0 ? void 0 : load(definition, {
1795
1795
  signal: controller.signal,
1796
1796
  query: defaultDetailQuery()
1797
1797
  })
1798
1798
  ).then((raw) => {
1799
1799
  if (controller.signal.aborted) return;
1800
+ const data = raw ?? fallback;
1801
+ if (data === void 0) {
1802
+ setState({
1803
+ status: "error",
1804
+ error: `Widget "${definition.key}" detail: no data. The loader returned nothing for this widget and its card does not hold the full data (use a loader that returns rows, or a TABLE without a footer or a BAR_LIST).`
1805
+ });
1806
+ return;
1807
+ }
1800
1808
  const checked = validateDetailData(
1801
- raw,
1809
+ data,
1802
1810
  `Widget "${definition.key}" detail`
1803
1811
  );
1804
1812
  setState(
@@ -1812,7 +1820,7 @@ function useDetailData(definition, load, enabled) {
1812
1820
  });
1813
1821
  });
1814
1822
  return () => controller.abort();
1815
- }, [enabled, load, definition, attempt]);
1823
+ }, [enabled, load, fallback, definition, attempt]);
1816
1824
  return { state, reload: () => setAttempt((count) => count + 1) };
1817
1825
  }
1818
1826
  var NARROW_VIEWPORT_QUERY = "(max-width: 640px)";
@@ -1910,12 +1918,16 @@ function DetailHost({
1910
1918
  const options = resolveDetailOptions(definition);
1911
1919
  const pageSize = options?.pageSize ?? 25;
1912
1920
  const [query, setQuery] = useState(defaultDetailQuery(pageSize));
1913
- const derived = widget.status === "ok" ? deriveDetailData(widget.data, locale) : void 0;
1914
- const load = useMemo(
1915
- () => loadDetail ?? (derived === void 0 ? void 0 : () => derived),
1916
- [loadDetail, derived]
1921
+ const derived = useMemo(
1922
+ () => widget.status === "ok" ? deriveDetailData(widget.data, locale) : void 0,
1923
+ [widget, locale]
1924
+ );
1925
+ const { state, reload } = useDetailData(
1926
+ definition,
1927
+ loadDetail,
1928
+ true,
1929
+ derived
1917
1930
  );
1918
- const { state, reload } = useDetailData(definition, load, true);
1919
1931
  const title = options?.title ?? definition.title;
1920
1932
  return /* @__PURE__ */ jsx(WidgetDetailDialog, { title, open: true, onClose, children: /* @__PURE__ */ jsx(
1921
1933
  WidgetDetail,
@@ -1923,10 +1935,7 @@ function DetailHost({
1923
1935
  title,
1924
1936
  query,
1925
1937
  onQueryChange: setQuery,
1926
- status: load === void 0 ? "error" : state.status,
1927
- ...load === void 0 ? {
1928
- error: `Widget "${definition.key}" has no detail data: pass loadDetail, or use a TABLE without a footer or a BAR_LIST.`
1929
- } : {},
1938
+ status: state.status,
1930
1939
  ...state.data === void 0 ? {} : { data: state.data },
1931
1940
  ...state.error === void 0 ? {} : { error: state.error },
1932
1941
  onRetry: reload