@wral/studio.ui.search-filter 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,197 @@
1
+ # @wral/studio.ui.search-filter
2
+
3
+ `<studio-search-filter>` — a visual Lucene query builder for WRAL Studio
4
+ apps (DEV-1121). Extracted from Curator's embedded query builder so any
5
+ Studio app can build search-index queries with the same UI.
6
+
7
+ It edits a query string through a rule/group/if-then tree with
8
+ include/exclude and all/any toggles, drag-to-reorder, and a raw
9
+ "Advanced" editor. When an incoming query uses syntax the visual model
10
+ cannot round-trip losslessly (unknown fields, withheld operators, mixed
11
+ AND/OR precedence, `+` prefixes, positive implicit joins), the component
12
+ automatically stays in raw mode so production queries are never silently
13
+ rewritten.
14
+
15
+ ## Host contract
16
+
17
+ ```html
18
+ <studio-search-filter
19
+ .value=${queryString}
20
+ @change=${e => save(e.detail.value)}
21
+ ></studio-search-filter>
22
+ ```
23
+
24
+ - **`value`** (String property/attribute) — the Lucene query string in.
25
+ - **`change`** (CustomEvent, `detail.value`) — the edited query string
26
+ out, fired on every committed edit. The event does not bubble and does
27
+ not cross shadow boundaries: listen directly on the element.
28
+
29
+ The component only *builds* the query string. Executing searches stays
30
+ in the host app (e.g. via `@wral/sdk-search`).
31
+
32
+ ## Configuration (all JSON)
33
+
34
+ The component cannot know which fields a consumer's search index allows,
35
+ so the field dropdown, operator dropdowns, and boolean joiners are all
36
+ supplied by the consumer as JSON — as properties or as JSON-encoded
37
+ attributes. Everything defaults to the bundled **publication preset**
38
+ (story search against `api.wral.com/search`), which is exactly Curator's
39
+ original catalog and copy.
40
+
41
+ ### `fields` — the field catalog
42
+
43
+ ```json
44
+ {
45
+ "tag": { "label": "Tag", "path": "tags.name", "type": "text",
46
+ "hint": "e.g. UNC sports" },
47
+ "uri": { "label": "Article URL", "path": "uri", "type": "url" },
48
+ "date": { "label": "Published", "path": "metadata.articlePublishedTime",
49
+ "type": "date" },
50
+ "video": { "label": "Has hero video", "path": "metadata.ogVideo",
51
+ "type": "video", "durationPath": "metadata.ogVideoDuration" },
52
+ "any": { "label": "Any field", "path": "", "type": "text" }
53
+ }
54
+ ```
55
+
56
+ - `path` is the actual search-index field name. `""` marks the backend's
57
+ default full-text field: bare terms (no `field:` prefix) parse to it
58
+ and it serializes without a prefix.
59
+ - `type` is one of `text`, `url`, `date`, `number`, `video`.
60
+ - `url` gets `is exactly` (matches `path.keyword`) and
61
+ `is anywhere under` (quoted contains on the analyzed field).
62
+ - `video` models an existence flag plus a numeric companion field:
63
+ `has a …` tests `_exists_:path`; the duration operators range over
64
+ `durationPath` (required for this type).
65
+ - `hint` is the value input's placeholder.
66
+ - `ops` (optional) overrides the operator list for just this field.
67
+
68
+ `parseLucene` and the round-trip check key off the active catalog: a
69
+ query naming a field outside it will not open in the visual editor.
70
+
71
+ ### `operators` — per-type operator dropdowns
72
+
73
+ ```json
74
+ { "text": [ { "id": "phrase", "label": "contains the phrase" },
75
+ { "id": "term", "label": "matches the word" } ] }
76
+ ```
77
+
78
+ Consumers may subset and relabel, but not invent ids — serialization is
79
+ keyed on the id. Known ids per type:
80
+
81
+ | type | ids |
82
+ |--------|-----|
83
+ | text | `phrase`, `term`, `starts`, `fuzzy`, `exists` |
84
+ | url | `is`, `under` |
85
+ | date | `after`, `before`, `between` |
86
+ | number | `gte`, `lte`, `between` |
87
+ | video | `exists`, `longer`, `shorter`, `between` |
88
+
89
+ A query using a withheld operator stays in raw mode.
90
+
91
+ ### `booleans` — the group joiners (all/any toggle)
92
+
93
+ ```json
94
+ [ { "id": "AND", "label": "all" }, { "id": "OR", "label": "any" } ]
95
+ ```
96
+
97
+ Only `AND` and `OR` exist in the Lucene subset. Restricting to one
98
+ joiner keeps queries using the other in raw mode, and withdraws the
99
+ If/then action (conditionals desugar to OR-of-ANDs).
100
+
101
+ ### `defaults` — seed rules
102
+
103
+ ```json
104
+ {
105
+ "field": "title",
106
+ "conditional": {
107
+ "if": { "field": "tag", "op": "term" },
108
+ "then": { "field": "uri", "op": "under", "negate": true },
109
+ "else": { "field": "uri", "op": "under" }
110
+ }
111
+ }
112
+ ```
113
+
114
+ The field a new rule starts on, and the three seed rules of a new
115
+ If/then. Falls back to the catalog's first field.
116
+
117
+ ### `labels` — copy
118
+
119
+ ```json
120
+ { "itemNoun": "asset", "itemNounPlural": "assets" }
121
+ ```
122
+
123
+ All copy defaults to the publication preset's story wording
124
+ ("Stories must match…", "No rules yet, so every story can appear…").
125
+ `itemNoun`/`itemNounPlural` rewrite the noun-bearing strings; every
126
+ individual string can also be overridden — see `src/labels.mjs` for the
127
+ full key list.
128
+
129
+ ### Presets
130
+
131
+ `import { publicationPreset } from '@wral/studio.ui.search-filter'` —
132
+ `{ fields, defaults, labels }` for publication search. New presets
133
+ (system search, DAM, Content assets) are plain data modules under
134
+ `src/presets/`; nothing in the component needs to change to add one.
135
+
136
+ ## Installing
137
+
138
+ ```sh
139
+ npm install @wral/studio.ui.search-filter lit
140
+ ```
141
+
142
+ ```js
143
+ import '@wral/studio.ui.search-filter'; // registers the tag too
144
+ // or, registration only:
145
+ import '@wral/studio.ui.search-filter/define';
146
+ // model utilities for host apps:
147
+ import { equivalent, createQueryModel } from '@wral/studio.ui.search-filter';
148
+ // DOM-free (servers, workers, node tests — no lit/component):
149
+ import { equivalent } from '@wral/studio.ui.search-filter/model';
150
+ ```
151
+
152
+ Or drop in from the CDN (lit bundled, nothing else needed):
153
+
154
+ ```html
155
+ <script type="module"
156
+ src="https://cdn.wral.com/@wral/studio.ui.search-filter/latest/define.standalone.js"></script>
157
+ ```
158
+
159
+ `equivalent(a, b)` tells hosts whether two query strings differ only by
160
+ representational noise the builder normalizes (implicit vs explicit AND,
161
+ `NOT`/`-`, redundant parens, …) — Curator uses it to avoid marking a
162
+ collection dirty when the builder re-serializes an equivalent query.
163
+
164
+ ## Host expectations
165
+
166
+ - `lit` is a peer dependency in the npm build (external, host-provided);
167
+ only `define.standalone.js` bundles it.
168
+ - The templates render **studio-ui** elements (`studio-button`,
169
+ `studio-button-group`, `studio-select`, `studio-input`, `studio-icon`,
170
+ `studio-tooltip`). They are global custom elements the host page must
171
+ provide — in Studio, the shell loads
172
+ `https://cdn.wral.studio/ui/components/core/latest-v0/studio-ui.bundle.js`.
173
+ They are deliberately not imported or bundled here.
174
+ - Theming is via CSS custom properties, every one consumed with a
175
+ fallback: `--studio-primary/secondary/surface/bg/fg/muted/subtle/
176
+ border/line` and `--wral-red/green/green-deep/purple` plus
177
+ `--wral-font-body/ui/mono`.
178
+
179
+ ## Developing
180
+
181
+ ```sh
182
+ npm run dev # vite demo (index.html)
183
+ npm test # jest (ESM), jsdom for component tests
184
+ npm run lint
185
+ npm run build # dist/index.mjs + dist/define.mjs (lit external)
186
+ # + dist/define.standalone.js (self-contained)
187
+ ```
188
+
189
+ ## Releasing
190
+
191
+ Tag `vX.Y.Z` and push. Bitbucket Pipelines lints/tests, builds, publishes
192
+ to npm, uploads `dist/` to
193
+ `${S3_BUCKET}/${S3_PREFIX}/vX.Y.Z` (immutable) and refreshes the
194
+ `vX.Y`, `vX` and `latest` aliases (+ CloudFront invalidation), matching
195
+ the `web-components.*` convention. Required repo variables: `NPM_TOKEN`,
196
+ `AWS_*`, `S3_BUCKET`, `S3_PREFIX` (e.g. `@wral/studio.ui.search-filter`),
197
+ `AWS_DISTRIBUTION_ID`.
@@ -0,0 +1,11 @@
1
+ import { S as i } from "./search-filter-B-aiJCKp.js";
2
+ const t = "studio-search-filter";
3
+ function o() {
4
+ const e = globalThis?.customElements;
5
+ e && (e.get(t) || e.define(t, i));
6
+ }
7
+ typeof window < "u" && globalThis.customElements && o();
8
+ export {
9
+ t as TAG_NAME,
10
+ o as defineAll
11
+ };