@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 +197 -0
- package/dist/define.mjs +11 -0
- package/dist/define.standalone.js +1603 -0
- package/dist/index.mjs +17 -0
- package/dist/model.mjs +582 -0
- package/dist/search-filter-B-aiJCKp.js +453 -0
- package/package.json +40 -0
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`.
|
package/dist/define.mjs
ADDED
|
@@ -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
|
+
};
|