@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.js CHANGED
@@ -14203,6 +14203,75 @@ function BreadcrumbCrumb({
14203
14203
 
14204
14204
  // src/kits/query.ts
14205
14205
  import * as React74 from "react";
14206
+
14207
+ // src/kits/queryUrl.ts
14208
+ var historyUrlAdapter = {
14209
+ read: () => new URLSearchParams(window.location.search),
14210
+ write(next) {
14211
+ const search = next.toString();
14212
+ const url = window.location.pathname + (search ? `?${search}` : "") + window.location.hash;
14213
+ window.history.replaceState(window.history.state, "", url);
14214
+ },
14215
+ subscribe(onChange) {
14216
+ const onPopState = () => onChange(new URLSearchParams(window.location.search));
14217
+ window.addEventListener("popstate", onPopState);
14218
+ return () => window.removeEventListener("popstate", onPopState);
14219
+ }
14220
+ };
14221
+ var names = (scope) => {
14222
+ const prefix = scope ? `${scope}-` : "";
14223
+ return {
14224
+ q: `${prefix}q`,
14225
+ sort: `${prefix}sort`,
14226
+ dir: `${prefix}dir`,
14227
+ page: `${prefix}page`,
14228
+ pageSize: `${prefix}pageSize`,
14229
+ filters: `${prefix}filters`
14230
+ };
14231
+ };
14232
+ var positiveInt = (text) => {
14233
+ const n = Number(text);
14234
+ return text !== null && Number.isInteger(n) && n >= 1 ? n : null;
14235
+ };
14236
+ function parseFilters(text) {
14237
+ if (text === null) return null;
14238
+ try {
14239
+ const parsed = JSON.parse(text);
14240
+ return parsed !== null && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : null;
14241
+ } catch {
14242
+ return null;
14243
+ }
14244
+ }
14245
+ function readQueryFromUrl(params, scope, base, paging) {
14246
+ const n = names(scope);
14247
+ const dir = params.get(n.dir);
14248
+ return {
14249
+ page: paging === "page" ? positiveInt(params.get(n.page)) ?? base.page : base.page,
14250
+ pageSize: positiveInt(params.get(n.pageSize)) ?? base.pageSize,
14251
+ sort: params.get(n.sort) || base.sort,
14252
+ dir: dir === "asc" || dir === "desc" ? dir : base.dir,
14253
+ q: params.get(n.q) ?? base.q,
14254
+ filters: parseFilters(params.get(n.filters)) ?? base.filters
14255
+ };
14256
+ }
14257
+ function writeQueryToUrl(params, scope, query, base, paging) {
14258
+ const n = names(scope);
14259
+ const next = new URLSearchParams(params);
14260
+ const set = (name, value, isDefault) => {
14261
+ if (value === null || isDefault) next.delete(name);
14262
+ else next.set(name, value);
14263
+ };
14264
+ set(n.q, query.q, query.q === base.q);
14265
+ set(n.sort, query.sort, query.sort === base.sort);
14266
+ set(n.dir, query.dir, query.dir === base.dir);
14267
+ set(n.pageSize, String(query.pageSize), query.pageSize === base.pageSize);
14268
+ set(n.page, String(query.page), paging === "keyset" || query.page === base.page);
14269
+ const filters = JSON.stringify(query.filters);
14270
+ set(n.filters, filters, filters === JSON.stringify(base.filters));
14271
+ return next;
14272
+ }
14273
+
14274
+ // src/kits/query.ts
14206
14275
  var isMultiSelectShaped = (v) => !!v && typeof v === "object" && Array.isArray(v.values);
14207
14276
  var isRangeShaped = (v) => !!v && typeof v === "object" && ("min" in v || "max" in v);
14208
14277
  var isDateShaped = (v) => !!v && typeof v === "object" && "kind" in v;
@@ -14256,50 +14325,129 @@ function compare(a, b, dir) {
14256
14325
  }
14257
14326
  var API = null;
14258
14327
  var PREFS = null;
14259
- function useServerTable({ endpoint, params, defaults, deps, prefsKey }) {
14260
- const [query, setQuery] = React74.useState(() => {
14328
+ function loadedFrom(paging, answer) {
14329
+ if (paging === "keyset") {
14330
+ const r2 = answer;
14331
+ return { rows: r2.rows, pager: null, next: r2.next, total: r2.total ?? null, facets: r2.filter?.facets ?? {}, aggregate: r2.aggregate ?? {} };
14332
+ }
14333
+ const r = answer;
14334
+ return { rows: r.rows, pager: r.pager ?? null, next: null, total: null, facets: r.filter?.facets ?? {}, aggregate: r.aggregate ?? {} };
14335
+ }
14336
+ function requestFor(params, query, paging, after) {
14337
+ if (paging === "page") return { ...params || {}, ...query };
14338
+ const { sort, dir, q, filters, pageSize } = query;
14339
+ return { ...params || {}, sort, dir, q, filters, limit: pageSize, ...after === void 0 ? {} : { after } };
14340
+ }
14341
+ var toError = (e) => e instanceof Error ? e : new Error(String(e));
14342
+ function useServerTable({ endpoint, params, defaults, deps, prefsKey, paging = "page", url }) {
14343
+ const [base] = React74.useState(() => {
14261
14344
  const store = PREFS || window.PlannerPrefs;
14262
14345
  const saved = prefsKey && store ? store.getTable(prefsKey) : {};
14263
14346
  return { ...DEFAULTS, ...defaults || {}, ...saved.pageSize ? { pageSize: saved.pageSize } : {}, ...saved.sort ? { sort: saved.sort, dir: saved.dir || "desc" } : {} };
14264
14347
  });
14348
+ const [mountParams] = React74.useState(() => url ? (url.adapter ?? historyUrlAdapter).read() : null);
14349
+ const address = React74.useRef(mountParams);
14350
+ const [query, setQuery] = React74.useState(() => url && mountParams ? readQueryFromUrl(mountParams, url.scope, base, paging) : base);
14265
14351
  const savePref = React74.useCallback((patch2) => {
14266
14352
  const store = PREFS || window.PlannerPrefs;
14267
14353
  if (prefsKey && store) store.setTable(prefsKey, patch2);
14268
14354
  }, [prefsKey]);
14269
14355
  const [res, setRes] = React74.useState(null);
14270
14356
  const [loading, setLoading] = React74.useState(true);
14271
- const seq2 = React74.useRef(0);
14357
+ const [loadingMore, setLoadingMore] = React74.useState(false);
14358
+ const [failure, setFailure] = React74.useState(null);
14359
+ const [reloadToken, setReloadToken] = React74.useState(0);
14360
+ const moreController = React74.useRef(null);
14272
14361
  const depKey = (deps || []).join("|");
14273
14362
  const paramKey = JSON.stringify(params || {});
14274
14363
  const queryKey = JSON.stringify(query);
14364
+ const lifetime = React74.useRef(null);
14365
+ React74.useEffect(() => {
14366
+ const controller = new AbortController();
14367
+ lifetime.current = controller;
14368
+ return () => controller.abort();
14369
+ }, []);
14370
+ const latest = React74.useRef({ res, loading, query, params, endpoint, url, base });
14371
+ React74.useEffect(() => {
14372
+ latest.current = { res, loading, query, params, endpoint, url, base };
14373
+ });
14275
14374
  React74.useEffect(() => {
14276
- const id = ++seq2.current;
14375
+ const controller = new AbortController();
14376
+ const { signal } = controller;
14277
14377
  setLoading(true);
14378
+ setLoadingMore(false);
14379
+ setFailure(null);
14278
14380
  const t = setTimeout(() => {
14279
- QueryKit.getApi().request(endpoint, { ...params || {}, ...query }).then((r) => {
14280
- if (id !== seq2.current) return;
14281
- setRes(r);
14282
- setLoading(false);
14283
- });
14381
+ const { url: u, base: b } = latest.current;
14382
+ if (u && address.current) {
14383
+ const next = writeQueryToUrl(address.current, u.scope, query, b, paging);
14384
+ if (next.toString() !== address.current.toString()) {
14385
+ address.current = next;
14386
+ (u.adapter ?? historyUrlAdapter).write(next);
14387
+ }
14388
+ }
14389
+ QueryKit.getApi().request(endpoint, requestFor(params, query, paging), { signal }).then(
14390
+ (r) => {
14391
+ if (signal.aborted) return;
14392
+ setRes(loadedFrom(paging, r));
14393
+ setLoading(false);
14394
+ },
14395
+ (e) => {
14396
+ if (signal.aborted) return;
14397
+ setFailure({ error: toError(e), during: "query" });
14398
+ setLoading(false);
14399
+ }
14400
+ );
14284
14401
  }, query.q ? 220 : 60);
14285
- return () => clearTimeout(t);
14286
- }, [endpoint, queryKey, paramKey, depKey]);
14402
+ return () => {
14403
+ clearTimeout(t);
14404
+ controller.abort();
14405
+ moreController.current?.abort();
14406
+ moreController.current = null;
14407
+ };
14408
+ }, [endpoint, queryKey, paramKey, depKey, paging, reloadToken]);
14409
+ const adapter = url ? url.adapter ?? historyUrlAdapter : null;
14410
+ React74.useEffect(() => {
14411
+ if (!adapter) return;
14412
+ return adapter.subscribe((params2) => {
14413
+ const { url: u, base: b } = latest.current;
14414
+ if (!u) return;
14415
+ address.current = params2;
14416
+ const fromAddress = readQueryFromUrl(params2, u.scope, b, paging);
14417
+ setQuery((s) => JSON.stringify(s) === JSON.stringify(fromAddress) ? s : fromAddress);
14418
+ });
14419
+ }, [adapter, paging]);
14287
14420
  const patch = (p, resetPage = true) => setQuery((s) => ({ ...s, ...p, page: resetPage ? 1 : p.page != null ? p.page : s.page }));
14288
- const defaultPager = { page: query.page, pageSize: query.pageSize, total: 0, totalPages: 1, from: 0, to: 0, hasPrev: false, hasNext: false };
14289
- const pager = res && res.pager || defaultPager;
14290
- const api = {
14421
+ const loadMore = () => {
14422
+ const { res: loaded, loading: querying, query: q, params: p, endpoint: e } = latest.current;
14423
+ if (!loaded || loaded.next === null || querying || moreController.current !== null) return;
14424
+ const controller = new AbortController();
14425
+ const { signal } = controller;
14426
+ moreController.current = controller;
14427
+ setLoadingMore(true);
14428
+ setFailure(null);
14429
+ QueryKit.getApi().request(e, requestFor(p, q, "keyset", loaded.next), { signal }).then(
14430
+ (r) => {
14431
+ if (signal.aborted) return;
14432
+ moreController.current = null;
14433
+ const more = loadedFrom("keyset", r);
14434
+ setRes((now) => now && { ...more, rows: [...now.rows, ...more.rows], total: more.total ?? now.total });
14435
+ setLoadingMore(false);
14436
+ },
14437
+ (err) => {
14438
+ if (signal.aborted) return;
14439
+ moreController.current = null;
14440
+ setFailure({ error: toError(err), during: "more" });
14441
+ setLoadingMore(false);
14442
+ }
14443
+ );
14444
+ };
14445
+ const shared = {
14291
14446
  rows: res ? res.rows : [],
14292
- /** The response's pager block, verbatim. */
14293
- pager,
14294
- total: pager.total || 0,
14295
- page: pager.page || query.page,
14296
14447
  pageSize: query.pageSize,
14297
- totalPages: pager.totalPages || 1,
14298
- from: pager.from || 0,
14299
- to: pager.to || 0,
14300
- facets: res && res.filter && res.filter.facets || {},
14448
+ facets: res ? res.facets : {},
14301
14449
  /** Server-computed totals over the whole match, never the current page. */
14302
- aggregate: res && res.aggregate || {},
14450
+ aggregate: res ? res.aggregate : {},
14303
14451
  /** The live query object — pass to fetchAll-style calls that need the full match. */
14304
14452
  query,
14305
14453
  sort: query.sort ? { key: query.sort, dir: query.dir } : void 0,
@@ -14308,8 +14456,12 @@ function useServerTable({ endpoint, params, defaults, deps, prefsKey }) {
14308
14456
  loading,
14309
14457
  /** True only on the very first load — later loads keep showing stale rows. */
14310
14458
  initialLoading: loading && !res,
14459
+ error: failure ? failure.error : null,
14460
+ retry: () => {
14461
+ if (failure?.during === "more") loadMore();
14462
+ else setReloadToken((t) => t + 1);
14463
+ },
14311
14464
  activeFilterCount: Object.values(query.filters).filter(isActiveFilterValue).length,
14312
- setPage: (page) => patch({ page }, false),
14313
14465
  setPageSize: (pageSize) => {
14314
14466
  savePref({ pageSize });
14315
14467
  patch({ pageSize });
@@ -14324,8 +14476,6 @@ function useServerTable({ endpoint, params, defaults, deps, prefsKey }) {
14324
14476
  savePref({ sort: key, dir: dir || "desc" });
14325
14477
  setQuery((s) => ({ ...s, page: 1, sort: key, dir: dir || "desc" }));
14326
14478
  },
14327
- /** Same query, unpaged — for CSV export and select-all-matching. */
14328
- fetchAll: () => QueryKit.getApi().request(endpoint, { ...params || {}, ...query, page: 1, pageSize: 1e5 }).then((r) => r.rows),
14329
14479
  /** value may be an updater fn — required for toggles, or two fast clicks
14330
14480
  both read the same stale array and the first one is lost. */
14331
14481
  setFilter: (id, value) => setQuery((s) => ({ ...s, page: 1, filters: { ...s.filters, [id]: typeof value === "function" ? value(s.filters[id]) : value } })),
@@ -14336,14 +14486,30 @@ function useServerTable({ endpoint, params, defaults, deps, prefsKey }) {
14336
14486
  }),
14337
14487
  setFilters: (filters) => patch({ filters }),
14338
14488
  clearFilters: () => patch({ filters: {}, q: "" }),
14339
- refresh: () => setQuery((s) => ({ ...s })),
14489
+ refresh: () => setReloadToken((t) => t + 1),
14490
+ patchRows: (change) => setRes((now) => now && { ...now, rows: change(now.rows) }),
14340
14491
  /* True on a re-query while the PREVIOUS rows are still rendered — sort, filter, page.
14341
14492
  Distinct from `loading` (first load, nothing on screen) because the two need different
14342
14493
  treatments: skeletons versus a stale overlay on real content. */
14343
14494
  refreshing: loading && !!res,
14344
14495
  queryString: QueryKit.toQueryString(query)
14345
14496
  };
14346
- return api;
14497
+ if (paging === "keyset") {
14498
+ return { ...shared, total: res ? res.total : null, hasMore: !!res && res.next !== null, loadingMore, loadMore };
14499
+ }
14500
+ const pager = res && res.pager || { page: query.page, pageSize: query.pageSize, total: 0, totalPages: 1, from: 0, to: 0, hasPrev: false, hasNext: false };
14501
+ return {
14502
+ ...shared,
14503
+ pager,
14504
+ total: pager.total || 0,
14505
+ page: pager.page || query.page,
14506
+ totalPages: pager.totalPages || 1,
14507
+ from: pager.from || 0,
14508
+ to: pager.to || 0,
14509
+ setPage: (page) => patch({ page }, false),
14510
+ /** Same query, unpaged — for CSV export and select-all-matching. */
14511
+ fetchAll: () => QueryKit.getApi().request(endpoint, { ...params || {}, ...query, page: 1, pageSize: 1e5 }, { signal: lifetime.current.signal }).then((r) => r.rows)
14512
+ };
14347
14513
  }
14348
14514
  var QueryKit = {
14349
14515
  DEFAULTS,
@@ -14788,6 +14954,7 @@ export {
14788
14954
  formatRelative,
14789
14955
  groupMessages,
14790
14956
  hasModifier,
14957
+ historyUrlAdapter,
14791
14958
  iconForMime,
14792
14959
  importanceLevel,
14793
14960
  importanceTokens,
@@ -14807,6 +14974,7 @@ export {
14807
14974
  preferenceScore,
14808
14975
  preferenceSegments,
14809
14976
  rangeFilter,
14977
+ readQueryFromUrl,
14810
14978
  resolveTheme,
14811
14979
  revokeAttachment,
14812
14980
  savePrefs,
@@ -14830,6 +14998,7 @@ export {
14830
14998
  useToast,
14831
14999
  validateFilterValue,
14832
15000
  visibleMessages,
14833
- watchAppearance
15001
+ watchAppearance,
15002
+ writeQueryToUrl
14834
15003
  };
14835
15004
  //# sourceMappingURL=index.js.map