@aglyn/shared-ui-jsx 1.0.0-beta.198 → 1.0.0-beta.200
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/package.json +5 -5
- package/src/lib/components/list-filter-chips.component.d.ts +2 -2
- package/src/lib/components/list-filter-chips.component.js +6 -28
- package/src/lib/components/list-filter-chips.component.js.map +1 -1
- package/src/lib/components/list-filter-toolbar.component.d.ts +56 -0
- package/src/lib/components/list-filter-toolbar.component.js +280 -0
- package/src/lib/components/list-filter-toolbar.component.js.map +1 -0
- package/src/lib/components/list-query-notices.component.d.ts +61 -0
- package/src/lib/components/list-query-notices.component.js +61 -0
- package/src/lib/components/list-query-notices.component.js.map +1 -0
- package/src/lib/components/list-table.component.d.ts +16 -0
- package/src/lib/components/list-table.component.js +57 -8
- package/src/lib/components/list-table.component.js.map +1 -1
- package/src/lib/const/list-filter-sentence.d.ts +39 -0
- package/src/lib/const/list-filter-sentence.js +107 -0
- package/src/lib/const/list-filter-sentence.js.map +1 -0
- package/src/lib/const/list-filter.d.ts +14 -46
- package/src/lib/const/list-filter.js +54 -113
- package/src/lib/const/list-filter.js.map +1 -1
- package/src/lib/const/list-grid-filter.d.ts +38 -21
- package/src/lib/const/list-grid-filter.js +59 -65
- package/src/lib/const/list-grid-filter.js.map +1 -1
- package/src/lib/const/list-query-plan.d.ts +170 -0
- package/src/lib/const/list-query-plan.js +624 -0
- package/src/lib/const/list-query-plan.js.map +1 -0
- package/src/lib/hooks/use-list-grid-filter.d.ts +7 -11
- package/src/lib/hooks/use-list-grid-filter.js +11 -12
- package/src/lib/hooks/use-list-grid-filter.js.map +1 -1
- package/src/lib/hooks/use-list-rows-filter.d.ts +0 -75
- package/src/lib/hooks/use-list-rows-filter.js +0 -83
- package/src/lib/hooks/use-list-rows-filter.js.map +0 -1
- package/src/lib/hooks/use-paged-rows-filter.d.ts +0 -77
- package/src/lib/hooks/use-paged-rows-filter.js +0 -126
- package/src/lib/hooks/use-paged-rows-filter.js.map +0 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../../../libs/shared/ui/jsx/src/lib/const/list-query-plan.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n type ListFilterField,\n type ListFilterRequest,\n listFilterDay,\n listFilterOperators,\n} from './list-filter'\n\n/*\n * EVERY CLAUSE ON THE QUERY (AGL-3321).\n *\n * The AGL-2501 contract turned ONE filter into a Firestore query — the list\n * put the first servable clause on its query and matched the rest over the\n * rows it had loaded, and search the same way. A clause matched over a loaded\n * page answers \"no match\" for a record on the next one, which is the one\n * answer a list must never give wrongly.\n *\n * This composes them all. A list declares its fields (the same\n * `ListFilterField` grammar), the sorts it offers and the token array its\n * quick search reads; `planListQuery` turns the clauses in force and the\n * search words into ONE query — every predicate, one order — and says which\n * it could not put there and why. Nothing is matched in memory afterwards:\n * a clause the plan refuses is not applied, and the list says so, rather\n * than being applied to some rows and not others.\n *\n * The plan is SDK-free and model-free: the text normalizers the written\n * search fields were stamped with come in as `ListQueryNormalizers` — the\n * platform's are `nameSearchNormalizers` (`@aglyn/aglyn/app-utils/name-search`)\n * — so the query asks for exactly the keys the writers stored. The web twin (`listQueryConstraints` in the instance\n * library) and the Admin twin (`applyListQuery` in the console) turn it into\n * their SDK's query, so the two cannot disagree about what a clause means.\n *\n * ## What one Firestore query can hold\n *\n * - any number of equalities (`==`, `in`), composed through INDEX MERGING:\n * one `(field, sort)` composite per equality field serves every\n * combination of them, measured against production (AGL-3321);\n * - ONE array clause — `array-contains` or `array-contains-any` — which a\n * search token, a `contains` filter and a scope clause all compete for;\n * - ONE inequality field (a range, `!=`, a prefix range), which must then\n * lead the order — so a range orders the list by the field it ranges over;\n * - at most thirty disjunctions, which an `in` and an\n * `array-contains-any` multiply.\n *\n * The declaration's `listQueryIndexes` enumerates the composites those\n * shapes need, and each list's spec holds the index file to it.\n */\n\n/**\n * How the written search fields were normalized, so a typed value becomes the\n * key the query can match: the lower-cased key (`equals`, `startsWith`), the\n * one word-prefix token (`contains`, search), and the reversed key\n * (`endsWith`). `maxPrefix` is the longest token a writer stores.\n */\nexport interface ListQueryNormalizers {\n key(value: string): string\n token(value: string): string\n reversed(value: string): string\n maxPrefix: number\n}\n\n/** A predicate on the query, SDK-free. `path` `__name__` is the document id. */\nexport type ListQueryOp =\n | '=='\n | '!='\n | '<'\n | '<='\n | '>'\n | '>='\n | 'in'\n | 'array-contains'\n | 'array-contains-any'\n\nexport type ListQueryValue = string | number | boolean | null | Date | readonly (string | number)[]\n\nexport interface ListQueryFilter {\n path: string\n op: ListQueryOp\n value: ListQueryValue\n}\n\nexport interface ListQuerySort {\n path: string\n direction: 'asc' | 'desc'\n /** The grid column this order sorts, when the list lets the grid sort by it. */\n column?: string\n}\n\n/** The document id, as a path. */\nexport const LIST_QUERY_ID_PATH = '__name__'\n\n/** Firestore's cap on disjunctions — `in` values times `array-contains-any` values. */\nexport const LIST_QUERY_DISJUNCTIONS = 30\n\n/** A very high private-use codepoint: `[p, p + HIGH]` spans every string starting `p`. */\nconst HIGH = '\\uf8ff'\n\nexport interface ListQueryDeclaration {\n /** Every field the Filters panel may name. */\n fields: readonly ListFilterField[]\n /**\n * The orders the list offers, its default FIRST. The query takes no other:\n * each is a composite the index file carries for every filterable field.\n */\n sorts: readonly ListQuerySort[]\n /**\n * The quick search: the token array every write stamps (see\n * `nameSearchTokens`), queried with `array-contains` on one typed word.\n * Absent, the list offers no search box.\n */\n search?: {\n tokensPath: string\n /**\n * Under a scope clause the search cannot be a SECOND array clause, so a\n * scoped list writes `<scopeToken><join><prefix>` tokens and the query\n * asks `array-contains-any` over the scope's tokens joined to the word —\n * one array clause that answers both.\n *\n * ⛔ Rules that grant a list from `visibleTo.hasAny(...)` DENY this\n * query, because it no longer constrains `visibleTo`. Declare it only for\n * readers whose rule needs no `visibleTo` term, or for an Admin-SDK read;\n * see `SCOPED_SEARCH_JOIN` in `@aglyn/aglyn/app-utils/name-search`.\n */\n scoped?: { tokensPath: string; join: string }\n }\n /**\n * Keys a presence-map field (`keysOf`) is filtered by, for the index\n * enumeration: `sources.form`, `sources.order`, …\n */\n keys?: Readonly<Record<string, readonly string[]>>\n}\n\n/** What the list asks this time. */\nexport interface ListQueryRequest {\n clauses: readonly ListFilterRequest[]\n /** The quick search's words. */\n search?: readonly string[]\n /** The order asked for; a sort the declaration does not offer is ignored. */\n sort?: ListQuerySort | null\n /**\n * Predicates the list always applies — its scope — which count against\n * the same limits. An `array-contains-any` scope is what a scoped search\n * folds into (`declaration.search.scoped`).\n */\n base?: readonly ListQueryFilter[]\n}\n\nexport interface ListQueryRefusal {\n /** The clause refused, or `search`. */\n clause: ListFilterRequest | 'search'\n reason: string\n}\n\nexport interface ListQueryPlan {\n /** Every predicate, base included, in the order the SDK should add them. */\n filters: ListQueryFilter[]\n /** The one order: the range field's when there is one, else the sort. */\n orderBy: ListQuerySort\n /** The clauses on the query. */\n served: ListFilterRequest[]\n /** The search word on the query, or null. */\n searched: string | null\n /** What was asked and could not be put on the query, with why. */\n refused: ListQueryRefusal[]\n /** Said to the reader about what WAS served — e.g. one search word of two. */\n notices: string[]\n}\n\n/** How one clause lands on a query, before it is composed with the others. */\ninterface ClauseShape {\n filters: ListQueryFilter[]\n /** The inequality field the clause needs to lead the order, if any. */\n inequality?: string\n /** The clause is the query's array clause. */\n array?: boolean\n /** `in` values, for the disjunction budget. */\n disjunctions?: number\n}\n\nconst csv = (raw: string): string[] =>\n raw\n .split(',')\n .map((entry) => entry.trim())\n .filter(Boolean)\n\nconst dayBounds = (raw: string): { start: Date; end: Date } | null => {\n const start = listFilterDay(raw)\n if (Number.isNaN(start.getTime())) return null\n const day = new Date(start)\n day.setHours(0, 0, 0, 0)\n const end = new Date(day)\n end.setDate(end.getDate() + 1)\n return { start: day, end }\n}\n\n/** The order a range over `path` puts the list in. */\nconst rangeOrder = (\n declaration: ListQueryDeclaration,\n field: ListFilterField | undefined,\n path: string,\n): ListQuerySort => {\n const offered = declaration.sorts.find((sort) => sort.path === path)\n if (offered) return offered\n return { path, direction: field?.kind === 'date' ? 'desc' : 'asc' }\n}\n\n/**\n * One clause as predicates, or the reason it has none. The same operator\n * meanings as the AGL-2501 twins (`applyListFilter`, `listFilterConstraints`),\n * which this replaces for a list that composes clauses.\n */\nfunction shapeClause(\n field: ListFilterField,\n input: ListFilterRequest,\n names: ListQueryNormalizers,\n): ClauseShape | string {\n if (field.windowOnly) return 'this field is not stored where a query can reach it'\n if (!listFilterOperators(field).includes(input.op)) {\n return `${input.op} is not something this list can ask of ${field.column}`\n }\n const raw = (input.value ?? '').trim()\n const op = input.op\n\n if (op === 'isEmpty') {\n if (field.presence !== 'nullable') return 'only a field stored as null can be asked for empty'\n return { filters: [{ path: field.path, op: '==', value: null }] }\n }\n if (op === 'isNotEmpty') {\n if (field.presence === 'always') return 'this field is never empty'\n return { filters: [{ path: field.path, op: '!=', value: null }], inequality: field.path }\n }\n if (field.kind === 'boolean') {\n if (raw !== 'true' && raw !== 'false') return 'pick true or false'\n return { filters: [{ path: field.path, op: '==', value: raw === 'true' }] }\n }\n if (!raw) return 'no value yet'\n\n if (field.keysOf) {\n if (op !== 'equals' || !/^[A-Za-z0-9_]+$/.test(raw)) return 'pick one of the choices'\n return { filters: [{ path: `${field.path}.${raw}`, op: '==', value: true }] }\n }\n\n if (field.kind === 'text') {\n if (op === 'contains' && field.tokensPath) {\n const token = field.verbatimTokens ? raw : names.token(raw)\n if (!token) return 'no value yet'\n return {\n filters: [{ path: field.tokensPath, op: 'array-contains', value: token }],\n array: true,\n }\n }\n if (op === 'equals' && field.lowerPath) {\n return { filters: [{ path: field.lowerPath, op: '==', value: names.key(raw) }] }\n }\n if (op === 'equals' && field.presence === 'always' && !field.lowerPath) {\n return { filters: [{ path: field.path, op: '==', value: raw }] }\n }\n if (op === 'startsWith' && field.lowerPath) {\n const key = names.key(raw)\n return {\n filters: [\n { path: field.lowerPath, op: '>=', value: key },\n { path: field.lowerPath, op: '<=', value: `${key}${HIGH}` },\n ],\n inequality: field.lowerPath,\n }\n }\n if (op === 'endsWith' && field.reversedPath) {\n const key = names.reversed(raw)\n return {\n filters: [\n { path: field.reversedPath, op: '>=', value: key },\n { path: field.reversedPath, op: '<=', value: `${key}${HIGH}` },\n ],\n inequality: field.reversedPath,\n }\n }\n if (op === 'isAnyOf' && field.lowerPath) {\n const values = csv(raw).map((value) => names.key(value))\n if (values.length > LIST_QUERY_DISJUNCTIONS) return `at most ${LIST_QUERY_DISJUNCTIONS} values`\n return {\n filters: [{ path: field.lowerPath, op: 'in', value: values }],\n disjunctions: values.length,\n }\n }\n return `${op} is not something this list can ask of ${field.column}`\n }\n\n if (field.kind === 'id') {\n if (op === 'equals') return { filters: [{ path: LIST_QUERY_ID_PATH, op: '==', value: raw }] }\n if (op === 'isAnyOf') {\n const values = csv(raw)\n if (values.length > LIST_QUERY_DISJUNCTIONS) return `at most ${LIST_QUERY_DISJUNCTIONS} values`\n return {\n filters: [{ path: LIST_QUERY_ID_PATH, op: 'in', value: values }],\n disjunctions: values.length,\n }\n }\n if (op === 'startsWith') {\n return {\n filters: [\n { path: LIST_QUERY_ID_PATH, op: '>=', value: raw },\n { path: LIST_QUERY_ID_PATH, op: '<=', value: `${raw}${HIGH}` },\n ],\n inequality: LIST_QUERY_ID_PATH,\n }\n }\n return `${op} is not something this list can ask of ${field.column}`\n }\n\n if (field.kind === 'exact') {\n if (op === 'equals') return { filters: [{ path: field.path, op: '==', value: raw }] }\n if (op === 'isAnyOf') {\n const values = csv(raw)\n if (values.length > LIST_QUERY_DISJUNCTIONS) return `at most ${LIST_QUERY_DISJUNCTIONS} values`\n if (field.tokensPath) {\n // An array field asked \"any of these\": one array clause.\n return {\n filters: [{ path: field.tokensPath, op: 'array-contains-any', value: values }],\n array: true,\n disjunctions: values.length,\n }\n }\n return {\n filters: [{ path: field.path, op: 'in', value: values }],\n disjunctions: values.length,\n }\n }\n if (op === 'contains' && field.tokensPath) {\n return {\n filters: [{ path: field.tokensPath, op: 'array-contains', value: raw }],\n array: true,\n }\n }\n return `${op} is not something this list can ask of ${field.column}`\n }\n\n if (field.kind === 'number') {\n const value = Number(raw)\n if (!Number.isFinite(value)) return 'type a number'\n if (op === '=') return { filters: [{ path: field.path, op: '==', value }] }\n const comparison: Record<string, ListQueryOp> = {\n '!=': '!=',\n '>': '>',\n '>=': '>=',\n '<': '<',\n '<=': '<=',\n }\n const found = comparison[op]\n if (!found) return `${op} is not something this list can ask of ${field.column}`\n return { filters: [{ path: field.path, op: found, value }], inequality: field.path }\n }\n\n if (field.kind === 'date') {\n const day = dayBounds(raw)\n if (!day) return 'pick a date'\n // A `…AtMs` field is compared as the number it is stored as.\n const at = (date: Date): Date | number =>\n field.storedAs === 'millis' ? date.getTime() : date\n if (op === 'is') {\n return {\n filters: [\n { path: field.path, op: '>=', value: at(day.start) },\n { path: field.path, op: '<', value: at(day.end) },\n ],\n inequality: field.path,\n }\n }\n const bound: Record<string, [ListQueryOp, Date]> = {\n after: ['>=', day.end],\n onOrAfter: ['>=', day.start],\n before: ['<', day.start],\n onOrBefore: ['<', day.end],\n }\n const found = bound[op]\n if (!found) return `${op} is not something this list can ask of ${field.column}`\n return { filters: [{ path: field.path, op: found[0], value: at(found[1]) }], inequality: field.path }\n }\n\n return `${op} is not something this list can ask of ${field.column}`\n}\n\nconst disjunctionsOf = (filter: ListQueryFilter): number =>\n filter.op === 'in' || filter.op === 'array-contains-any'\n ? Math.max(1, Array.isArray(filter.value) ? filter.value.length : 1)\n : 1\n\n/**\n * Every clause and the search, as ONE query — or as much of it as one query\n * can hold, with the rest refused by name.\n *\n * Order of claim: the base (the list's scope) first, then the search, then\n * the clauses in the order they were added. A clause that would be a second\n * array clause, a second inequality field or push the disjunctions past\n * thirty is refused, and the list shows the refusal beside its chip; the\n * clauses before it stand. The search is claimed before the clauses because\n * it is the thing a reader typed last and looks at.\n */\nexport function planListQuery(\n declaration: ListQueryDeclaration,\n request: ListQueryRequest,\n names: ListQueryNormalizers,\n): ListQueryPlan {\n const filters: ListQueryFilter[] = [...(request.base ?? [])]\n const served: ListFilterRequest[] = []\n const refused: ListQueryRefusal[] = []\n const notices: string[] = []\n let arrayTaken = filters.some(\n (filter) => filter.op === 'array-contains' || filter.op === 'array-contains-any',\n )\n let inequality: string | null =\n filters.find((filter) => ['!=', '<', '<=', '>', '>='].includes(filter.op))?.path ?? null\n let disjunctions = filters.reduce((product, filter) => product * disjunctionsOf(filter), 1)\n\n // The search.\n let searched: string | null = null\n const words = (request.search ?? []).map((word) => word.trim()).filter(Boolean)\n if (words.length) {\n const search = declaration.search\n const token = names.token(words.join(' '))\n if (!search) {\n refused.push({ clause: 'search', reason: 'this list has no search' })\n } else if (!token) {\n // Nothing searchable was typed.\n } else {\n const scopeAt = filters.findIndex(\n (filter) => filter.op === 'array-contains-any' || filter.op === 'array-contains',\n )\n if (scopeAt === -1) {\n filters.push({ path: search.tokensPath, op: 'array-contains', value: token })\n arrayTaken = true\n searched = token\n } else if (search.scoped) {\n // Fold the search into the scope: one array clause answers both.\n const scope = filters[scopeAt]\n const scopes = Array.isArray(scope.value) ? scope.value : [scope.value]\n filters.splice(scopeAt, 1, {\n path: search.scoped.tokensPath,\n op: 'array-contains-any',\n value: scopes.map((entry) => `${String(entry)}${search.scoped?.join}${token}`),\n })\n searched = token\n } else {\n refused.push({\n clause: 'search',\n reason: 'this list is already narrowed to what you can see, which search cannot combine with',\n })\n }\n if (searched && words.length > 1) {\n notices.push(\n `Search matches one word at a time: showing results for \"${token}\".`,\n )\n }\n if (searched && names.key(words[0]).length > names.maxPrefix) {\n notices.push(`Search reads the first ${names.maxPrefix} letters of a word.`)\n }\n }\n }\n\n // The clauses.\n for (const clause of request.clauses) {\n const field = declaration.fields.find((entry) => entry.column === clause.field)\n if (!field) {\n refused.push({ clause, reason: 'this list does not filter by that' })\n continue\n }\n const shape = shapeClause(field, clause, names)\n if (typeof shape === 'string') {\n refused.push({ clause, reason: shape })\n continue\n }\n if (shape.array && arrayTaken) {\n refused.push({\n clause,\n reason: searched\n ? 'cannot be combined with the search — clear the search to use it'\n : 'cannot be combined with another \"contains\" or \"any of\" filter on a list',\n })\n continue\n }\n if (shape.inequality && inequality && shape.inequality !== inequality) {\n refused.push({\n clause,\n reason: 'only one range (dates, numbers, starts with) can apply at a time',\n })\n continue\n }\n const product =\n disjunctions * shape.filters.reduce((total, filter) => total * disjunctionsOf(filter), 1)\n if (product > LIST_QUERY_DISJUNCTIONS) {\n refused.push({ clause, reason: `too many values at once (the limit is ${LIST_QUERY_DISJUNCTIONS})` })\n continue\n }\n filters.push(...shape.filters)\n served.push(clause)\n if (shape.array) arrayTaken = true\n if (shape.inequality) inequality = shape.inequality\n disjunctions = product\n }\n\n // The order.\n const asked = request.sort\n ? declaration.sorts.find(\n (sort) => sort.path === request.sort?.path && sort.direction === request.sort?.direction,\n )\n : undefined\n const orderBy = inequality\n ? rangeOrder(\n declaration,\n declaration.fields.find(\n (field) =>\n field.path === inequality ||\n field.lowerPath === inequality ||\n field.reversedPath === inequality,\n ),\n inequality,\n )\n : (asked ?? declaration.sorts[0] ?? { path: LIST_QUERY_ID_PATH, direction: 'asc' })\n\n return { filters, orderBy, served, searched, refused, notices }\n}\n\n/** A composite the plan's shapes need, in the index file's own terms. */\nexport interface ListQueryIndex {\n fields: Array<{ fieldPath: string; order?: 'ASCENDING' | 'DESCENDING'; arrayConfig?: 'CONTAINS' }>\n}\n\nconst ORDER = { asc: 'ASCENDING', desc: 'DESCENDING' } as const\n\n/**\n * Every composite a declaration's queries can need, under index merging:\n * one `(predicate field, order)` index per equality or array field and per\n * order the list can take — its sorts, and the order each range imposes.\n *\n * `base` names the scope predicates the list always applies (their paths\n * and whether they are array clauses), which need their own index per order\n * like any other equality. Pinned by each list's spec against\n * `cloud/firebase-firestore.indexes.json`.\n */\nexport function listQueryIndexes(\n declaration: ListQueryDeclaration,\n base: readonly { path: string; array?: boolean }[] = [],\n): ListQueryIndex[] {\n const equalities = new Map<string, boolean>() // path → is array clause\n const orders: ListQuerySort[] = [...declaration.sorts]\n const addOrder = (sort: ListQuerySort) => {\n if (!orders.some((entry) => entry.path === sort.path && entry.direction === sort.direction)) {\n orders.push(sort)\n }\n }\n for (const entry of base) equalities.set(entry.path, Boolean(entry.array))\n if (declaration.search) {\n equalities.set(declaration.search.tokensPath, true)\n if (declaration.search.scoped) equalities.set(declaration.search.scoped.tokensPath, true)\n }\n for (const field of declaration.fields) {\n if (field.windowOnly) continue\n const ops = listFilterOperators(field)\n const has = (...names: string[]) => names.some((name) => ops.includes(name))\n if (field.keysOf) {\n for (const key of declaration.keys?.[field.column] ?? []) {\n equalities.set(`${field.path}.${key}`, false)\n }\n continue\n }\n if (field.kind === 'text') {\n if (has('contains') && field.tokensPath) equalities.set(field.tokensPath, true)\n if (has('equals', 'isAnyOf') && field.lowerPath) equalities.set(field.lowerPath, false)\n if (has('equals') && !field.lowerPath && field.presence === 'always') equalities.set(field.path, false)\n if (has('startsWith') && field.lowerPath) addOrder(rangeOrder(declaration, field, field.lowerPath))\n if (has('endsWith') && field.reversedPath) addOrder(rangeOrder(declaration, field, field.reversedPath))\n } else if (field.kind === 'exact') {\n if (has('equals', 'isAnyOf')) equalities.set(field.path, false)\n if (has('contains', 'isAnyOf') && field.tokensPath) equalities.set(field.tokensPath, true)\n } else if (field.kind === 'boolean') {\n equalities.set(field.path, false)\n } else if (field.kind === 'number') {\n if (has('=')) equalities.set(field.path, false)\n if (has('!=', '>', '>=', '<', '<=')) addOrder(rangeOrder(declaration, field, field.path))\n } else if (field.kind === 'date') {\n if (has('is', 'after', 'onOrAfter', 'before', 'onOrBefore')) {\n addOrder(rangeOrder(declaration, field, field.path))\n }\n }\n if (has('isEmpty')) equalities.set(field.path, false)\n if (has('isNotEmpty')) addOrder(rangeOrder(declaration, field, field.path))\n }\n const indexes: ListQueryIndex[] = []\n const seen = new Set<string>()\n for (const [path, array] of equalities) {\n for (const order of orders) {\n if (order.path === path || order.path === LIST_QUERY_ID_PATH) continue\n const index: ListQueryIndex = {\n fields: [\n array ? { fieldPath: path, arrayConfig: 'CONTAINS' } : { fieldPath: path, order: 'ASCENDING' },\n { fieldPath: order.path, order: ORDER[order.direction] },\n ],\n }\n const key = JSON.stringify(index)\n if (!seen.has(key)) {\n seen.add(key)\n indexes.push(index)\n }\n }\n }\n return indexes\n}\n\n/** An index entry as `cloud/firebase-firestore.indexes.json` stores it. */\ninterface IndexFileEntry {\n collectionGroup: string\n queryScope: string\n fields: Array<{ fieldPath: string; order?: string; arrayConfig?: string }>\n}\n\n/**\n * The composites a declaration needs that an index file does not hold — what\n * a list's spec asserts is empty, so a declared filter cannot ship without\n * the index that serves it.\n */\nexport function missingListQueryIndexes(\n file: { indexes: readonly IndexFileEntry[] },\n collectionGroup: string,\n needed: readonly ListQueryIndex[],\n queryScope: 'COLLECTION' | 'COLLECTION_GROUP' = 'COLLECTION',\n): ListQueryIndex[] {\n const shape = (fields: IndexFileEntry['fields']) =>\n fields\n .filter((field) => field.fieldPath !== LIST_QUERY_ID_PATH)\n .map((field) => `${field.fieldPath}:${field.order ?? field.arrayConfig}`)\n .join(',')\n const present = new Set(\n file.indexes\n .filter((index) => index.collectionGroup === collectionGroup && index.queryScope === queryScope)\n .map((index) => shape(index.fields)),\n )\n return needed.filter((index) => !present.has(shape(index.fields)))\n}\n"],"names":["listFilterDay","listFilterOperators","LIST_QUERY_ID_PATH","LIST_QUERY_DISJUNCTIONS","HIGH","csv","raw","split","map","entry","trim","filter","Boolean","dayBounds","start","Number","isNaN","getTime","day","Date","setHours","end","setDate","getDate","rangeOrder","declaration","field","path","offered","sorts","find","sort","direction","kind","shapeClause","input","names","windowOnly","includes","op","column","value","presence","filters","inequality","keysOf","test","tokensPath","token","verbatimTokens","array","lowerPath","key","reversedPath","reversed","values","length","disjunctions","isFinite","comparison","found","at","date","storedAs","bound","after","onOrAfter","before","onOrBefore","disjunctionsOf","Math","max","Array","isArray","planListQuery","request","asked","base","served","refused","notices","arrayTaken","some","reduce","product","searched","words","search","word","join","push","clause","reason","scopeAt","findIndex","scoped","scope","scopes","splice","String","maxPrefix","clauses","fields","shape","total","undefined","orderBy","ORDER","asc","desc","listQueryIndexes","equalities","Map","orders","addOrder","set","ops","has","name","keys","indexes","seen","Set","order","index","fieldPath","arrayConfig","JSON","stringify","add","missingListQueryIndexes","file","collectionGroup","needed","queryScope","present"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED,SAGEA,aAAa,EACbC,mBAAmB,QACd,mBAAe;AAkFtB,gCAAgC,GAChC,OAAO,MAAMC,qBAAqB,WAAU;AAE5C,qFAAqF,GACrF,OAAO,MAAMC,0BAA0B,GAAE;AAEzC,wFAAwF,GACxF,MAAMC,OAAO;AAoFb,MAAMC,MAAM,CAACC,MACXA,IACGC,KAAK,CAAC,KACNC,GAAG,CAAC,CAACC,QAAUA,MAAMC,IAAI,IACzBC,MAAM,CAACC;AAEZ,MAAMC,YAAY,CAACP;IACjB,MAAMQ,QAAQd,cAAcM;IAC5B,IAAIS,OAAOC,KAAK,CAACF,MAAMG,OAAO,KAAK,OAAO;IAC1C,MAAMC,MAAM,IAAIC,KAAKL;IACrBI,IAAIE,QAAQ,CAAC,GAAG,GAAG,GAAG;IACtB,MAAMC,MAAM,IAAIF,KAAKD;IACrBG,IAAIC,OAAO,CAACD,IAAIE,OAAO,KAAK;IAC5B,OAAO;QAAET,OAAOI;QAAKG;IAAI;AAC3B;AAEA,oDAAoD,GACpD,MAAMG,aAAa,CACjBC,aACAC,OACAC;IAEA,MAAMC,UAAUH,YAAYI,KAAK,CAACC,IAAI,CAAC,CAACC,OAASA,KAAKJ,IAAI,KAAKA;IAC/D,IAAIC,SAAS,OAAOA;IACpB,OAAO;QAAED;QAAMK,WAAWN,CAAAA,yBAAAA,MAAOO,IAAI,MAAK,SAAS,SAAS;IAAM;AACpE;AAEA;;;;CAIC,GACD,SAASC,YACPR,KAAsB,EACtBS,KAAwB,EACxBC,KAA2B;QAMdD;IAJb,IAAIT,MAAMW,UAAU,EAAE,OAAO;IAC7B,IAAI,CAACpC,oBAAoByB,OAAOY,QAAQ,CAACH,MAAMI,EAAE,GAAG;QAClD,OAAO,GAAGJ,MAAMI,EAAE,CAAC,uCAAuC,EAAEb,MAAMc,MAAM,EAAE;IAC5E;IACA,MAAMlC,MAAM,EAAC6B,eAAAA,MAAMM,KAAK,YAAXN,eAAe,IAAIzB,IAAI;IACpC,MAAM6B,KAAKJ,MAAMI,EAAE;IAEnB,IAAIA,OAAO,WAAW;QACpB,IAAIb,MAAMgB,QAAQ,KAAK,YAAY,OAAO;QAC1C,OAAO;YAAEC,SAAS;gBAAC;oBAAEhB,MAAMD,MAAMC,IAAI;oBAAEY,IAAI;oBAAME,OAAO;gBAAK;aAAE;QAAC;IAClE;IACA,IAAIF,OAAO,cAAc;QACvB,IAAIb,MAAMgB,QAAQ,KAAK,UAAU,OAAO;QACxC,OAAO;YAAEC,SAAS;gBAAC;oBAAEhB,MAAMD,MAAMC,IAAI;oBAAEY,IAAI;oBAAME,OAAO;gBAAK;aAAE;YAAEG,YAAYlB,MAAMC,IAAI;QAAC;IAC1F;IACA,IAAID,MAAMO,IAAI,KAAK,WAAW;QAC5B,IAAI3B,QAAQ,UAAUA,QAAQ,SAAS,OAAO;QAC9C,OAAO;YAAEqC,SAAS;gBAAC;oBAAEhB,MAAMD,MAAMC,IAAI;oBAAEY,IAAI;oBAAME,OAAOnC,QAAQ;gBAAO;aAAE;QAAC;IAC5E;IACA,IAAI,CAACA,KAAK,OAAO;IAEjB,IAAIoB,MAAMmB,MAAM,EAAE;QAChB,IAAIN,OAAO,YAAY,CAAC,kBAAkBO,IAAI,CAACxC,MAAM,OAAO;QAC5D,OAAO;YAAEqC,SAAS;gBAAC;oBAAEhB,MAAM,GAAGD,MAAMC,IAAI,CAAC,CAAC,EAAErB,KAAK;oBAAEiC,IAAI;oBAAME,OAAO;gBAAK;aAAE;QAAC;IAC9E;IAEA,IAAIf,MAAMO,IAAI,KAAK,QAAQ;QACzB,IAAIM,OAAO,cAAcb,MAAMqB,UAAU,EAAE;YACzC,MAAMC,QAAQtB,MAAMuB,cAAc,GAAG3C,MAAM8B,MAAMY,KAAK,CAAC1C;YACvD,IAAI,CAAC0C,OAAO,OAAO;YACnB,OAAO;gBACLL,SAAS;oBAAC;wBAAEhB,MAAMD,MAAMqB,UAAU;wBAAER,IAAI;wBAAkBE,OAAOO;oBAAM;iBAAE;gBACzEE,OAAO;YACT;QACF;QACA,IAAIX,OAAO,YAAYb,MAAMyB,SAAS,EAAE;YACtC,OAAO;gBAAER,SAAS;oBAAC;wBAAEhB,MAAMD,MAAMyB,SAAS;wBAAEZ,IAAI;wBAAME,OAAOL,MAAMgB,GAAG,CAAC9C;oBAAK;iBAAE;YAAC;QACjF;QACA,IAAIiC,OAAO,YAAYb,MAAMgB,QAAQ,KAAK,YAAY,CAAChB,MAAMyB,SAAS,EAAE;YACtE,OAAO;gBAAER,SAAS;oBAAC;wBAAEhB,MAAMD,MAAMC,IAAI;wBAAEY,IAAI;wBAAME,OAAOnC;oBAAI;iBAAE;YAAC;QACjE;QACA,IAAIiC,OAAO,gBAAgBb,MAAMyB,SAAS,EAAE;YAC1C,MAAMC,MAAMhB,MAAMgB,GAAG,CAAC9C;YACtB,OAAO;gBACLqC,SAAS;oBACP;wBAAEhB,MAAMD,MAAMyB,SAAS;wBAAEZ,IAAI;wBAAME,OAAOW;oBAAI;oBAC9C;wBAAEzB,MAAMD,MAAMyB,SAAS;wBAAEZ,IAAI;wBAAME,OAAO,GAAGW,MAAMhD,MAAM;oBAAC;iBAC3D;gBACDwC,YAAYlB,MAAMyB,SAAS;YAC7B;QACF;QACA,IAAIZ,OAAO,cAAcb,MAAM2B,YAAY,EAAE;YAC3C,MAAMD,MAAMhB,MAAMkB,QAAQ,CAAChD;YAC3B,OAAO;gBACLqC,SAAS;oBACP;wBAAEhB,MAAMD,MAAM2B,YAAY;wBAAEd,IAAI;wBAAME,OAAOW;oBAAI;oBACjD;wBAAEzB,MAAMD,MAAM2B,YAAY;wBAAEd,IAAI;wBAAME,OAAO,GAAGW,MAAMhD,MAAM;oBAAC;iBAC9D;gBACDwC,YAAYlB,MAAM2B,YAAY;YAChC;QACF;QACA,IAAId,OAAO,aAAab,MAAMyB,SAAS,EAAE;YACvC,MAAMI,SAASlD,IAAIC,KAAKE,GAAG,CAAC,CAACiC,QAAUL,MAAMgB,GAAG,CAACX;YACjD,IAAIc,OAAOC,MAAM,GAAGrD,yBAAyB,OAAO,CAAC,QAAQ,EAAEA,wBAAwB,OAAO,CAAC;YAC/F,OAAO;gBACLwC,SAAS;oBAAC;wBAAEhB,MAAMD,MAAMyB,SAAS;wBAAEZ,IAAI;wBAAME,OAAOc;oBAAO;iBAAE;gBAC7DE,cAAcF,OAAOC,MAAM;YAC7B;QACF;QACA,OAAO,GAAGjB,GAAG,uCAAuC,EAAEb,MAAMc,MAAM,EAAE;IACtE;IAEA,IAAId,MAAMO,IAAI,KAAK,MAAM;QACvB,IAAIM,OAAO,UAAU,OAAO;YAAEI,SAAS;gBAAC;oBAAEhB,MAAMzB;oBAAoBqC,IAAI;oBAAME,OAAOnC;gBAAI;aAAE;QAAC;QAC5F,IAAIiC,OAAO,WAAW;YACpB,MAAMgB,SAASlD,IAAIC;YACnB,IAAIiD,OAAOC,MAAM,GAAGrD,yBAAyB,OAAO,CAAC,QAAQ,EAAEA,wBAAwB,OAAO,CAAC;YAC/F,OAAO;gBACLwC,SAAS;oBAAC;wBAAEhB,MAAMzB;wBAAoBqC,IAAI;wBAAME,OAAOc;oBAAO;iBAAE;gBAChEE,cAAcF,OAAOC,MAAM;YAC7B;QACF;QACA,IAAIjB,OAAO,cAAc;YACvB,OAAO;gBACLI,SAAS;oBACP;wBAAEhB,MAAMzB;wBAAoBqC,IAAI;wBAAME,OAAOnC;oBAAI;oBACjD;wBAAEqB,MAAMzB;wBAAoBqC,IAAI;wBAAME,OAAO,GAAGnC,MAAMF,MAAM;oBAAC;iBAC9D;gBACDwC,YAAY1C;YACd;QACF;QACA,OAAO,GAAGqC,GAAG,uCAAuC,EAAEb,MAAMc,MAAM,EAAE;IACtE;IAEA,IAAId,MAAMO,IAAI,KAAK,SAAS;QAC1B,IAAIM,OAAO,UAAU,OAAO;YAAEI,SAAS;gBAAC;oBAAEhB,MAAMD,MAAMC,IAAI;oBAAEY,IAAI;oBAAME,OAAOnC;gBAAI;aAAE;QAAC;QACpF,IAAIiC,OAAO,WAAW;YACpB,MAAMgB,SAASlD,IAAIC;YACnB,IAAIiD,OAAOC,MAAM,GAAGrD,yBAAyB,OAAO,CAAC,QAAQ,EAAEA,wBAAwB,OAAO,CAAC;YAC/F,IAAIuB,MAAMqB,UAAU,EAAE;gBACpB,yDAAyD;gBACzD,OAAO;oBACLJ,SAAS;wBAAC;4BAAEhB,MAAMD,MAAMqB,UAAU;4BAAER,IAAI;4BAAsBE,OAAOc;wBAAO;qBAAE;oBAC9EL,OAAO;oBACPO,cAAcF,OAAOC,MAAM;gBAC7B;YACF;YACA,OAAO;gBACLb,SAAS;oBAAC;wBAAEhB,MAAMD,MAAMC,IAAI;wBAAEY,IAAI;wBAAME,OAAOc;oBAAO;iBAAE;gBACxDE,cAAcF,OAAOC,MAAM;YAC7B;QACF;QACA,IAAIjB,OAAO,cAAcb,MAAMqB,UAAU,EAAE;YACzC,OAAO;gBACLJ,SAAS;oBAAC;wBAAEhB,MAAMD,MAAMqB,UAAU;wBAAER,IAAI;wBAAkBE,OAAOnC;oBAAI;iBAAE;gBACvE4C,OAAO;YACT;QACF;QACA,OAAO,GAAGX,GAAG,uCAAuC,EAAEb,MAAMc,MAAM,EAAE;IACtE;IAEA,IAAId,MAAMO,IAAI,KAAK,UAAU;QAC3B,MAAMQ,QAAQ1B,OAAOT;QACrB,IAAI,CAACS,OAAO2C,QAAQ,CAACjB,QAAQ,OAAO;QACpC,IAAIF,OAAO,KAAK,OAAO;YAAEI,SAAS;gBAAC;oBAAEhB,MAAMD,MAAMC,IAAI;oBAAEY,IAAI;oBAAME;gBAAM;aAAE;QAAC;QAC1E,MAAMkB,aAA0C;YAC9C,MAAM;YACN,KAAK;YACL,MAAM;YACN,KAAK;YACL,MAAM;QACR;QACA,MAAMC,QAAQD,UAAU,CAACpB,GAAG;QAC5B,IAAI,CAACqB,OAAO,OAAO,GAAGrB,GAAG,uCAAuC,EAAEb,MAAMc,MAAM,EAAE;QAChF,OAAO;YAAEG,SAAS;gBAAC;oBAAEhB,MAAMD,MAAMC,IAAI;oBAAEY,IAAIqB;oBAAOnB;gBAAM;aAAE;YAAEG,YAAYlB,MAAMC,IAAI;QAAC;IACrF;IAEA,IAAID,MAAMO,IAAI,KAAK,QAAQ;QACzB,MAAMf,MAAML,UAAUP;QACtB,IAAI,CAACY,KAAK,OAAO;QACjB,6DAA6D;QAC7D,MAAM2C,KAAK,CAACC,OACVpC,MAAMqC,QAAQ,KAAK,WAAWD,KAAK7C,OAAO,KAAK6C;QACjD,IAAIvB,OAAO,MAAM;YACf,OAAO;gBACLI,SAAS;oBACP;wBAAEhB,MAAMD,MAAMC,IAAI;wBAAEY,IAAI;wBAAME,OAAOoB,GAAG3C,IAAIJ,KAAK;oBAAE;oBACnD;wBAAEa,MAAMD,MAAMC,IAAI;wBAAEY,IAAI;wBAAKE,OAAOoB,GAAG3C,IAAIG,GAAG;oBAAE;iBACjD;gBACDuB,YAAYlB,MAAMC,IAAI;YACxB;QACF;QACA,MAAMqC,QAA6C;YACjDC,OAAO;gBAAC;gBAAM/C,IAAIG,GAAG;aAAC;YACtB6C,WAAW;gBAAC;gBAAMhD,IAAIJ,KAAK;aAAC;YAC5BqD,QAAQ;gBAAC;gBAAKjD,IAAIJ,KAAK;aAAC;YACxBsD,YAAY;gBAAC;gBAAKlD,IAAIG,GAAG;aAAC;QAC5B;QACA,MAAMuC,QAAQI,KAAK,CAACzB,GAAG;QACvB,IAAI,CAACqB,OAAO,OAAO,GAAGrB,GAAG,uCAAuC,EAAEb,MAAMc,MAAM,EAAE;QAChF,OAAO;YAAEG,SAAS;gBAAC;oBAAEhB,MAAMD,MAAMC,IAAI;oBAAEY,IAAIqB,KAAK,CAAC,EAAE;oBAAEnB,OAAOoB,GAAGD,KAAK,CAAC,EAAE;gBAAE;aAAE;YAAEhB,YAAYlB,MAAMC,IAAI;QAAC;IACtG;IAEA,OAAO,GAAGY,GAAG,uCAAuC,EAAEb,MAAMc,MAAM,EAAE;AACtE;AAEA,MAAM6B,iBAAiB,CAAC1D,SACtBA,OAAO4B,EAAE,KAAK,QAAQ5B,OAAO4B,EAAE,KAAK,uBAChC+B,KAAKC,GAAG,CAAC,GAAGC,MAAMC,OAAO,CAAC9D,OAAO8B,KAAK,IAAI9B,OAAO8B,KAAK,CAACe,MAAM,GAAG,KAChE;AAEN;;;;;;;;;;CAUC,GACD,OAAO,SAASkB,cACdjD,WAAiC,EACjCkD,OAAyB,EACzBvC,KAA2B;QAEauC,qBAazBA,iBAqGVC;QA1GHjC;IARF,MAAMA,UAA6B;YAAKgC,gBAAAA,QAAQE,IAAI,YAAZF,gBAAgB,EAAE;KAAE;IAC5D,MAAMG,SAA8B,EAAE;IACtC,MAAMC,UAA8B,EAAE;IACtC,MAAMC,UAAoB,EAAE;IAC5B,IAAIC,aAAatC,QAAQuC,IAAI,CAC3B,CAACvE,SAAWA,OAAO4B,EAAE,KAAK,oBAAoB5B,OAAO4B,EAAE,KAAK;IAE9D,IAAIK,sBACFD,gBAAAA,QAAQb,IAAI,CAAC,CAACnB,SAAW;YAAC;YAAM;YAAK;YAAM;YAAK;SAAK,CAAC2B,QAAQ,CAAC3B,OAAO4B,EAAE,uBAAxEI,cAA4EhB,IAAI,mBAAI;IACtF,IAAI8B,eAAed,QAAQwC,MAAM,CAAC,CAACC,SAASzE,SAAWyE,UAAUf,eAAe1D,SAAS;IAEzF,cAAc;IACd,IAAI0E,WAA0B;IAC9B,MAAMC,QAAQ,EAACX,kBAAAA,QAAQY,MAAM,YAAdZ,kBAAkB,EAAE,EAAEnE,GAAG,CAAC,CAACgF,OAASA,KAAK9E,IAAI,IAAIC,MAAM,CAACC;IACvE,IAAI0E,MAAM9B,MAAM,EAAE;QAChB,MAAM+B,SAAS9D,YAAY8D,MAAM;QACjC,MAAMvC,QAAQZ,MAAMY,KAAK,CAACsC,MAAMG,IAAI,CAAC;QACrC,IAAI,CAACF,QAAQ;YACXR,QAAQW,IAAI,CAAC;gBAAEC,QAAQ;gBAAUC,QAAQ;YAA0B;QACrE,OAAO,IAAI,CAAC5C,OAAO;QACjB,gCAAgC;QAClC,OAAO;YACL,MAAM6C,UAAUlD,QAAQmD,SAAS,CAC/B,CAACnF,SAAWA,OAAO4B,EAAE,KAAK,wBAAwB5B,OAAO4B,EAAE,KAAK;YAElE,IAAIsD,YAAY,CAAC,GAAG;gBAClBlD,QAAQ+C,IAAI,CAAC;oBAAE/D,MAAM4D,OAAOxC,UAAU;oBAAER,IAAI;oBAAkBE,OAAOO;gBAAM;gBAC3EiC,aAAa;gBACbI,WAAWrC;YACb,OAAO,IAAIuC,OAAOQ,MAAM,EAAE;gBACxB,iEAAiE;gBACjE,MAAMC,QAAQrD,OAAO,CAACkD,QAAQ;gBAC9B,MAAMI,SAASzB,MAAMC,OAAO,CAACuB,MAAMvD,KAAK,IAAIuD,MAAMvD,KAAK,GAAG;oBAACuD,MAAMvD,KAAK;iBAAC;gBACvEE,QAAQuD,MAAM,CAACL,SAAS,GAAG;oBACzBlE,MAAM4D,OAAOQ,MAAM,CAAChD,UAAU;oBAC9BR,IAAI;oBACJE,OAAOwD,OAAOzF,GAAG,CAAC,CAACC;4BAA6B8E;+BAAnB,GAAGY,OAAO1F,UAAS8E,iBAAAA,OAAOQ,MAAM,qBAAbR,eAAeE,IAAI,GAAGzC,OAAO;;gBAC/E;gBACAqC,WAAWrC;YACb,OAAO;gBACL+B,QAAQW,IAAI,CAAC;oBACXC,QAAQ;oBACRC,QAAQ;gBACV;YACF;YACA,IAAIP,YAAYC,MAAM9B,MAAM,GAAG,GAAG;gBAChCwB,QAAQU,IAAI,CACV,CAAC,wDAAwD,EAAE1C,MAAM,EAAE,CAAC;YAExE;YACA,IAAIqC,YAAYjD,MAAMgB,GAAG,CAACkC,KAAK,CAAC,EAAE,EAAE9B,MAAM,GAAGpB,MAAMgE,SAAS,EAAE;gBAC5DpB,QAAQU,IAAI,CAAC,CAAC,uBAAuB,EAAEtD,MAAMgE,SAAS,CAAC,mBAAmB,CAAC;YAC7E;QACF;IACF;IAEA,eAAe;IACf,KAAK,MAAMT,UAAUhB,QAAQ0B,OAAO,CAAE;QACpC,MAAM3E,QAAQD,YAAY6E,MAAM,CAACxE,IAAI,CAAC,CAACrB,QAAUA,MAAM+B,MAAM,KAAKmD,OAAOjE,KAAK;QAC9E,IAAI,CAACA,OAAO;YACVqD,QAAQW,IAAI,CAAC;gBAAEC;gBAAQC,QAAQ;YAAoC;YACnE;QACF;QACA,MAAMW,QAAQrE,YAAYR,OAAOiE,QAAQvD;QACzC,IAAI,OAAOmE,UAAU,UAAU;YAC7BxB,QAAQW,IAAI,CAAC;gBAAEC;gBAAQC,QAAQW;YAAM;YACrC;QACF;QACA,IAAIA,MAAMrD,KAAK,IAAI+B,YAAY;YAC7BF,QAAQW,IAAI,CAAC;gBACXC;gBACAC,QAAQP,WACJ,oEACA;YACN;YACA;QACF;QACA,IAAIkB,MAAM3D,UAAU,IAAIA,cAAc2D,MAAM3D,UAAU,KAAKA,YAAY;YACrEmC,QAAQW,IAAI,CAAC;gBACXC;gBACAC,QAAQ;YACV;YACA;QACF;QACA,MAAMR,UACJ3B,eAAe8C,MAAM5D,OAAO,CAACwC,MAAM,CAAC,CAACqB,OAAO7F,SAAW6F,QAAQnC,eAAe1D,SAAS;QACzF,IAAIyE,UAAUjF,yBAAyB;YACrC4E,QAAQW,IAAI,CAAC;gBAAEC;gBAAQC,QAAQ,CAAC,sCAAsC,EAAEzF,wBAAwB,CAAC,CAAC;YAAC;YACnG;QACF;QACAwC,QAAQ+C,IAAI,IAAIa,MAAM5D,OAAO;QAC7BmC,OAAOY,IAAI,CAACC;QACZ,IAAIY,MAAMrD,KAAK,EAAE+B,aAAa;QAC9B,IAAIsB,MAAM3D,UAAU,EAAEA,aAAa2D,MAAM3D,UAAU;QACnDa,eAAe2B;IACjB;IAEA,aAAa;IACb,MAAMR,QAAQD,QAAQ5C,IAAI,GACtBN,YAAYI,KAAK,CAACC,IAAI,CACpB,CAACC;YAAuB4C,eAAyCA;eAAvD5C,KAAKJ,IAAI,OAAKgD,gBAAAA,QAAQ5C,IAAI,qBAAZ4C,cAAchD,IAAI,KAAII,KAAKC,SAAS,OAAK2C,iBAAAA,QAAQ5C,IAAI,qBAAZ4C,eAAc3C,SAAS;SAE1FyE;IACJ,MAAMC,UAAU9D,aACZpB,WACEC,aACAA,YAAY6E,MAAM,CAACxE,IAAI,CACrB,CAACJ,QACCA,MAAMC,IAAI,KAAKiB,cACflB,MAAMyB,SAAS,KAAKP,cACpBlB,MAAM2B,YAAY,KAAKT,aAE3BA,eAEDgC,QAAAA,gBAAAA,QAASnD,YAAYI,KAAK,CAAC,EAAE,YAA7B+C,QAAiC;QAAEjD,MAAMzB;QAAoB8B,WAAW;IAAM;IAEnF,OAAO;QAAEW;QAAS+D;QAAS5B;QAAQO;QAAUN;QAASC;IAAQ;AAChE;AAOA,MAAM2B,QAAQ;IAAEC,KAAK;IAAaC,MAAM;AAAa;AAErD;;;;;;;;;CASC,GACD,OAAO,SAASC,iBACdrF,WAAiC,EACjCoD,OAAqD,EAAE;IAEvD,MAAMkC,aAAa,IAAIC,MAAuB,yBAAyB;;IACvE,MAAMC,SAA0B;WAAIxF,YAAYI,KAAK;KAAC;IACtD,MAAMqF,WAAW,CAACnF;QAChB,IAAI,CAACkF,OAAO/B,IAAI,CAAC,CAACzE,QAAUA,MAAMkB,IAAI,KAAKI,KAAKJ,IAAI,IAAIlB,MAAMuB,SAAS,KAAKD,KAAKC,SAAS,GAAG;YAC3FiF,OAAOvB,IAAI,CAAC3D;QACd;IACF;IACA,KAAK,MAAMtB,SAASoE,KAAMkC,WAAWI,GAAG,CAAC1G,MAAMkB,IAAI,EAAEf,QAAQH,MAAMyC,KAAK;IACxE,IAAIzB,YAAY8D,MAAM,EAAE;QACtBwB,WAAWI,GAAG,CAAC1F,YAAY8D,MAAM,CAACxC,UAAU,EAAE;QAC9C,IAAItB,YAAY8D,MAAM,CAACQ,MAAM,EAAEgB,WAAWI,GAAG,CAAC1F,YAAY8D,MAAM,CAACQ,MAAM,CAAChD,UAAU,EAAE;IACtF;IACA,KAAK,MAAMrB,SAASD,YAAY6E,MAAM,CAAE;QACtC,IAAI5E,MAAMW,UAAU,EAAE;QACtB,MAAM+E,MAAMnH,oBAAoByB;QAChC,MAAM2F,MAAM,CAAC,GAAGjF,QAAoBA,MAAM8C,IAAI,CAAC,CAACoC,OAASF,IAAI9E,QAAQ,CAACgF;QACtE,IAAI5F,MAAMmB,MAAM,EAAE;;gBACEpB;YAAlB,KAAK,MAAM2B,gBAAO3B,oBAAAA,YAAY8F,IAAI,qBAAhB9F,iBAAkB,CAACC,MAAMc,MAAM,CAAC,mBAAI,EAAE,CAAE;gBACxDuE,WAAWI,GAAG,CAAC,GAAGzF,MAAMC,IAAI,CAAC,CAAC,EAAEyB,KAAK,EAAE;YACzC;YACA;QACF;QACA,IAAI1B,MAAMO,IAAI,KAAK,QAAQ;YACzB,IAAIoF,IAAI,eAAe3F,MAAMqB,UAAU,EAAEgE,WAAWI,GAAG,CAACzF,MAAMqB,UAAU,EAAE;YAC1E,IAAIsE,IAAI,UAAU,cAAc3F,MAAMyB,SAAS,EAAE4D,WAAWI,GAAG,CAACzF,MAAMyB,SAAS,EAAE;YACjF,IAAIkE,IAAI,aAAa,CAAC3F,MAAMyB,SAAS,IAAIzB,MAAMgB,QAAQ,KAAK,UAAUqE,WAAWI,GAAG,CAACzF,MAAMC,IAAI,EAAE;YACjG,IAAI0F,IAAI,iBAAiB3F,MAAMyB,SAAS,EAAE+D,SAAS1F,WAAWC,aAAaC,OAAOA,MAAMyB,SAAS;YACjG,IAAIkE,IAAI,eAAe3F,MAAM2B,YAAY,EAAE6D,SAAS1F,WAAWC,aAAaC,OAAOA,MAAM2B,YAAY;QACvG,OAAO,IAAI3B,MAAMO,IAAI,KAAK,SAAS;YACjC,IAAIoF,IAAI,UAAU,YAAYN,WAAWI,GAAG,CAACzF,MAAMC,IAAI,EAAE;YACzD,IAAI0F,IAAI,YAAY,cAAc3F,MAAMqB,UAAU,EAAEgE,WAAWI,GAAG,CAACzF,MAAMqB,UAAU,EAAE;QACvF,OAAO,IAAIrB,MAAMO,IAAI,KAAK,WAAW;YACnC8E,WAAWI,GAAG,CAACzF,MAAMC,IAAI,EAAE;QAC7B,OAAO,IAAID,MAAMO,IAAI,KAAK,UAAU;YAClC,IAAIoF,IAAI,MAAMN,WAAWI,GAAG,CAACzF,MAAMC,IAAI,EAAE;YACzC,IAAI0F,IAAI,MAAM,KAAK,MAAM,KAAK,OAAOH,SAAS1F,WAAWC,aAAaC,OAAOA,MAAMC,IAAI;QACzF,OAAO,IAAID,MAAMO,IAAI,KAAK,QAAQ;YAChC,IAAIoF,IAAI,MAAM,SAAS,aAAa,UAAU,eAAe;gBAC3DH,SAAS1F,WAAWC,aAAaC,OAAOA,MAAMC,IAAI;YACpD;QACF;QACA,IAAI0F,IAAI,YAAYN,WAAWI,GAAG,CAACzF,MAAMC,IAAI,EAAE;QAC/C,IAAI0F,IAAI,eAAeH,SAAS1F,WAAWC,aAAaC,OAAOA,MAAMC,IAAI;IAC3E;IACA,MAAM6F,UAA4B,EAAE;IACpC,MAAMC,OAAO,IAAIC;IACjB,KAAK,MAAM,CAAC/F,MAAMuB,MAAM,IAAI6D,WAAY;QACtC,KAAK,MAAMY,SAASV,OAAQ;YAC1B,IAAIU,MAAMhG,IAAI,KAAKA,QAAQgG,MAAMhG,IAAI,KAAKzB,oBAAoB;YAC9D,MAAM0H,QAAwB;gBAC5BtB,QAAQ;oBACNpD,QAAQ;wBAAE2E,WAAWlG;wBAAMmG,aAAa;oBAAW,IAAI;wBAAED,WAAWlG;wBAAMgG,OAAO;oBAAY;oBAC7F;wBAAEE,WAAWF,MAAMhG,IAAI;wBAAEgG,OAAOhB,KAAK,CAACgB,MAAM3F,SAAS,CAAC;oBAAC;iBACxD;YACH;YACA,MAAMoB,MAAM2E,KAAKC,SAAS,CAACJ;YAC3B,IAAI,CAACH,KAAKJ,GAAG,CAACjE,MAAM;gBAClBqE,KAAKQ,GAAG,CAAC7E;gBACToE,QAAQ9B,IAAI,CAACkC;YACf;QACF;IACF;IACA,OAAOJ;AACT;AASA;;;;CAIC,GACD,OAAO,SAASU,wBACdC,IAA4C,EAC5CC,eAAuB,EACvBC,MAAiC,EACjCC,aAAgD,YAAY;IAE5D,MAAM/B,QAAQ,CAACD,SACbA,OACG3F,MAAM,CAAC,CAACe,QAAUA,MAAMmG,SAAS,KAAK3H,oBACtCM,GAAG,CAAC,CAACkB;gBAAgCA;mBAAtB,GAAGA,MAAMmG,SAAS,CAAC,CAAC,GAAEnG,eAAAA,MAAMiG,KAAK,YAAXjG,eAAeA,MAAMoG,WAAW,EAAE;WACvErC,IAAI,CAAC;IACV,MAAM8C,UAAU,IAAIb,IAClBS,KAAKX,OAAO,CACT7G,MAAM,CAAC,CAACiH,QAAUA,MAAMQ,eAAe,KAAKA,mBAAmBR,MAAMU,UAAU,KAAKA,YACpF9H,GAAG,CAAC,CAACoH,QAAUrB,MAAMqB,MAAMtB,MAAM;IAEtC,OAAO+B,OAAO1H,MAAM,CAAC,CAACiH,QAAU,CAACW,QAAQlB,GAAG,CAACd,MAAMqB,MAAMtB,MAAM;AACjE"}
|
|
@@ -28,13 +28,9 @@ export interface ListGridFilterOptions {
|
|
|
28
28
|
/** A field whose stored clauses predate the panel, translated its own way. */
|
|
29
29
|
codecs?: Readonly<Record<string, ListGridFilterCodec>>;
|
|
30
30
|
/**
|
|
31
|
-
* The query serves ONE clause, so the panel's replaces every other
|
|
32
|
-
* server-paged list, which has no loaded window to narrow further.
|
|
33
|
-
* `keepAlongside` names the clauses that do narrow the loaded page and so
|
|
34
|
-
* may stand beside it.
|
|
31
|
+
* The query serves ONE clause, so the panel's replaces every other.
|
|
35
32
|
*/
|
|
36
33
|
single?: boolean;
|
|
37
|
-
keepAlongside?: (clause: ListFilterClause) => boolean;
|
|
38
34
|
/**
|
|
39
35
|
* The quick search's words, held by the list when it needs them before
|
|
40
36
|
* the grid's columns exist; the hook holds them itself otherwise.
|
|
@@ -74,12 +70,12 @@ export interface ListGridFilter {
|
|
|
74
70
|
*
|
|
75
71
|
* ## The list answers, never the grid
|
|
76
72
|
*
|
|
77
|
-
* Every list passes `filterMode="server"`: the grid holds one page,
|
|
78
|
-
*
|
|
79
|
-
*
|
|
80
|
-
*
|
|
81
|
-
*
|
|
82
|
-
* rows
|
|
73
|
+
* Every list passes `filterMode="server"`: the grid holds one page, so a
|
|
74
|
+
* filter the grid ran itself would answer "no match" for a row on the next
|
|
75
|
+
* page. The list puts the clauses and the quick search's words on its query
|
|
76
|
+
* (`planListQuery`, AGL-3321), refuses by name what one query cannot hold,
|
|
77
|
+
* and hands the grid the page that query returned. Nothing is matched over
|
|
78
|
+
* rows already loaded.
|
|
83
79
|
*/
|
|
84
80
|
export declare function useListGridFilter(options?: ListGridFilterOptions): ListGridFilter;
|
|
85
81
|
export default useListGridFilter;
|
|
@@ -36,16 +36,16 @@ const NO_CLAUSES = [];
|
|
|
36
36
|
*
|
|
37
37
|
* ## The list answers, never the grid
|
|
38
38
|
*
|
|
39
|
-
* Every list passes `filterMode="server"`: the grid holds one page,
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
* rows
|
|
39
|
+
* Every list passes `filterMode="server"`: the grid holds one page, so a
|
|
40
|
+
* filter the grid ran itself would answer "no match" for a row on the next
|
|
41
|
+
* page. The list puts the clauses and the quick search's words on its query
|
|
42
|
+
* (`planListQuery`, AGL-3321), refuses by name what one query cannot hold,
|
|
43
|
+
* and hands the grid the page that query returned. Nothing is matched over
|
|
44
|
+
* rows already loaded.
|
|
45
45
|
*/ export function useListGridFilter(options = {}) {
|
|
46
46
|
var _options_clauses, _ref, _ref1;
|
|
47
47
|
var _options_search, _options_search1;
|
|
48
|
-
const { selectFields = [], codecs = {}, single = false
|
|
48
|
+
const { selectFields = [], codecs = {}, single = false } = options;
|
|
49
49
|
const [ownClauses, setOwnClauses] = useState([]);
|
|
50
50
|
const controlled = options.clauses !== undefined;
|
|
51
51
|
const clauses = (_options_clauses = options.clauses) != null ? _options_clauses : controlled ? NO_CLAUSES : ownClauses;
|
|
@@ -129,9 +129,9 @@ const NO_CLAUSES = [];
|
|
|
129
129
|
return;
|
|
130
130
|
}
|
|
131
131
|
setPending(null);
|
|
132
|
-
//
|
|
133
|
-
//
|
|
134
|
-
const base = single
|
|
132
|
+
// On a single-clause list the new clause replaces whatever the query
|
|
133
|
+
// was serving.
|
|
134
|
+
const base = single ? clauses.filter((entry)=>entry.field === field) : clauses;
|
|
135
135
|
const next = upsertListFilterClause(base, field, clause);
|
|
136
136
|
const same = next.length === clauses.length && next.every((entry, at)=>entry.field === clauses[at].field && entry.op === clauses[at].op && entry.value === clauses[at].value);
|
|
137
137
|
if (!same) onChange(next);
|
|
@@ -142,8 +142,7 @@ const NO_CLAUSES = [];
|
|
|
142
142
|
clauses,
|
|
143
143
|
onChange,
|
|
144
144
|
codecFor,
|
|
145
|
-
single
|
|
146
|
-
keepAlongside
|
|
145
|
+
single
|
|
147
146
|
]);
|
|
148
147
|
return {
|
|
149
148
|
filterModel,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../../libs/shared/ui/jsx/src/lib/hooks/use-list-grid-filter.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n'use client'\n\nimport type { GridFilterItem, GridFilterModel } from '@mui/x-data-grid'\nimport { useCallback, useMemo, useState } from 'react'\nimport {\n type ListFilterClause,\n type ListGridFilterCodec,\n listPlainCodec,\n listSelectCodec,\n upsertListFilterClause,\n} from '../const/list-grid-filter'\n\nexport interface ListGridFilterOptions {\n /**\n * The clauses the list is narrowed by — a saved view's working filters.\n * Omitted, the hook holds them itself, for a list with no saved views.\n */\n clauses?: readonly ListFilterClause[]\n onChange?: (clauses: ListFilterClause[]) => void\n /** Fields whose values are picked, which the panel shows as a select. */\n selectFields?: readonly string[]\n /** A field whose stored clauses predate the panel, translated its own way. */\n codecs?: Readonly<Record<string, ListGridFilterCodec>>\n /**\n * The query serves ONE clause, so the panel's replaces every other — a\n * server-paged list, which has no loaded window to narrow further.\n * `keepAlongside` names the clauses that do narrow the loaded page and so\n * may stand beside it.\n */\n single?: boolean\n keepAlongside?: (clause: ListFilterClause) => boolean\n /**\n * The quick search's words, held by the list when it needs them before\n * the grid's columns exist; the hook holds them itself otherwise.\n */\n search?: { words: string[]; onChange: (words: string[]) => void }\n}\n\n/** What the grid is handed, and what the list filters its rows by. */\nexport interface ListGridFilter {\n /** Controlled: the panel's one item, and the quick search's words. */\n filterModel: GridFilterModel\n onFilterModelChange: (model: GridFilterModel) => void\n /** The quick search's words, for the list to match its rows against. */\n searchWords: string[]\n /** Every clause in force — the ones given, or the ones the hook holds. */\n clauses: readonly ListFilterClause[]\n /** Replaces every clause; what the chips call to remove one. */\n setClauses: (clauses: ListFilterClause[]) => void\n}\n\nconst NO_CLAUSES: readonly ListFilterClause[] = []\n\n/**\n * The grid's Filters panel and quick search, bound to a list's clauses\n * (AGL-3313; shared since AGL-3317). See `const/list-grid-filter.ts` for the\n * shape both sides keep.\n *\n * ## One field, one clause; the panel edits one at a time\n *\n * The free DataGrid's panel holds a single item, while a list may be\n * narrowed by several clauses. So each FIELD holds one clause, and the panel\n * is a window onto whichever field was last filtered: setting a value writes\n * that field's clause, clearing it or deleting the row removes it, and\n * choosing another column in the panel moves the window without touching\n * the clause the reader just set. Filtering one field and then another is\n * therefore both, which is what a reader who does it means; the chips over\n * the grid (`ListFilterChips`) show every clause and remove any of them.\n *\n * ## The list answers, never the grid\n *\n * Every list passes `filterMode=\"server\"`: the grid holds one page, or a\n * window the query already narrowed, so a filter the grid ran itself would\n * answer \"no match\" for a row on the next page. The list reads the clauses\n * — onto its query where it can, over its loaded rows where it cannot\n * (`filterListRows`) — and the quick search's words, and hands the grid the\n * rows that answer.\n */\nexport function useListGridFilter(options: ListGridFilterOptions = {}): ListGridFilter {\n const { selectFields = [], codecs = {}, single = false, keepAlongside } = options\n\n const [ownClauses, setOwnClauses] = useState<ListFilterClause[]>([])\n const controlled = options.clauses !== undefined\n const clauses = options.clauses ?? (controlled ? NO_CLAUSES : ownClauses)\n const onChange = useMemo(\n () => (controlled ? (options.onChange ?? (() => undefined)) : setOwnClauses),\n [controlled, options.onChange],\n )\n\n const codecFor = useCallback(\n (field: string): ListGridFilterCodec =>\n codecs[field] ?? (selectFields.includes(field) ? listSelectCodec : listPlainCodec),\n [codecs, selectFields],\n )\n\n /** The field the panel shows, once the reader has pointed it anywhere. */\n const [focus, setFocus] = useState<string | null>(null)\n /** The panel's row while it holds no value yet — a column chosen, a value not. */\n const [pending, setPending] = useState<GridFilterItem | null>(null)\n const [ownWords, setOwnWords] = useState<string[]>([])\n const searchWords = options.search?.words ?? ownWords\n const setSearchWords = options.search?.onChange ?? setOwnWords\n\n /** Whatever the panel last pointed at, else the newest clause it can show. */\n const shownField = useMemo(() => {\n if (focus) return focus\n for (let at = clauses.length - 1; at >= 0; at -= 1) {\n if (codecFor(clauses[at].field).toItem(clauses[at])) return clauses[at].field\n }\n return null\n }, [focus, clauses, codecFor])\n\n const filterModel = useMemo<GridFilterModel>(() => {\n const clause = shownField\n ? clauses.find((entry) => entry.field === shownField)\n : undefined\n const shown = clause ? codecFor(clause.field).toItem(clause) : null\n const item: GridFilterItem | null =\n pending && pending.field === shownField\n ? pending\n : clause && shown\n ? { id: 'list', field: clause.field, ...shown }\n : null\n return { items: item ? [item] : [], quickFilterValues: searchWords }\n }, [shownField, clauses, codecFor, pending, searchWords])\n\n const onFilterModelChange = useCallback(\n (model: GridFilterModel) => {\n const words = (model.quickFilterValues ?? []).map((word) => String(word))\n if (words.join(' ') !== searchWords.join(' ')) setSearchWords(words)\n\n const item = model.items[0]\n // The row deleted: the field it showed is no longer filtered.\n if (!item) {\n if (shownField && clauses.some((clause) => clause.field === shownField)) {\n onChange(upsertListFilterClause(clauses, shownField, null))\n }\n setPending(null)\n setFocus(null)\n return\n }\n const field = String(item.field)\n const moved = field !== shownField\n setFocus(field)\n const clause = codecFor(field).toClause(item)\n if (!clause) {\n // No value yet. A value cleared on the field already shown removes\n // its clause; a column just chosen leaves the last one standing.\n setPending({ ...item, id: 'list' })\n if (!moved && clauses.some((entry) => entry.field === field)) {\n onChange(upsertListFilterClause(clauses, field, null))\n }\n return\n }\n setPending(null)\n // A clause that stands alongside the served one is added beside it;\n // any other replaces whatever the query was serving.\n const base =\n single && !keepAlongside?.(clause)\n ? clauses.filter((entry) => entry.field === field || keepAlongside?.(entry))\n : clauses\n const next = upsertListFilterClause(base, field, clause)\n const same =\n next.length === clauses.length &&\n next.every(\n (entry, at) =>\n entry.field === clauses[at].field &&\n entry.op === clauses[at].op &&\n entry.value === clauses[at].value,\n )\n if (!same) onChange(next)\n },\n [searchWords, setSearchWords, shownField, clauses, onChange, codecFor, single, keepAlongside],\n )\n\n return { filterModel, onFilterModelChange, searchWords, clauses, setClauses: onChange }\n}\n\nexport default useListGridFilter\n"],"names":["useCallback","useMemo","useState","listPlainCodec","listSelectCodec","upsertListFilterClause","NO_CLAUSES","useListGridFilter","options","selectFields","codecs","single","keepAlongside","ownClauses","setOwnClauses","controlled","clauses","undefined","onChange","codecFor","field","includes","focus","setFocus","pending","setPending","ownWords","setOwnWords","searchWords","search","words","setSearchWords","shownField","at","length","toItem","filterModel","clause","find","entry","shown","item","id","items","quickFilterValues","onFilterModelChange","model","map","word","String","join","some","moved","toClause","base","filter","next","same","every","op","value","setClauses"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../../../libs/shared/ui/jsx/src/lib/hooks/use-list-grid-filter.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n'use client'\n\nimport type { GridFilterItem, GridFilterModel } from '@mui/x-data-grid'\nimport { useCallback, useMemo, useState } from 'react'\nimport {\n type ListFilterClause,\n type ListGridFilterCodec,\n listPlainCodec,\n listSelectCodec,\n upsertListFilterClause,\n} from '../const/list-grid-filter'\n\nexport interface ListGridFilterOptions {\n /**\n * The clauses the list is narrowed by — a saved view's working filters.\n * Omitted, the hook holds them itself, for a list with no saved views.\n */\n clauses?: readonly ListFilterClause[]\n onChange?: (clauses: ListFilterClause[]) => void\n /** Fields whose values are picked, which the panel shows as a select. */\n selectFields?: readonly string[]\n /** A field whose stored clauses predate the panel, translated its own way. */\n codecs?: Readonly<Record<string, ListGridFilterCodec>>\n /**\n * The query serves ONE clause, so the panel's replaces every other.\n */\n single?: boolean\n /**\n * The quick search's words, held by the list when it needs them before\n * the grid's columns exist; the hook holds them itself otherwise.\n */\n search?: { words: string[]; onChange: (words: string[]) => void }\n}\n\n/** What the grid is handed, and what the list filters its rows by. */\nexport interface ListGridFilter {\n /** Controlled: the panel's one item, and the quick search's words. */\n filterModel: GridFilterModel\n onFilterModelChange: (model: GridFilterModel) => void\n /** The quick search's words, for the list to match its rows against. */\n searchWords: string[]\n /** Every clause in force — the ones given, or the ones the hook holds. */\n clauses: readonly ListFilterClause[]\n /** Replaces every clause; what the chips call to remove one. */\n setClauses: (clauses: ListFilterClause[]) => void\n}\n\nconst NO_CLAUSES: readonly ListFilterClause[] = []\n\n/**\n * The grid's Filters panel and quick search, bound to a list's clauses\n * (AGL-3313; shared since AGL-3317). See `const/list-grid-filter.ts` for the\n * shape both sides keep.\n *\n * ## One field, one clause; the panel edits one at a time\n *\n * The free DataGrid's panel holds a single item, while a list may be\n * narrowed by several clauses. So each FIELD holds one clause, and the panel\n * is a window onto whichever field was last filtered: setting a value writes\n * that field's clause, clearing it or deleting the row removes it, and\n * choosing another column in the panel moves the window without touching\n * the clause the reader just set. Filtering one field and then another is\n * therefore both, which is what a reader who does it means; the chips over\n * the grid (`ListFilterChips`) show every clause and remove any of them.\n *\n * ## The list answers, never the grid\n *\n * Every list passes `filterMode=\"server\"`: the grid holds one page, so a\n * filter the grid ran itself would answer \"no match\" for a row on the next\n * page. The list puts the clauses and the quick search's words on its query\n * (`planListQuery`, AGL-3321), refuses by name what one query cannot hold,\n * and hands the grid the page that query returned. Nothing is matched over\n * rows already loaded.\n */\nexport function useListGridFilter(options: ListGridFilterOptions = {}): ListGridFilter {\n const { selectFields = [], codecs = {}, single = false } = options\n\n const [ownClauses, setOwnClauses] = useState<ListFilterClause[]>([])\n const controlled = options.clauses !== undefined\n const clauses = options.clauses ?? (controlled ? NO_CLAUSES : ownClauses)\n const onChange = useMemo(\n () => (controlled ? (options.onChange ?? (() => undefined)) : setOwnClauses),\n [controlled, options.onChange],\n )\n\n const codecFor = useCallback(\n (field: string): ListGridFilterCodec =>\n codecs[field] ?? (selectFields.includes(field) ? listSelectCodec : listPlainCodec),\n [codecs, selectFields],\n )\n\n /** The field the panel shows, once the reader has pointed it anywhere. */\n const [focus, setFocus] = useState<string | null>(null)\n /** The panel's row while it holds no value yet — a column chosen, a value not. */\n const [pending, setPending] = useState<GridFilterItem | null>(null)\n const [ownWords, setOwnWords] = useState<string[]>([])\n const searchWords = options.search?.words ?? ownWords\n const setSearchWords = options.search?.onChange ?? setOwnWords\n\n /** Whatever the panel last pointed at, else the newest clause it can show. */\n const shownField = useMemo(() => {\n if (focus) return focus\n for (let at = clauses.length - 1; at >= 0; at -= 1) {\n if (codecFor(clauses[at].field).toItem(clauses[at])) return clauses[at].field\n }\n return null\n }, [focus, clauses, codecFor])\n\n const filterModel = useMemo<GridFilterModel>(() => {\n const clause = shownField\n ? clauses.find((entry) => entry.field === shownField)\n : undefined\n const shown = clause ? codecFor(clause.field).toItem(clause) : null\n const item: GridFilterItem | null =\n pending && pending.field === shownField\n ? pending\n : clause && shown\n ? { id: 'list', field: clause.field, ...shown }\n : null\n return { items: item ? [item] : [], quickFilterValues: searchWords }\n }, [shownField, clauses, codecFor, pending, searchWords])\n\n const onFilterModelChange = useCallback(\n (model: GridFilterModel) => {\n const words = (model.quickFilterValues ?? []).map((word) => String(word))\n if (words.join(' ') !== searchWords.join(' ')) setSearchWords(words)\n\n const item = model.items[0]\n // The row deleted: the field it showed is no longer filtered.\n if (!item) {\n if (shownField && clauses.some((clause) => clause.field === shownField)) {\n onChange(upsertListFilterClause(clauses, shownField, null))\n }\n setPending(null)\n setFocus(null)\n return\n }\n const field = String(item.field)\n const moved = field !== shownField\n setFocus(field)\n const clause = codecFor(field).toClause(item)\n if (!clause) {\n // No value yet. A value cleared on the field already shown removes\n // its clause; a column just chosen leaves the last one standing.\n setPending({ ...item, id: 'list' })\n if (!moved && clauses.some((entry) => entry.field === field)) {\n onChange(upsertListFilterClause(clauses, field, null))\n }\n return\n }\n setPending(null)\n // On a single-clause list the new clause replaces whatever the query\n // was serving.\n const base = single ? clauses.filter((entry) => entry.field === field) : clauses\n const next = upsertListFilterClause(base, field, clause)\n const same =\n next.length === clauses.length &&\n next.every(\n (entry, at) =>\n entry.field === clauses[at].field &&\n entry.op === clauses[at].op &&\n entry.value === clauses[at].value,\n )\n if (!same) onChange(next)\n },\n [searchWords, setSearchWords, shownField, clauses, onChange, codecFor, single],\n )\n\n return { filterModel, onFilterModelChange, searchWords, clauses, setClauses: onChange }\n}\n\nexport default useListGridFilter\n"],"names":["useCallback","useMemo","useState","listPlainCodec","listSelectCodec","upsertListFilterClause","NO_CLAUSES","useListGridFilter","options","selectFields","codecs","single","ownClauses","setOwnClauses","controlled","clauses","undefined","onChange","codecFor","field","includes","focus","setFocus","pending","setPending","ownWords","setOwnWords","searchWords","search","words","setSearchWords","shownField","at","length","toItem","filterModel","clause","find","entry","shown","item","id","items","quickFilterValues","onFilterModelChange","model","map","word","String","join","some","moved","toClause","base","filter","next","same","every","op","value","setClauses"],"mappings":"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"}
|
|
@@ -1,75 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @license
|
|
3
|
-
* Copyright 2026 Aglyn LLC
|
|
4
|
-
*
|
|
5
|
-
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
-
* you may not use this file except in compliance with the License.
|
|
7
|
-
* You may obtain a copy of the License at
|
|
8
|
-
*
|
|
9
|
-
* http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
-
*
|
|
11
|
-
* Unless required by applicable law or agreed to in writing, software
|
|
12
|
-
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
-
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
-
* See the License for the specific language governing permissions and
|
|
15
|
-
* limitations under the License.
|
|
16
|
-
*/
|
|
17
|
-
import type { GridColDef, GridFilterModel } from '@mui/x-data-grid';
|
|
18
|
-
import type { ListFilterField } from '../const/list-filter';
|
|
19
|
-
import { type ListFilterClause, type ListFilterOption } from '../const/list-grid-filter';
|
|
20
|
-
import { type ListGridFilter } from './use-list-grid-filter';
|
|
21
|
-
export interface ListRowsFilterOptions<Row extends object> {
|
|
22
|
-
/** Every row the list read — the whole set, or the window its query capped. */
|
|
23
|
-
rows: readonly Row[];
|
|
24
|
-
/** What the panel offers, in the `ListFilterField` grammar. */
|
|
25
|
-
fields: readonly ListFilterField[];
|
|
26
|
-
/** Choices per picked field; each field named here is shown as a select. */
|
|
27
|
-
options?: Readonly<Record<string, readonly ListFilterOption[]>>;
|
|
28
|
-
/** How a field reads on a chip and as a hidden column's header. */
|
|
29
|
-
headers?: Readonly<Record<string, string>>;
|
|
30
|
-
/** The row paths the quick search reads. */
|
|
31
|
-
search: readonly string[];
|
|
32
|
-
/** A clause the list's query already served, not matched again. */
|
|
33
|
-
served?: (clause: ListFilterClause) => boolean;
|
|
34
|
-
/** A saved view's clauses, when the list keeps them; held here otherwise. */
|
|
35
|
-
clauses?: readonly ListFilterClause[];
|
|
36
|
-
onChange?: (clauses: ListFilterClause[]) => void;
|
|
37
|
-
}
|
|
38
|
-
/** What a list that holds its rows hands its grid and its chips. */
|
|
39
|
-
export interface ListRowsFilter<Row> {
|
|
40
|
-
/** The rows that answer every clause and the search. */
|
|
41
|
-
rows: Row[];
|
|
42
|
-
/** Whether anything narrows the list — a clause or a search word. */
|
|
43
|
-
filtering: boolean;
|
|
44
|
-
gridFilter: ListGridFilter;
|
|
45
|
-
/** Spread onto `ListTable`: the controlled panel, answered by the list. */
|
|
46
|
-
gridProps: {
|
|
47
|
-
filterMode: 'server';
|
|
48
|
-
filterModel: GridFilterModel;
|
|
49
|
-
onFilterModelChange: (model: GridFilterModel) => void;
|
|
50
|
-
quickFilter: true;
|
|
51
|
-
};
|
|
52
|
-
/** Spread onto `ListFilterChips`. */
|
|
53
|
-
chipsProps: {
|
|
54
|
-
fields: readonly ListFilterField[];
|
|
55
|
-
headers: Readonly<Record<string, string>>;
|
|
56
|
-
clauses: readonly ListFilterClause[];
|
|
57
|
-
onChange: (clauses: ListFilterClause[]) => void;
|
|
58
|
-
options: Readonly<Record<string, readonly ListFilterOption[]>>;
|
|
59
|
-
};
|
|
60
|
-
/** The list's columns, typed for the panel (`listFilterGridColumns`). */
|
|
61
|
-
filterColumns: (columns: readonly GridColDef[]) => GridColDef[];
|
|
62
|
-
}
|
|
63
|
-
/**
|
|
64
|
-
* The grid's Filters panel and quick search for a list that HOLDS its rows
|
|
65
|
-
* (AGL-3317) — every row it read, or the window its query capped — and so
|
|
66
|
-
* answers both over that whole set rather than the page on screen.
|
|
67
|
-
*
|
|
68
|
-
* It is `useListGridFilter` with the rest of the recipe every such list
|
|
69
|
-
* repeated: the rows matched through `filterListRows`, the columns typed
|
|
70
|
-
* through `listFilterGridColumns`, and the props the grid and the chips
|
|
71
|
-
* take. A list whose query can serve a clause names it in `served`, puts it
|
|
72
|
-
* on the query, and the rows here are not matched against it again.
|
|
73
|
-
*/
|
|
74
|
-
export declare function useListRowsFilter<Row extends object>(options: ListRowsFilterOptions<Row>): ListRowsFilter<Row>;
|
|
75
|
-
export default useListRowsFilter;
|
|
@@ -1,83 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @license
|
|
3
|
-
* Copyright 2026 Aglyn LLC
|
|
4
|
-
*
|
|
5
|
-
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
-
* you may not use this file except in compliance with the License.
|
|
7
|
-
* You may obtain a copy of the License at
|
|
8
|
-
*
|
|
9
|
-
* http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
-
*
|
|
11
|
-
* Unless required by applicable law or agreed to in writing, software
|
|
12
|
-
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
-
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
-
* See the License for the specific language governing permissions and
|
|
15
|
-
* limitations under the License.
|
|
16
|
-
*/ 'use client';
|
|
17
|
-
import { useCallback, useMemo } from "react";
|
|
18
|
-
import { filterListRows, listFilterGridColumns } from "../const/list-grid-filter.js";
|
|
19
|
-
import { useListGridFilter } from "./use-list-grid-filter.js";
|
|
20
|
-
const NO_OPTIONS = {};
|
|
21
|
-
const NO_HEADERS = {};
|
|
22
|
-
/**
|
|
23
|
-
* The grid's Filters panel and quick search for a list that HOLDS its rows
|
|
24
|
-
* (AGL-3317) — every row it read, or the window its query capped — and so
|
|
25
|
-
* answers both over that whole set rather than the page on screen.
|
|
26
|
-
*
|
|
27
|
-
* It is `useListGridFilter` with the rest of the recipe every such list
|
|
28
|
-
* repeated: the rows matched through `filterListRows`, the columns typed
|
|
29
|
-
* through `listFilterGridColumns`, and the props the grid and the chips
|
|
30
|
-
* take. A list whose query can serve a clause names it in `served`, puts it
|
|
31
|
-
* on the query, and the rows here are not matched against it again.
|
|
32
|
-
*/ export function useListRowsFilter(options) {
|
|
33
|
-
const { rows, fields, options: choices = NO_OPTIONS, headers = NO_HEADERS, search, served } = options;
|
|
34
|
-
const selectFields = useMemo(()=>Object.keys(choices), [
|
|
35
|
-
choices
|
|
36
|
-
]);
|
|
37
|
-
const gridFilter = useListGridFilter({
|
|
38
|
-
clauses: options.clauses,
|
|
39
|
-
onChange: options.onChange,
|
|
40
|
-
selectFields
|
|
41
|
-
});
|
|
42
|
-
const searchKey = gridFilter.searchWords.join(' ');
|
|
43
|
-
const matched = useMemo(()=>filterListRows(rows, fields, gridFilter.clauses, {
|
|
44
|
-
paths: search,
|
|
45
|
-
words: gridFilter.searchWords
|
|
46
|
-
}, served), // `searchKey` stands for the words, which are a new array each render.
|
|
47
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
48
|
-
[
|
|
49
|
-
rows,
|
|
50
|
-
fields,
|
|
51
|
-
gridFilter.clauses,
|
|
52
|
-
searchKey,
|
|
53
|
-
search,
|
|
54
|
-
served
|
|
55
|
-
]);
|
|
56
|
-
const filterColumns = useCallback((columns)=>listFilterGridColumns(columns, fields, choices, headers), [
|
|
57
|
-
fields,
|
|
58
|
-
choices,
|
|
59
|
-
headers
|
|
60
|
-
]);
|
|
61
|
-
return {
|
|
62
|
-
rows: matched,
|
|
63
|
-
filtering: gridFilter.clauses.length > 0 || gridFilter.searchWords.length > 0,
|
|
64
|
-
gridFilter,
|
|
65
|
-
gridProps: {
|
|
66
|
-
filterMode: 'server',
|
|
67
|
-
filterModel: gridFilter.filterModel,
|
|
68
|
-
onFilterModelChange: gridFilter.onFilterModelChange,
|
|
69
|
-
quickFilter: true
|
|
70
|
-
},
|
|
71
|
-
chipsProps: {
|
|
72
|
-
fields,
|
|
73
|
-
headers,
|
|
74
|
-
clauses: gridFilter.clauses,
|
|
75
|
-
onChange: gridFilter.setClauses,
|
|
76
|
-
options: choices
|
|
77
|
-
},
|
|
78
|
-
filterColumns
|
|
79
|
-
};
|
|
80
|
-
}
|
|
81
|
-
export default useListRowsFilter;
|
|
82
|
-
|
|
83
|
-
//# sourceMappingURL=use-list-rows-filter.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../../libs/shared/ui/jsx/src/lib/hooks/use-list-rows-filter.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n'use client'\n\nimport type { GridColDef, GridFilterModel } from '@mui/x-data-grid'\nimport { useCallback, useMemo } from 'react'\nimport type { ListFilterField } from '../const/list-filter'\nimport {\n filterListRows,\n type ListFilterClause,\n type ListFilterOption,\n listFilterGridColumns,\n} from '../const/list-grid-filter'\nimport { type ListGridFilter, useListGridFilter } from './use-list-grid-filter'\n\nexport interface ListRowsFilterOptions<Row extends object> {\n /** Every row the list read — the whole set, or the window its query capped. */\n rows: readonly Row[]\n /** What the panel offers, in the `ListFilterField` grammar. */\n fields: readonly ListFilterField[]\n /** Choices per picked field; each field named here is shown as a select. */\n options?: Readonly<Record<string, readonly ListFilterOption[]>>\n /** How a field reads on a chip and as a hidden column's header. */\n headers?: Readonly<Record<string, string>>\n /** The row paths the quick search reads. */\n search: readonly string[]\n /** A clause the list's query already served, not matched again. */\n served?: (clause: ListFilterClause) => boolean\n /** A saved view's clauses, when the list keeps them; held here otherwise. */\n clauses?: readonly ListFilterClause[]\n onChange?: (clauses: ListFilterClause[]) => void\n}\n\n/** What a list that holds its rows hands its grid and its chips. */\nexport interface ListRowsFilter<Row> {\n /** The rows that answer every clause and the search. */\n rows: Row[]\n /** Whether anything narrows the list — a clause or a search word. */\n filtering: boolean\n gridFilter: ListGridFilter\n /** Spread onto `ListTable`: the controlled panel, answered by the list. */\n gridProps: {\n filterMode: 'server'\n filterModel: GridFilterModel\n onFilterModelChange: (model: GridFilterModel) => void\n quickFilter: true\n }\n /** Spread onto `ListFilterChips`. */\n chipsProps: {\n fields: readonly ListFilterField[]\n headers: Readonly<Record<string, string>>\n clauses: readonly ListFilterClause[]\n onChange: (clauses: ListFilterClause[]) => void\n options: Readonly<Record<string, readonly ListFilterOption[]>>\n }\n /** The list's columns, typed for the panel (`listFilterGridColumns`). */\n filterColumns: (columns: readonly GridColDef[]) => GridColDef[]\n}\n\nconst NO_OPTIONS: Readonly<Record<string, readonly ListFilterOption[]>> = {}\nconst NO_HEADERS: Readonly<Record<string, string>> = {}\n\n/**\n * The grid's Filters panel and quick search for a list that HOLDS its rows\n * (AGL-3317) — every row it read, or the window its query capped — and so\n * answers both over that whole set rather than the page on screen.\n *\n * It is `useListGridFilter` with the rest of the recipe every such list\n * repeated: the rows matched through `filterListRows`, the columns typed\n * through `listFilterGridColumns`, and the props the grid and the chips\n * take. A list whose query can serve a clause names it in `served`, puts it\n * on the query, and the rows here are not matched against it again.\n */\nexport function useListRowsFilter<Row extends object>(\n options: ListRowsFilterOptions<Row>,\n): ListRowsFilter<Row> {\n const {\n rows,\n fields,\n options: choices = NO_OPTIONS,\n headers = NO_HEADERS,\n search,\n served,\n } = options\n const selectFields = useMemo(() => Object.keys(choices), [choices])\n const gridFilter = useListGridFilter({\n clauses: options.clauses,\n onChange: options.onChange,\n selectFields,\n })\n const searchKey = gridFilter.searchWords.join(' ')\n const matched = useMemo(\n () =>\n filterListRows(\n rows,\n fields,\n gridFilter.clauses,\n { paths: search, words: gridFilter.searchWords },\n served,\n ),\n // `searchKey` stands for the words, which are a new array each render.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n [rows, fields, gridFilter.clauses, searchKey, search, served],\n )\n const filterColumns = useCallback(\n (columns: readonly GridColDef[]) =>\n listFilterGridColumns(columns, fields, choices, headers),\n [fields, choices, headers],\n )\n return {\n rows: matched,\n filtering: gridFilter.clauses.length > 0 || gridFilter.searchWords.length > 0,\n gridFilter,\n gridProps: {\n filterMode: 'server',\n filterModel: gridFilter.filterModel,\n onFilterModelChange: gridFilter.onFilterModelChange,\n quickFilter: true,\n },\n chipsProps: {\n fields,\n headers,\n clauses: gridFilter.clauses,\n onChange: gridFilter.setClauses,\n options: choices,\n },\n filterColumns,\n }\n}\n\nexport default useListRowsFilter\n"],"names":["useCallback","useMemo","filterListRows","listFilterGridColumns","useListGridFilter","NO_OPTIONS","NO_HEADERS","useListRowsFilter","options","rows","fields","choices","headers","search","served","selectFields","Object","keys","gridFilter","clauses","onChange","searchKey","searchWords","join","matched","paths","words","filterColumns","columns","filtering","length","gridProps","filterMode","filterModel","onFilterModelChange","quickFilter","chipsProps","setClauses"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED;AAGA,SAASA,WAAW,EAAEC,OAAO,QAAQ,QAAO;AAE5C,SACEC,cAAc,EAGdC,qBAAqB,QAChB,+BAA2B;AAClC,SAA8BC,iBAAiB,QAAQ,4BAAwB;AA8C/E,MAAMC,aAAoE,CAAC;AAC3E,MAAMC,aAA+C,CAAC;AAEtD;;;;;;;;;;CAUC,GACD,OAAO,SAASC,kBACdC,OAAmC;IAEnC,MAAM,EACJC,IAAI,EACJC,MAAM,EACNF,SAASG,UAAUN,UAAU,EAC7BO,UAAUN,UAAU,EACpBO,MAAM,EACNC,MAAM,EACP,GAAGN;IACJ,MAAMO,eAAed,QAAQ,IAAMe,OAAOC,IAAI,CAACN,UAAU;QAACA;KAAQ;IAClE,MAAMO,aAAad,kBAAkB;QACnCe,SAASX,QAAQW,OAAO;QACxBC,UAAUZ,QAAQY,QAAQ;QAC1BL;IACF;IACA,MAAMM,YAAYH,WAAWI,WAAW,CAACC,IAAI,CAAC;IAC9C,MAAMC,UAAUvB,QACd,IACEC,eACEO,MACAC,QACAQ,WAAWC,OAAO,EAClB;YAAEM,OAAOZ;YAAQa,OAAOR,WAAWI,WAAW;QAAC,GAC/CR,SAEJ,uEAAuE;IACvE,uDAAuD;IACvD;QAACL;QAAMC;QAAQQ,WAAWC,OAAO;QAAEE;QAAWR;QAAQC;KAAO;IAE/D,MAAMa,gBAAgB3B,YACpB,CAAC4B,UACCzB,sBAAsByB,SAASlB,QAAQC,SAASC,UAClD;QAACF;QAAQC;QAASC;KAAQ;IAE5B,OAAO;QACLH,MAAMe;QACNK,WAAWX,WAAWC,OAAO,CAACW,MAAM,GAAG,KAAKZ,WAAWI,WAAW,CAACQ,MAAM,GAAG;QAC5EZ;QACAa,WAAW;YACTC,YAAY;YACZC,aAAaf,WAAWe,WAAW;YACnCC,qBAAqBhB,WAAWgB,mBAAmB;YACnDC,aAAa;QACf;QACAC,YAAY;YACV1B;YACAE;YACAO,SAASD,WAAWC,OAAO;YAC3BC,UAAUF,WAAWmB,UAAU;YAC/B7B,SAASG;QACX;QACAgB;IACF;AACF;AAEA,eAAepB,kBAAiB"}
|
|
@@ -1,77 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @license
|
|
3
|
-
* Copyright 2026 Aglyn LLC
|
|
4
|
-
*
|
|
5
|
-
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
-
* you may not use this file except in compliance with the License.
|
|
7
|
-
* You may obtain a copy of the License at
|
|
8
|
-
*
|
|
9
|
-
* http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
-
*
|
|
11
|
-
* Unless required by applicable law or agreed to in writing, software
|
|
12
|
-
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
-
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
-
* See the License for the specific language governing permissions and
|
|
15
|
-
* limitations under the License.
|
|
16
|
-
*/
|
|
17
|
-
import { type ListRowsFilter, type ListRowsFilterOptions } from './use-list-rows-filter';
|
|
18
|
-
/** How many rows a paged list reads while it is filtered — see below. */
|
|
19
|
-
export declare const PAGED_FILTER_WINDOW = 100;
|
|
20
|
-
/**
|
|
21
|
-
* A paged window over a live list — the shape `usePagedCollection` returns:
|
|
22
|
-
* every row from the first page to the one being read (plus, possibly, one
|
|
23
|
-
* probe row past it), the page, and whether more exist.
|
|
24
|
-
*/
|
|
25
|
-
export interface PagedRowsWindow<Row> {
|
|
26
|
-
/** Every row read so far, page 0 to the current page; may hold a probe row. */
|
|
27
|
-
data?: readonly Row[] | null;
|
|
28
|
-
/** The current page's rows, when nothing narrows the list. */
|
|
29
|
-
rows: readonly Row[];
|
|
30
|
-
hasMore: boolean;
|
|
31
|
-
page: number;
|
|
32
|
-
setPage: (page: number) => void;
|
|
33
|
-
pageSize: number;
|
|
34
|
-
setPageSize: (pageSize: number) => void;
|
|
35
|
-
}
|
|
36
|
-
/** What the list's grid, chips and footer take while it is paged. */
|
|
37
|
-
export interface PagedRowsFilter<Row> extends Omit<ListRowsFilter<Row>, 'rows'> {
|
|
38
|
-
/** The rows the grid shows: the page, or the page of the matches. */
|
|
39
|
-
rows: Row[];
|
|
40
|
-
/** How many rows the window read — what a filter has looked through. */
|
|
41
|
-
read: number;
|
|
42
|
-
/** Spread onto `ListPagination`. */
|
|
43
|
-
pagination: {
|
|
44
|
-
page: number;
|
|
45
|
-
pageSize: number;
|
|
46
|
-
rowCount: number;
|
|
47
|
-
hasMore: boolean;
|
|
48
|
-
onPageChange: (page: number) => void;
|
|
49
|
-
onPageSizeChange: (pageSize: number) => void;
|
|
50
|
-
};
|
|
51
|
-
}
|
|
52
|
-
/**
|
|
53
|
-
* The grid's Filters panel and quick search over a PAGED live list
|
|
54
|
-
* (AGL-3317), one that reads a window growing a page at a time rather than
|
|
55
|
-
* every row.
|
|
56
|
-
*
|
|
57
|
-
* Unfiltered it is the pager it wraps: the grid shows the page, and the
|
|
58
|
-
* footer turns it. While a clause or a search word is in force the list
|
|
59
|
-
* matches over EVERYTHING the window has read — every page up to the
|
|
60
|
-
* current one, not the page on screen — and pages the matches instead;
|
|
61
|
-
* turning past the last match widens the window by one more page, so a
|
|
62
|
-
* filter reaches further the further the reader goes. `read` says how far
|
|
63
|
-
* it has looked, for the list to say so while more exist.
|
|
64
|
-
*
|
|
65
|
-
* ## The first filter widens the window once
|
|
66
|
-
*
|
|
67
|
-
* A window of one ten-row page is too little to filter: a search would look
|
|
68
|
-
* through ten rows and call the rest "no match". So the first clause or word
|
|
69
|
-
* widens the window to `filterWindow` rows (default
|
|
70
|
-
* {@link PAGED_FILTER_WINDOW}) — one listener that much wider, for as long as
|
|
71
|
-
* the list is narrowed — and clearing the last one puts the reader back on
|
|
72
|
-
* the page they left.
|
|
73
|
-
*/
|
|
74
|
-
export declare function usePagedRowsFilter<Row extends object>(paged: PagedRowsWindow<Row>, options: Omit<ListRowsFilterOptions<Row>, 'rows'> & {
|
|
75
|
-
filterWindow?: number;
|
|
76
|
-
}): PagedRowsFilter<Row>;
|
|
77
|
-
export default usePagedRowsFilter;
|
|
@@ -1,126 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @license
|
|
3
|
-
* Copyright 2026 Aglyn LLC
|
|
4
|
-
*
|
|
5
|
-
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
-
* you may not use this file except in compliance with the License.
|
|
7
|
-
* You may obtain a copy of the License at
|
|
8
|
-
*
|
|
9
|
-
* http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
-
*
|
|
11
|
-
* Unless required by applicable law or agreed to in writing, software
|
|
12
|
-
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
-
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
-
* See the License for the specific language governing permissions and
|
|
15
|
-
* limitations under the License.
|
|
16
|
-
*/ 'use client';
|
|
17
|
-
import { _ as _extends } from "@swc/helpers/_/_extends";
|
|
18
|
-
import { _ as _object_without_properties_loose } from "@swc/helpers/_/_object_without_properties_loose";
|
|
19
|
-
import { useEffect, useMemo, useRef, useState } from "react";
|
|
20
|
-
import { useListRowsFilter } from "./use-list-rows-filter.js";
|
|
21
|
-
/** How many rows a paged list reads while it is filtered — see below. */ export const PAGED_FILTER_WINDOW = 100;
|
|
22
|
-
/**
|
|
23
|
-
* The grid's Filters panel and quick search over a PAGED live list
|
|
24
|
-
* (AGL-3317), one that reads a window growing a page at a time rather than
|
|
25
|
-
* every row.
|
|
26
|
-
*
|
|
27
|
-
* Unfiltered it is the pager it wraps: the grid shows the page, and the
|
|
28
|
-
* footer turns it. While a clause or a search word is in force the list
|
|
29
|
-
* matches over EVERYTHING the window has read — every page up to the
|
|
30
|
-
* current one, not the page on screen — and pages the matches instead;
|
|
31
|
-
* turning past the last match widens the window by one more page, so a
|
|
32
|
-
* filter reaches further the further the reader goes. `read` says how far
|
|
33
|
-
* it has looked, for the list to say so while more exist.
|
|
34
|
-
*
|
|
35
|
-
* ## The first filter widens the window once
|
|
36
|
-
*
|
|
37
|
-
* A window of one ten-row page is too little to filter: a search would look
|
|
38
|
-
* through ten rows and call the rest "no match". So the first clause or word
|
|
39
|
-
* widens the window to `filterWindow` rows (default
|
|
40
|
-
* {@link PAGED_FILTER_WINDOW}) — one listener that much wider, for as long as
|
|
41
|
-
* the list is narrowed — and clearing the last one puts the reader back on
|
|
42
|
-
* the page they left.
|
|
43
|
-
*/ export function usePagedRowsFilter(paged, options) {
|
|
44
|
-
const { pageSize } = paged;
|
|
45
|
-
const { filterWindow = PAGED_FILTER_WINDOW } = options, filterOptions = _object_without_properties_loose(options, [
|
|
46
|
-
"filterWindow"
|
|
47
|
-
]);
|
|
48
|
-
const windowRows = useMemo(()=>{
|
|
49
|
-
var _paged_data;
|
|
50
|
-
return ((_paged_data = paged.data) != null ? _paged_data : paged.rows).slice(0, pageSize * (paged.page + 1));
|
|
51
|
-
}, [
|
|
52
|
-
paged.data,
|
|
53
|
-
paged.rows,
|
|
54
|
-
pageSize,
|
|
55
|
-
paged.page
|
|
56
|
-
]);
|
|
57
|
-
const filter = useListRowsFilter(_extends({}, filterOptions, {
|
|
58
|
-
rows: windowRows
|
|
59
|
-
}));
|
|
60
|
-
const [matchPage, setMatchPage] = useState(0);
|
|
61
|
-
const searchKey = filter.gridFilter.searchWords.join(' ');
|
|
62
|
-
// A narrowed list starts again at its first page.
|
|
63
|
-
useEffect(()=>setMatchPage(0), [
|
|
64
|
-
filter.gridFilter.clauses,
|
|
65
|
-
searchKey
|
|
66
|
-
]);
|
|
67
|
-
/** The page the reader was on before the list was narrowed. */ const before = useRef(null);
|
|
68
|
-
const { filtering } = filter;
|
|
69
|
-
useEffect(()=>{
|
|
70
|
-
if (filtering && before.current === null) {
|
|
71
|
-
before.current = paged.page;
|
|
72
|
-
const widened = Math.max(0, Math.ceil(filterWindow / pageSize) - 1);
|
|
73
|
-
if (widened > paged.page) paged.setPage(widened);
|
|
74
|
-
} else if (!filtering && before.current !== null) {
|
|
75
|
-
paged.setPage(before.current);
|
|
76
|
-
before.current = null;
|
|
77
|
-
}
|
|
78
|
-
// Only the transition into and out of a filter moves the window.
|
|
79
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
80
|
-
}, [
|
|
81
|
-
filtering
|
|
82
|
-
]);
|
|
83
|
-
if (!filter.filtering) {
|
|
84
|
-
return _extends({}, filter, {
|
|
85
|
-
rows: [
|
|
86
|
-
...paged.rows
|
|
87
|
-
],
|
|
88
|
-
read: windowRows.length,
|
|
89
|
-
pagination: {
|
|
90
|
-
page: paged.page,
|
|
91
|
-
pageSize,
|
|
92
|
-
rowCount: paged.rows.length,
|
|
93
|
-
hasMore: paged.hasMore,
|
|
94
|
-
onPageChange: paged.setPage,
|
|
95
|
-
onPageSizeChange: paged.setPageSize
|
|
96
|
-
}
|
|
97
|
-
});
|
|
98
|
-
}
|
|
99
|
-
const matches = filter.rows;
|
|
100
|
-
const shown = matches.slice(matchPage * pageSize, (matchPage + 1) * pageSize);
|
|
101
|
-
const lastMatchPage = Math.max(0, Math.ceil(matches.length / pageSize) - 1);
|
|
102
|
-
return _extends({}, filter, {
|
|
103
|
-
rows: shown,
|
|
104
|
-
read: windowRows.length,
|
|
105
|
-
pagination: {
|
|
106
|
-
page: matchPage,
|
|
107
|
-
pageSize,
|
|
108
|
-
rowCount: shown.length,
|
|
109
|
-
hasMore: paged.hasMore || matchPage < lastMatchPage,
|
|
110
|
-
onPageChange: (next)=>{
|
|
111
|
-
// Past the matches read so far, the window reads another page first.
|
|
112
|
-
if ((next + 1) * pageSize > matches.length && paged.hasMore) {
|
|
113
|
-
paged.setPage(paged.page + 1);
|
|
114
|
-
}
|
|
115
|
-
setMatchPage(next);
|
|
116
|
-
},
|
|
117
|
-
onPageSizeChange: (next)=>{
|
|
118
|
-
paged.setPageSize(next);
|
|
119
|
-
setMatchPage(0);
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
});
|
|
123
|
-
}
|
|
124
|
-
export default usePagedRowsFilter;
|
|
125
|
-
|
|
126
|
-
//# sourceMappingURL=use-paged-rows-filter.js.map
|