@flytedan/flytebot-design-system 0.60.0 → 0.61.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/index.cjs CHANGED
@@ -216,6 +216,7 @@ __export(index_exports, {
216
216
  formatRelative: () => formatRelative,
217
217
  groupMessages: () => groupMessages,
218
218
  hasModifier: () => hasModifier,
219
+ historyUrlAdapter: () => historyUrlAdapter,
219
220
  iconForMime: () => iconForMime,
220
221
  importanceLevel: () => importanceLevel,
221
222
  importanceTokens: () => importanceTokens,
@@ -235,6 +236,7 @@ __export(index_exports, {
235
236
  preferenceScore: () => preferenceScore,
236
237
  preferenceSegments: () => preferenceSegments,
237
238
  rangeFilter: () => rangeFilter,
239
+ readQueryFromUrl: () => readQueryFromUrl,
238
240
  resolveTheme: () => resolveTheme,
239
241
  revokeAttachment: () => revokeAttachment,
240
242
  savePrefs: () => savePrefs,
@@ -258,7 +260,8 @@ __export(index_exports, {
258
260
  useToast: () => useToast,
259
261
  validateFilterValue: () => validateFilterValue,
260
262
  visibleMessages: () => visibleMessages,
261
- watchAppearance: () => watchAppearance
263
+ watchAppearance: () => watchAppearance,
264
+ writeQueryToUrl: () => writeQueryToUrl
262
265
  });
263
266
  module.exports = __toCommonJS(index_exports);
264
267
 
@@ -14467,6 +14470,75 @@ function BreadcrumbCrumb({
14467
14470
 
14468
14471
  // src/kits/query.ts
14469
14472
  var React74 = __toESM(require("react"), 1);
14473
+
14474
+ // src/kits/queryUrl.ts
14475
+ var historyUrlAdapter = {
14476
+ read: () => new URLSearchParams(window.location.search),
14477
+ write(next) {
14478
+ const search = next.toString();
14479
+ const url = window.location.pathname + (search ? `?${search}` : "") + window.location.hash;
14480
+ window.history.replaceState(window.history.state, "", url);
14481
+ },
14482
+ subscribe(onChange) {
14483
+ const onPopState = () => onChange(new URLSearchParams(window.location.search));
14484
+ window.addEventListener("popstate", onPopState);
14485
+ return () => window.removeEventListener("popstate", onPopState);
14486
+ }
14487
+ };
14488
+ var names = (scope) => {
14489
+ const prefix = scope ? `${scope}-` : "";
14490
+ return {
14491
+ q: `${prefix}q`,
14492
+ sort: `${prefix}sort`,
14493
+ dir: `${prefix}dir`,
14494
+ page: `${prefix}page`,
14495
+ pageSize: `${prefix}pageSize`,
14496
+ filters: `${prefix}filters`
14497
+ };
14498
+ };
14499
+ var positiveInt = (text) => {
14500
+ const n = Number(text);
14501
+ return text !== null && Number.isInteger(n) && n >= 1 ? n : null;
14502
+ };
14503
+ function parseFilters(text) {
14504
+ if (text === null) return null;
14505
+ try {
14506
+ const parsed = JSON.parse(text);
14507
+ return parsed !== null && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : null;
14508
+ } catch {
14509
+ return null;
14510
+ }
14511
+ }
14512
+ function readQueryFromUrl(params, scope, base, paging) {
14513
+ const n = names(scope);
14514
+ const dir = params.get(n.dir);
14515
+ return {
14516
+ page: paging === "page" ? positiveInt(params.get(n.page)) ?? base.page : base.page,
14517
+ pageSize: positiveInt(params.get(n.pageSize)) ?? base.pageSize,
14518
+ sort: params.get(n.sort) || base.sort,
14519
+ dir: dir === "asc" || dir === "desc" ? dir : base.dir,
14520
+ q: params.get(n.q) ?? base.q,
14521
+ filters: parseFilters(params.get(n.filters)) ?? base.filters
14522
+ };
14523
+ }
14524
+ function writeQueryToUrl(params, scope, query, base, paging) {
14525
+ const n = names(scope);
14526
+ const next = new URLSearchParams(params);
14527
+ const set = (name, value, isDefault) => {
14528
+ if (value === null || isDefault) next.delete(name);
14529
+ else next.set(name, value);
14530
+ };
14531
+ set(n.q, query.q, query.q === base.q);
14532
+ set(n.sort, query.sort, query.sort === base.sort);
14533
+ set(n.dir, query.dir, query.dir === base.dir);
14534
+ set(n.pageSize, String(query.pageSize), query.pageSize === base.pageSize);
14535
+ set(n.page, String(query.page), paging === "keyset" || query.page === base.page);
14536
+ const filters = JSON.stringify(query.filters);
14537
+ set(n.filters, filters, filters === JSON.stringify(base.filters));
14538
+ return next;
14539
+ }
14540
+
14541
+ // src/kits/query.ts
14470
14542
  var isMultiSelectShaped = (v) => !!v && typeof v === "object" && Array.isArray(v.values);
14471
14543
  var isRangeShaped = (v) => !!v && typeof v === "object" && ("min" in v || "max" in v);
14472
14544
  var isDateShaped = (v) => !!v && typeof v === "object" && "kind" in v;
@@ -14520,50 +14592,129 @@ function compare(a, b, dir) {
14520
14592
  }
14521
14593
  var API = null;
14522
14594
  var PREFS = null;
14523
- function useServerTable({ endpoint, params, defaults, deps, prefsKey }) {
14524
- const [query, setQuery] = React74.useState(() => {
14595
+ function loadedFrom(paging, answer) {
14596
+ if (paging === "keyset") {
14597
+ const r2 = answer;
14598
+ return { rows: r2.rows, pager: null, next: r2.next, total: r2.total ?? null, facets: r2.filter?.facets ?? {}, aggregate: r2.aggregate ?? {} };
14599
+ }
14600
+ const r = answer;
14601
+ return { rows: r.rows, pager: r.pager ?? null, next: null, total: null, facets: r.filter?.facets ?? {}, aggregate: r.aggregate ?? {} };
14602
+ }
14603
+ function requestFor(params, query, paging, after) {
14604
+ if (paging === "page") return { ...params || {}, ...query };
14605
+ const { sort, dir, q, filters, pageSize } = query;
14606
+ return { ...params || {}, sort, dir, q, filters, limit: pageSize, ...after === void 0 ? {} : { after } };
14607
+ }
14608
+ var toError = (e) => e instanceof Error ? e : new Error(String(e));
14609
+ function useServerTable({ endpoint, params, defaults, deps, prefsKey, paging = "page", url }) {
14610
+ const [base] = React74.useState(() => {
14525
14611
  const store = PREFS || window.PlannerPrefs;
14526
14612
  const saved = prefsKey && store ? store.getTable(prefsKey) : {};
14527
14613
  return { ...DEFAULTS, ...defaults || {}, ...saved.pageSize ? { pageSize: saved.pageSize } : {}, ...saved.sort ? { sort: saved.sort, dir: saved.dir || "desc" } : {} };
14528
14614
  });
14615
+ const [mountParams] = React74.useState(() => url ? (url.adapter ?? historyUrlAdapter).read() : null);
14616
+ const address = React74.useRef(mountParams);
14617
+ const [query, setQuery] = React74.useState(() => url && mountParams ? readQueryFromUrl(mountParams, url.scope, base, paging) : base);
14529
14618
  const savePref = React74.useCallback((patch2) => {
14530
14619
  const store = PREFS || window.PlannerPrefs;
14531
14620
  if (prefsKey && store) store.setTable(prefsKey, patch2);
14532
14621
  }, [prefsKey]);
14533
14622
  const [res, setRes] = React74.useState(null);
14534
14623
  const [loading, setLoading] = React74.useState(true);
14535
- const seq2 = React74.useRef(0);
14624
+ const [loadingMore, setLoadingMore] = React74.useState(false);
14625
+ const [failure, setFailure] = React74.useState(null);
14626
+ const [reloadToken, setReloadToken] = React74.useState(0);
14627
+ const moreController = React74.useRef(null);
14536
14628
  const depKey = (deps || []).join("|");
14537
14629
  const paramKey = JSON.stringify(params || {});
14538
14630
  const queryKey = JSON.stringify(query);
14631
+ const lifetime = React74.useRef(null);
14632
+ React74.useEffect(() => {
14633
+ const controller = new AbortController();
14634
+ lifetime.current = controller;
14635
+ return () => controller.abort();
14636
+ }, []);
14637
+ const latest = React74.useRef({ res, loading, query, params, endpoint, url, base });
14638
+ React74.useEffect(() => {
14639
+ latest.current = { res, loading, query, params, endpoint, url, base };
14640
+ });
14539
14641
  React74.useEffect(() => {
14540
- const id = ++seq2.current;
14642
+ const controller = new AbortController();
14643
+ const { signal } = controller;
14541
14644
  setLoading(true);
14645
+ setLoadingMore(false);
14646
+ setFailure(null);
14542
14647
  const t = setTimeout(() => {
14543
- QueryKit.getApi().request(endpoint, { ...params || {}, ...query }).then((r) => {
14544
- if (id !== seq2.current) return;
14545
- setRes(r);
14546
- setLoading(false);
14547
- });
14648
+ const { url: u, base: b } = latest.current;
14649
+ if (u && address.current) {
14650
+ const next = writeQueryToUrl(address.current, u.scope, query, b, paging);
14651
+ if (next.toString() !== address.current.toString()) {
14652
+ address.current = next;
14653
+ (u.adapter ?? historyUrlAdapter).write(next);
14654
+ }
14655
+ }
14656
+ QueryKit.getApi().request(endpoint, requestFor(params, query, paging), { signal }).then(
14657
+ (r) => {
14658
+ if (signal.aborted) return;
14659
+ setRes(loadedFrom(paging, r));
14660
+ setLoading(false);
14661
+ },
14662
+ (e) => {
14663
+ if (signal.aborted) return;
14664
+ setFailure({ error: toError(e), during: "query" });
14665
+ setLoading(false);
14666
+ }
14667
+ );
14548
14668
  }, query.q ? 220 : 60);
14549
- return () => clearTimeout(t);
14550
- }, [endpoint, queryKey, paramKey, depKey]);
14669
+ return () => {
14670
+ clearTimeout(t);
14671
+ controller.abort();
14672
+ moreController.current?.abort();
14673
+ moreController.current = null;
14674
+ };
14675
+ }, [endpoint, queryKey, paramKey, depKey, paging, reloadToken]);
14676
+ const adapter = url ? url.adapter ?? historyUrlAdapter : null;
14677
+ React74.useEffect(() => {
14678
+ if (!adapter) return;
14679
+ return adapter.subscribe((params2) => {
14680
+ const { url: u, base: b } = latest.current;
14681
+ if (!u) return;
14682
+ address.current = params2;
14683
+ const fromAddress = readQueryFromUrl(params2, u.scope, b, paging);
14684
+ setQuery((s) => JSON.stringify(s) === JSON.stringify(fromAddress) ? s : fromAddress);
14685
+ });
14686
+ }, [adapter, paging]);
14551
14687
  const patch = (p, resetPage = true) => setQuery((s) => ({ ...s, ...p, page: resetPage ? 1 : p.page != null ? p.page : s.page }));
14552
- const defaultPager = { page: query.page, pageSize: query.pageSize, total: 0, totalPages: 1, from: 0, to: 0, hasPrev: false, hasNext: false };
14553
- const pager = res && res.pager || defaultPager;
14554
- const api = {
14688
+ const loadMore = () => {
14689
+ const { res: loaded, loading: querying, query: q, params: p, endpoint: e } = latest.current;
14690
+ if (!loaded || loaded.next === null || querying || moreController.current !== null) return;
14691
+ const controller = new AbortController();
14692
+ const { signal } = controller;
14693
+ moreController.current = controller;
14694
+ setLoadingMore(true);
14695
+ setFailure(null);
14696
+ QueryKit.getApi().request(e, requestFor(p, q, "keyset", loaded.next), { signal }).then(
14697
+ (r) => {
14698
+ if (signal.aborted) return;
14699
+ moreController.current = null;
14700
+ const more = loadedFrom("keyset", r);
14701
+ setRes((now) => now && { ...more, rows: [...now.rows, ...more.rows], total: more.total ?? now.total });
14702
+ setLoadingMore(false);
14703
+ },
14704
+ (err) => {
14705
+ if (signal.aborted) return;
14706
+ moreController.current = null;
14707
+ setFailure({ error: toError(err), during: "more" });
14708
+ setLoadingMore(false);
14709
+ }
14710
+ );
14711
+ };
14712
+ const shared = {
14555
14713
  rows: res ? res.rows : [],
14556
- /** The response's pager block, verbatim. */
14557
- pager,
14558
- total: pager.total || 0,
14559
- page: pager.page || query.page,
14560
14714
  pageSize: query.pageSize,
14561
- totalPages: pager.totalPages || 1,
14562
- from: pager.from || 0,
14563
- to: pager.to || 0,
14564
- facets: res && res.filter && res.filter.facets || {},
14715
+ facets: res ? res.facets : {},
14565
14716
  /** Server-computed totals over the whole match, never the current page. */
14566
- aggregate: res && res.aggregate || {},
14717
+ aggregate: res ? res.aggregate : {},
14567
14718
  /** The live query object — pass to fetchAll-style calls that need the full match. */
14568
14719
  query,
14569
14720
  sort: query.sort ? { key: query.sort, dir: query.dir } : void 0,
@@ -14572,8 +14723,12 @@ function useServerTable({ endpoint, params, defaults, deps, prefsKey }) {
14572
14723
  loading,
14573
14724
  /** True only on the very first load — later loads keep showing stale rows. */
14574
14725
  initialLoading: loading && !res,
14726
+ error: failure ? failure.error : null,
14727
+ retry: () => {
14728
+ if (failure?.during === "more") loadMore();
14729
+ else setReloadToken((t) => t + 1);
14730
+ },
14575
14731
  activeFilterCount: Object.values(query.filters).filter(isActiveFilterValue).length,
14576
- setPage: (page) => patch({ page }, false),
14577
14732
  setPageSize: (pageSize) => {
14578
14733
  savePref({ pageSize });
14579
14734
  patch({ pageSize });
@@ -14588,8 +14743,6 @@ function useServerTable({ endpoint, params, defaults, deps, prefsKey }) {
14588
14743
  savePref({ sort: key, dir: dir || "desc" });
14589
14744
  setQuery((s) => ({ ...s, page: 1, sort: key, dir: dir || "desc" }));
14590
14745
  },
14591
- /** Same query, unpaged — for CSV export and select-all-matching. */
14592
- fetchAll: () => QueryKit.getApi().request(endpoint, { ...params || {}, ...query, page: 1, pageSize: 1e5 }).then((r) => r.rows),
14593
14746
  /** value may be an updater fn — required for toggles, or two fast clicks
14594
14747
  both read the same stale array and the first one is lost. */
14595
14748
  setFilter: (id, value) => setQuery((s) => ({ ...s, page: 1, filters: { ...s.filters, [id]: typeof value === "function" ? value(s.filters[id]) : value } })),
@@ -14600,14 +14753,30 @@ function useServerTable({ endpoint, params, defaults, deps, prefsKey }) {
14600
14753
  }),
14601
14754
  setFilters: (filters) => patch({ filters }),
14602
14755
  clearFilters: () => patch({ filters: {}, q: "" }),
14603
- refresh: () => setQuery((s) => ({ ...s })),
14756
+ refresh: () => setReloadToken((t) => t + 1),
14757
+ patchRows: (change) => setRes((now) => now && { ...now, rows: change(now.rows) }),
14604
14758
  /* True on a re-query while the PREVIOUS rows are still rendered — sort, filter, page.
14605
14759
  Distinct from `loading` (first load, nothing on screen) because the two need different
14606
14760
  treatments: skeletons versus a stale overlay on real content. */
14607
14761
  refreshing: loading && !!res,
14608
14762
  queryString: QueryKit.toQueryString(query)
14609
14763
  };
14610
- return api;
14764
+ if (paging === "keyset") {
14765
+ return { ...shared, total: res ? res.total : null, hasMore: !!res && res.next !== null, loadingMore, loadMore };
14766
+ }
14767
+ const pager = res && res.pager || { page: query.page, pageSize: query.pageSize, total: 0, totalPages: 1, from: 0, to: 0, hasPrev: false, hasNext: false };
14768
+ return {
14769
+ ...shared,
14770
+ pager,
14771
+ total: pager.total || 0,
14772
+ page: pager.page || query.page,
14773
+ totalPages: pager.totalPages || 1,
14774
+ from: pager.from || 0,
14775
+ to: pager.to || 0,
14776
+ setPage: (page) => patch({ page }, false),
14777
+ /** Same query, unpaged — for CSV export and select-all-matching. */
14778
+ fetchAll: () => QueryKit.getApi().request(endpoint, { ...params || {}, ...query, page: 1, pageSize: 1e5 }, { signal: lifetime.current.signal }).then((r) => r.rows)
14779
+ };
14611
14780
  }
14612
14781
  var QueryKit = {
14613
14782
  DEFAULTS,
@@ -15053,6 +15222,7 @@ function useDocumentTitle(routeTitle, appName) {
15053
15222
  formatRelative,
15054
15223
  groupMessages,
15055
15224
  hasModifier,
15225
+ historyUrlAdapter,
15056
15226
  iconForMime,
15057
15227
  importanceLevel,
15058
15228
  importanceTokens,
@@ -15072,6 +15242,7 @@ function useDocumentTitle(routeTitle, appName) {
15072
15242
  preferenceScore,
15073
15243
  preferenceSegments,
15074
15244
  rangeFilter,
15245
+ readQueryFromUrl,
15075
15246
  resolveTheme,
15076
15247
  revokeAttachment,
15077
15248
  savePrefs,
@@ -15095,6 +15266,7 @@ function useDocumentTitle(routeTitle, appName) {
15095
15266
  useToast,
15096
15267
  validateFilterValue,
15097
15268
  visibleMessages,
15098
- watchAppearance
15269
+ watchAppearance,
15270
+ writeQueryToUrl
15099
15271
  });
15100
15272
  //# sourceMappingURL=index.cjs.map