@pythia-software/query-table-react 0.9.7 → 0.9.8

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.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { FieldSchema, WhereClause, QueryState, GroupedRow, GroupingModel, GroupingView, GroupKey, SummaryOp, RepresentativeRow, SummaryColumn, SelectColumn, PropertyViewState, SavedPropertyView, PropertyViewStorage, QueryLink, QuerySnapshot, ItemPropertyMode, ItemPageOptions, ItemDetailMode, ItemDetailsState, RelationshipAdapter, RowId, FormulaNode, FormulaValue, FormulaResult, ComputedColumn, FieldDef, ComputedColumnDraft, FormulaPlan, ColumnPreview, SavedQuery, StorageAdapter, AggOp, AggregationClause, AggregationResult, HostQueryState, DistinctValuesResult, FieldStats, OrderByClause, Transport, ComputedExecution, ComputedColumnStore, QueryLinkOptions, ObjectReference, ResolvedObjectReference } from '@pythia-software/query-table-core';
1
+ import { FieldSchema, WhereClause, QueryState, GroupedRow, GroupingModel, GroupingView, GroupKey, SummaryOp, RepresentativeRow, SummaryColumn, SelectColumn, PropertyViewState, SavedPropertyView, PropertyViewStorage, QueryLink, QuerySnapshot, ScopedJsonInspectionState, AttentionRegistry, ItemPropertyMode, ItemPageOptions, ItemDetailMode, ItemDetailsState, RelationshipAdapter, RowId, FormulaNode, FormulaValue, FormulaResult, ComputedColumn, FieldDef, ComputedColumnDraft, FormulaPlan, ColumnPreview, SavedQuery, StorageAdapter, AggOp, AggregationClause, AggregationResult, HostQueryState, DistinctValuesResult, FieldStats, OrderByClause, Transport, ComputedExecution, ComputedColumnStore, QueryLinkOptions, ObjectReference, ResolvedObjectReference } from '@pythia-software/query-table-core';
2
2
 
3
3
  /** Filter controls for either the result query or grouping's source query. */
4
4
  type QueryFilterApi<Row> = Pick<QueryTableApi<Row>, "query" | "setQuery" | "loading" | "addFilter" | "updateFilter" | "removeFilter" | "clearFilters" | "updatePredicate" | "removePredicate" | "negatePredicate" | "reorderFilters" | "mergeFilters" | "filterValues" | "fieldStats">;
