@wral/studio.ui.search-filter
v0.1.0
Published
Lit web component for building Lucene search queries visually (WRAL Studio Search Filter).
Downloads
86
Keywords
Readme
@wral/studio.ui.search-filter
<studio-search-filter> — a visual Lucene query builder for WRAL Studio
apps (DEV-1121). Extracted from Curator's embedded query builder so any
Studio app can build search-index queries with the same UI.
It edits a query string through a rule/group/if-then tree with
include/exclude and all/any toggles, drag-to-reorder, and a raw
"Advanced" editor. When an incoming query uses syntax the visual model
cannot round-trip losslessly (unknown fields, withheld operators, mixed
AND/OR precedence, + prefixes, positive implicit joins), the component
automatically stays in raw mode so production queries are never silently
rewritten.
Host contract
<studio-search-filter
.value=${queryString}
@change=${e => save(e.detail.value)}
></studio-search-filter>value(String property/attribute) — the Lucene query string in.change(CustomEvent,detail.value) — the edited query string out, fired on every committed edit. The event does not bubble and does not cross shadow boundaries: listen directly on the element.
The component only builds the query string. Executing searches stays
in the host app (e.g. via @wral/sdk-search).
Configuration (all JSON)
The component cannot know which fields a consumer's search index allows,
so the field dropdown, operator dropdowns, and boolean joiners are all
supplied by the consumer as JSON — as properties or as JSON-encoded
attributes. Everything defaults to the bundled publication preset
(story search against api.wral.com/search), which is exactly Curator's
original catalog and copy.
fields — the field catalog
{
"tag": { "label": "Tag", "path": "tags.name", "type": "text",
"hint": "e.g. UNC sports" },
"uri": { "label": "Article URL", "path": "uri", "type": "url" },
"date": { "label": "Published", "path": "metadata.articlePublishedTime",
"type": "date" },
"video": { "label": "Has hero video", "path": "metadata.ogVideo",
"type": "video", "durationPath": "metadata.ogVideoDuration" },
"any": { "label": "Any field", "path": "", "type": "text" }
}pathis the actual search-index field name.""marks the backend's default full-text field: bare terms (nofield:prefix) parse to it and it serializes without a prefix.typeis one oftext,url,date,number,video.urlgetsis exactly(matchespath.keyword) andis anywhere under(quoted contains on the analyzed field).videomodels an existence flag plus a numeric companion field:has a …tests_exists_:path; the duration operators range overdurationPath(required for this type).
hintis the value input's placeholder.ops(optional) overrides the operator list for just this field.
parseLucene and the round-trip check key off the active catalog: a
query naming a field outside it will not open in the visual editor.
operators — per-type operator dropdowns
{ "text": [ { "id": "phrase", "label": "contains the phrase" },
{ "id": "term", "label": "matches the word" } ] }Consumers may subset and relabel, but not invent ids — serialization is keyed on the id. Known ids per type:
| type | ids |
|--------|-----|
| text | phrase, term, starts, fuzzy, exists |
| url | is, under |
| date | after, before, between |
| number | gte, lte, between |
| video | exists, longer, shorter, between |
A query using a withheld operator stays in raw mode.
booleans — the group joiners (all/any toggle)
[ { "id": "AND", "label": "all" }, { "id": "OR", "label": "any" } ]Only AND and OR exist in the Lucene subset. Restricting to one
joiner keeps queries using the other in raw mode, and withdraws the
If/then action (conditionals desugar to OR-of-ANDs).
defaults — seed rules
{
"field": "title",
"conditional": {
"if": { "field": "tag", "op": "term" },
"then": { "field": "uri", "op": "under", "negate": true },
"else": { "field": "uri", "op": "under" }
}
}The field a new rule starts on, and the three seed rules of a new If/then. Falls back to the catalog's first field.
labels — copy
{ "itemNoun": "asset", "itemNounPlural": "assets" }All copy defaults to the publication preset's story wording
("Stories must match…", "No rules yet, so every story can appear…").
itemNoun/itemNounPlural rewrite the noun-bearing strings; every
individual string can also be overridden — see src/labels.mjs for the
full key list.
Presets
import { publicationPreset } from '@wral/studio.ui.search-filter' —
{ fields, defaults, labels } for publication search. New presets
(system search, DAM, Content assets) are plain data modules under
src/presets/; nothing in the component needs to change to add one.
Installing
npm install @wral/studio.ui.search-filter litimport '@wral/studio.ui.search-filter'; // registers the tag too
// or, registration only:
import '@wral/studio.ui.search-filter/define';
// model utilities for host apps:
import { equivalent, createQueryModel } from '@wral/studio.ui.search-filter';
// DOM-free (servers, workers, node tests — no lit/component):
import { equivalent } from '@wral/studio.ui.search-filter/model';Or drop in from the CDN (lit bundled, nothing else needed):
<script type="module"
src="https://cdn.wral.com/@wral/studio.ui.search-filter/latest/define.standalone.js"></script>equivalent(a, b) tells hosts whether two query strings differ only by
representational noise the builder normalizes (implicit vs explicit AND,
NOT/-, redundant parens, …) — Curator uses it to avoid marking a
collection dirty when the builder re-serializes an equivalent query.
Host expectations
litis a peer dependency in the npm build (external, host-provided); onlydefine.standalone.jsbundles it.- The templates render studio-ui elements (
studio-button,studio-button-group,studio-select,studio-input,studio-icon,studio-tooltip). They are global custom elements the host page must provide — in Studio, the shell loadshttps://cdn.wral.studio/ui/components/core/latest-v0/studio-ui.bundle.js. They are deliberately not imported or bundled here. - Theming is via CSS custom properties, every one consumed with a
fallback:
--studio-primary/secondary/surface/bg/fg/muted/subtle/ border/lineand--wral-red/green/green-deep/purpleplus--wral-font-body/ui/mono.
Developing
npm run dev # vite demo (index.html)
npm test # jest (ESM), jsdom for component tests
npm run lint
npm run build # dist/index.mjs + dist/define.mjs (lit external)
# + dist/define.standalone.js (self-contained)Releasing
Tag vX.Y.Z and push. Bitbucket Pipelines lints/tests, builds, publishes
to npm, uploads dist/ to
${S3_BUCKET}/${S3_PREFIX}/vX.Y.Z (immutable) and refreshes the
vX.Y, vX and latest aliases (+ CloudFront invalidation), matching
the web-components.* convention. Required repo variables: NPM_TOKEN,
AWS_*, S3_BUCKET, S3_PREFIX (e.g. @wral/studio.ui.search-filter),
AWS_DISTRIBUTION_ID.