@@ -101,6 +101,8 @@ interface ItemDetailRecord<Row> {
101
101
  interface ItemDetailsOptions<Row> {
102
102
  /** Detail catalogue; defaults to the table catalogue. Useful for non-column properties. */
103
103
  schema?: FieldSchema<Row>;
104
+ /** Optional semantic reveal service, shared with host attention navigation. */
105
+ attention?: AttentionRegistry;
104
106
  /** Legacy collection URL parameter. useQueryTable.queryParameter takes precedence. */
105
107
  queryParameter?: string;
106
108
  /** Creator-approved modalities; users can enable/disable these in Select columns. Defaults to all. */
@@ -119,6 +121,14 @@ interface ItemDetailsApi<Row> {
119
121
  /** "group" details describe grouped result rows, identified by their group identity. */
120
122
  subject?: "item" | "group";
121
123
  links?: QueryLinksApi;
124
+ jsonInspections?: readonly ScopedJsonInspectionState[];
125
+ setJsonInspection?: (inspection: ScopedJsonInspectionState) => void;
126
+ attention?: AttentionRegistry;
127
+ preserveFocus?: boolean;
128
+ /** Ephemeral request counter; virtual tables rematerialize an expanded row per reveal. */
129
+ attentionRevision?: number;
130
+ /** Keep the requested virtual detail materialized until its reveal settles. */
131
+ attentionPending?: boolean;
122
132
  schema: FieldSchema<Row>;
123
133
  /** Collection catalogue used to resolve Page template fields. */
124
134
  pageSchema: FieldSchema<Row>;
package/dist/index.js CHANGED
@@ -348,6 +348,7 @@ import { ITEM_DETAILS_COLUMN, itemPageFields } from "@pythia-software/query-tabl
348
348
 
349
349
  // src/useItemDetails.ts
350
350
  import { useCallback as useCallback3, useEffect as useEffect3, useRef as useRef3, useState as useState3 } from "react";
351
+ import { isPropertyFieldLoaded, jsonInspectionScopeKey, MAX_JSON_INSPECTIONS } from "@pythia-software/query-table-core";
351
352
 
352
353
  // src/usePropertyView.ts
353
354
  import { useCallback as useCallback2, useEffect as useEffect2, useMemo as useMemo2, useRef as useRef2, useState as useState2 } from "react";
@@ -646,7 +647,7 @@ function useItemDetails({
646
647
  setResult((previous) => previous.id === void 0 && !previous.loading && !previous.error && !previous.record ? previous : { source: void 0, epoch: "", id: void 0, record: null, loading: false, error: null });
647
648
  return;
648
649
  }
649
- setResult({ source: loadRow, epoch, id: activeId, record: null, loading: true, error: null });
650
+ setResult((previous) => ({ source: loadRow, epoch, id: activeId, record: previous.id === activeId && previous.source === loadRow ? previous.record : null, loading: true, error: null }));
650
651
  void Promise.resolve().then(() => loadRow(activeId, controller.signal)).then((record) => {
651
652
  if (!controller.signal.aborted) setResult({ source: loadRow, epoch, id: activeId, record, loading: false, error: null });
652
653
  }).catch((error) => {
@@ -661,11 +662,66 @@ function useItemDetails({
661
662
  });
662
663
  return () => controller.abort();
663
664
  }, [activeId, loadRow, refreshKey, retryKey]);
665
+ const preserveFocus = useRef3(false);
666
+ const attentionSequence = useRef3(0);
667
+ const [attentionRequest, setAttentionRequest] = useState3({ revision: 0, pending: false });
668
+ const attentionLatest = useRef3({ loadRow, setQuery, modes, views, active, query });
669
+ attentionLatest.current = { loadRow, setQuery, modes, views, active, query };
670
+ useEffect3(() => {
671
+ const registry = config?.attention;
672
+ if (!enabled || !registry) return;
673
+ const provider = { reveal: async (target, signal) => {
674
+ if (target.rowId === void 0 || signal.aborted) return false;
675
+ const current = attentionLatest.current;
676
+ const record = await current.loadRow(target.rowId, signal);
677
+ if (!record || signal.aborted) return false;
678
+ if (target.field !== void 0 && !detailSchema.fields.some((field) => field.name === target.field && field.property?.enabled !== false && isPropertyFieldLoaded(field, record.loadedFields))) return false;
679
+ const mode = current.active?.id === target.rowId ? current.active.mode : current.modes.includes("expand") ? "expand" : current.modes.includes("modal") ? "modal" : null;
680
+ if (!mode || target.field !== void 0 && !current.views[mode].properties.some((property) => property.field === target.field)) return false;
681
+ preserveFocus.current = true;
682
+ const revision = ++attentionSequence.current;
683
+ setAttentionRequest({ revision, pending: true });
684
+ try {
685
+ current.setQuery((previous) => ({ ...previous, itemDetails: {
686
+ ...previous.itemDetails,
687
+ modes: previous.itemDetails?.modes ?? current.modes,
688
+ views: current.views,
689
+ active: { id: target.rowId, mode }
690
+ } }));
691
+ const mounted = await registry.whenRegistered({ dataset: detailSchema.name, rowId: target.rowId }, signal);
692
+ if (mounted.ready) await mounted.ready(signal);
693
+ return signal.aborted ? false : await mounted.reveal(target, signal);
694
+ } finally {
695
+ setAttentionRequest((previous) => previous.revision === revision ? { revision, pending: false } : previous);
696
+ }
697
+ } };
698
+ return registry.register({ dataset: detailSchema.name }, provider);
699
+ }, [enabled, config?.attention, detailSchema]);
664
700
  if (!enabled) return;
665
701
  const looked = lookupRow && active ? lookupRow(active.id) : null;
666
702
  return {
667
703
  ...subject ? { subject } : {},
668
704
  ...links ? { links } : {},
705
+ ...config?.attention ? { attention: config.attention } : {},
706
+ preserveFocus: preserveFocus.current,
707
+ attentionRevision: attentionRequest.revision,
708
+ attentionPending: attentionRequest.pending,
709
+ jsonInspections: query.itemDetails?.jsonInspections ?? [],
710
+ setJsonInspection(entry) {
711
+ if (entry.dataset !== detailSchema.name) return;
712
+ setQuery((current) => {
713
+ const inspections = (current.itemDetails?.jsonInspections ?? []).filter((previous) => jsonInspectionScopeKey(previous) !== jsonInspectionScopeKey(entry));
714
+ while (inspections.length >= MAX_JSON_INSPECTIONS) {
715
+ const closed = inspections.findIndex((previous) => !previous.state.open);
716
+ inspections.splice(closed < 0 ? 0 : closed, 1);
717
+ }
718
+ return { ...current, itemDetails: {
719
+ ...current.itemDetails,
720
+ modes: current.itemDetails?.modes ?? modes,
721
+ jsonInspections: [...inspections, entry]
722
+ } };
723
+ });
724
+ },
669
725
  schema: detailSchema,
670
726
  queryParameter: queryParameter2 ?? config?.queryParameter ?? "q",
671
727
  properties: modalProperties,
@@ -675,7 +731,7 @@ function useItemDetails({
675
731
  active,
676
732
  page,
677
733
  pageSchema: schema,
678
- record: lookupRow ? looked ?? null : currentResult ? result.record : null,
734
+ record: lookupRow ? looked ?? null : result.id === activeId && result.source === loadRow ? result.record : null,
679
735
  loading: lookupRow ? looked === void 0 : activeId !== void 0 && (!currentResult || result.loading),
680
736
  error: lookupRow ? null : currentResult ? result.error : null,
681
737
  ...config?.relationships ? { relationships: config.relationships } : {},
@@ -690,6 +746,8 @@ function useItemDetails({
690
746
  });
691
747
  },
692
748
  open(id, mode) {
749
+ config?.attention?.cancel();
750
+ preserveFocus.current = false;
693
751
  if (!modes.includes(mode)) return;
694
752
  setQuery((current) => ({ ...current, itemDetails: {
695
753
  ...current.itemDetails,
@@ -699,6 +757,7 @@ function useItemDetails({
699
757
  } }));
700
758
  },
701
759
  close() {
760
+ config?.attention?.cancel();
702
761
  setQuery((current) => {
703
762
  if (!current.itemDetails?.active) return current;
704
763
  const { active: _active, ...state } = current.itemDetails;