@dumiflex/wp-kit 0.6.0 → 0.7.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/CHANGELOG.md +15 -0
- package/package.json +1 -1
- package/src/BundlePreview.vue +254 -0
- package/src/CombinePreview.vue +150 -0
- package/src/ConstraintMatrixPreview.vue +6 -22
- package/src/ConstraintPreview.vue +259 -0
- package/src/DerivationCondition.vue +68 -0
- package/src/DerivationPreview.vue +345 -0
- package/src/FixedValuesPreview.vue +184 -0
- package/src/IconPicker.vue +31 -102
- package/src/IconPickerPanel.vue +132 -0
- package/src/LoadingMark.vue +6 -61
- package/src/PayloadPreview.vue +154 -0
- package/src/PostDetailSkeleton.vue +65 -0
- package/src/PostGridSkeleton.vue +61 -0
- package/src/PreviewGroup.vue +75 -0
- package/src/ProfileSkeleton.vue +73 -0
- package/src/RichText.vue +61 -0
- package/src/SelectionBar.vue +90 -0
- package/src/SettingGroup.vue +57 -0
- package/src/SettingRow.vue +127 -0
- package/src/WildcardPreview.vue +607 -0
- package/src/WorkingLine.vue +38 -124
- package/src/canvas-motion.ts +251 -0
- package/src/kind-colour.ts +24 -1
- package/src/linkify.ts +142 -0
- package/src/loading-mark-motion.ts +12 -155
- package/src/matrix-axis.ts +28 -0
- package/src/pi-icons.ts +51 -0
- package/src/preview-readers.ts +401 -0
- package/src/preview.css +194 -0
- package/src/tokens.ts +8 -2
- package/src/working-line-motion.ts +135 -0
|
@@ -0,0 +1,607 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* Wildcard preview built for big pools: a stats strip, search + filters,
|
|
4
|
+
* tag facets with counts, and options as table rows (chance, tags,
|
|
5
|
+
* negative) grouped by one axis at a time. Groups past the first two start
|
|
6
|
+
* closed and each open group shows a page of rows behind "Show N more", so
|
|
7
|
+
* a 200-option wildcard stays a screen or two tall.
|
|
8
|
+
*/
|
|
9
|
+
import { computed, ref } from "vue";
|
|
10
|
+
import TokenizedValue from "./TokenizedValue.vue";
|
|
11
|
+
import {
|
|
12
|
+
scanText,
|
|
13
|
+
wildcardAxes,
|
|
14
|
+
wildcardOptions,
|
|
15
|
+
type Obj,
|
|
16
|
+
type WildcardOption,
|
|
17
|
+
} from "./preview-readers";
|
|
18
|
+
import WpSegmented, { type SegmentedOption } from "./WpSegmented.vue";
|
|
19
|
+
import SearchInput from "./SearchInput.vue";
|
|
20
|
+
import Button from "./Button.vue";
|
|
21
|
+
import ListRow from "./ListRow.vue";
|
|
22
|
+
import Chip from "./Chip.vue";
|
|
23
|
+
import PreviewGroup from "./PreviewGroup.vue";
|
|
24
|
+
|
|
25
|
+
const props = defineProps<{
|
|
26
|
+
inner: Obj | null;
|
|
27
|
+
refMap: ReadonlyMap<string, string>;
|
|
28
|
+
}>();
|
|
29
|
+
|
|
30
|
+
const OPEN_GROUPS = 2;
|
|
31
|
+
const PAGE = 8;
|
|
32
|
+
const LONG_CHARS = 90;
|
|
33
|
+
const FACET_PAGE = 6;
|
|
34
|
+
|
|
35
|
+
const options = computed(() => wildcardOptions(props.inner));
|
|
36
|
+
const axes = computed(() => wildcardAxes(props.inner));
|
|
37
|
+
const totalWeight = computed(() =>
|
|
38
|
+
options.value.reduce((n, o) => n + Math.max(0, o.weight), 0),
|
|
39
|
+
);
|
|
40
|
+
const maxWeight = computed(() => Math.max(0, ...options.value.map((o) => o.weight)));
|
|
41
|
+
const refCount = computed(() => {
|
|
42
|
+
let n = 0;
|
|
43
|
+
for (const o of options.value) {
|
|
44
|
+
const s = scanText(o.value);
|
|
45
|
+
if (s.refs || s.picks) n++;
|
|
46
|
+
}
|
|
47
|
+
return n;
|
|
48
|
+
});
|
|
49
|
+
const negCount = computed(() => options.value.filter((o) => o.negative).length);
|
|
50
|
+
const weighted = computed(() => options.value.some((o) => o.weight !== 1));
|
|
51
|
+
const tagTotal = computed(() => new Set(axes.value.flatMap((a) => a.tags)).size);
|
|
52
|
+
const weightRange = computed(() => {
|
|
53
|
+
const ws = options.value.map((o) => o.weight);
|
|
54
|
+
const lo = Math.min(...ws);
|
|
55
|
+
const hi = Math.max(...ws);
|
|
56
|
+
return lo === hi ? null : `${fmt(lo)}–${fmt(hi)}×`;
|
|
57
|
+
});
|
|
58
|
+
function fmt(n: number): string {
|
|
59
|
+
return Number.isInteger(n) ? String(n) : n.toFixed(2).replace(/\.?0+$/, "");
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// ─── filters ───
|
|
63
|
+
const search = ref("");
|
|
64
|
+
const onlyNeg = ref(false);
|
|
65
|
+
const onlyWeighted = ref(false);
|
|
66
|
+
const onlyRefs = ref(false);
|
|
67
|
+
/** Selected facet tags per axis. OR within an axis, AND across axes. */
|
|
68
|
+
const picked = ref<Record<string, string[]>>({});
|
|
69
|
+
function togglePick(axis: string, tag: string): void {
|
|
70
|
+
const cur = picked.value[axis] ?? [];
|
|
71
|
+
const next = cur.includes(tag) ? cur.filter((t) => t !== tag) : [...cur, tag];
|
|
72
|
+
picked.value = { ...picked.value, [axis]: next };
|
|
73
|
+
}
|
|
74
|
+
function isPicked(axis: string, tag: string): boolean {
|
|
75
|
+
return (picked.value[axis] ?? []).includes(tag);
|
|
76
|
+
}
|
|
77
|
+
const pickCount = computed(() =>
|
|
78
|
+
Object.values(picked.value).reduce((n, t) => n + t.length, 0),
|
|
79
|
+
);
|
|
80
|
+
const filtering = computed(
|
|
81
|
+
() =>
|
|
82
|
+
search.value.trim() !== "" ||
|
|
83
|
+
onlyNeg.value ||
|
|
84
|
+
onlyWeighted.value ||
|
|
85
|
+
onlyRefs.value ||
|
|
86
|
+
pickCount.value > 0,
|
|
87
|
+
);
|
|
88
|
+
function clearFilters(): void {
|
|
89
|
+
search.value = "";
|
|
90
|
+
onlyNeg.value = false;
|
|
91
|
+
onlyWeighted.value = false;
|
|
92
|
+
onlyRefs.value = false;
|
|
93
|
+
picked.value = {};
|
|
94
|
+
}
|
|
95
|
+
function passes(o: WildcardOption): boolean {
|
|
96
|
+
const q = search.value.trim().toLowerCase();
|
|
97
|
+
if (
|
|
98
|
+
q &&
|
|
99
|
+
!o.value.toLowerCase().includes(q) &&
|
|
100
|
+
!o.negative.toLowerCase().includes(q) &&
|
|
101
|
+
!o.tags.some((t) => t.toLowerCase().includes(q))
|
|
102
|
+
)
|
|
103
|
+
return false;
|
|
104
|
+
if (onlyNeg.value && !o.negative) return false;
|
|
105
|
+
if (onlyWeighted.value && o.weight === 1) return false;
|
|
106
|
+
if (onlyRefs.value) {
|
|
107
|
+
const s = scanText(o.value);
|
|
108
|
+
if (!s.refs && !s.picks) return false;
|
|
109
|
+
}
|
|
110
|
+
for (const tags of Object.values(picked.value)) {
|
|
111
|
+
if (tags.length && !o.tags.some((t) => tags.includes(t))) return false;
|
|
112
|
+
}
|
|
113
|
+
return true;
|
|
114
|
+
}
|
|
115
|
+
const visible = computed(() => options.value.filter(passes));
|
|
116
|
+
|
|
117
|
+
// ─── facets ───
|
|
118
|
+
const facetOpen = ref(new Set<string>());
|
|
119
|
+
function facetTags(axis: { name: string; tags: string[] }): { tag: string; count: number }[] {
|
|
120
|
+
return axis.tags
|
|
121
|
+
.map((tag) => ({ tag, count: options.value.filter((o) => o.tags.includes(tag)).length }))
|
|
122
|
+
.filter((f) => f.count > 0);
|
|
123
|
+
}
|
|
124
|
+
function shownFacets(axis: { name: string; tags: string[] }): { tag: string; count: number }[] {
|
|
125
|
+
const all = facetTags(axis);
|
|
126
|
+
return facetOpen.value.has(axis.name) ? all : all.slice(0, FACET_PAGE);
|
|
127
|
+
}
|
|
128
|
+
function toggleFacetMore(name: string): void {
|
|
129
|
+
const next = new Set(facetOpen.value);
|
|
130
|
+
if (next.has(name)) next.delete(name);
|
|
131
|
+
else next.add(name);
|
|
132
|
+
facetOpen.value = next;
|
|
133
|
+
}
|
|
134
|
+
const showFacets = computed(() => axes.value.length > 0 && options.value.length >= 20);
|
|
135
|
+
|
|
136
|
+
// ─── grouping ───
|
|
137
|
+
const groupBy = ref<string>("");
|
|
138
|
+
const groupOptions = computed<SegmentedOption[]>(() => [
|
|
139
|
+
...axes.value.map((a) => ({ value: a.name, label: a.name })),
|
|
140
|
+
{ value: "none", label: "none" },
|
|
141
|
+
]);
|
|
142
|
+
const effectiveAxis = computed(() =>
|
|
143
|
+
groupBy.value === "none" ? null : (axes.value.find((a) => a.name === groupBy.value) ?? axes.value[0] ?? null),
|
|
144
|
+
);
|
|
145
|
+
interface Group {
|
|
146
|
+
name: string;
|
|
147
|
+
options: WildcardOption[];
|
|
148
|
+
}
|
|
149
|
+
const groups = computed<Group[]>(() => {
|
|
150
|
+
const axis = effectiveAxis.value;
|
|
151
|
+
if (!axis) return visible.value.length ? [{ name: "", options: visible.value }] : [];
|
|
152
|
+
const buckets = new Map<string, WildcardOption[]>();
|
|
153
|
+
const untagged: WildcardOption[] = [];
|
|
154
|
+
for (const o of visible.value) {
|
|
155
|
+
// Each option lands ONCE: under the first of its tags this axis lists.
|
|
156
|
+
const tag = axis.tags.find((t) => o.tags.includes(t));
|
|
157
|
+
if (tag === undefined) untagged.push(o);
|
|
158
|
+
else buckets.set(tag, [...(buckets.get(tag) ?? []), o]);
|
|
159
|
+
}
|
|
160
|
+
const out: Group[] = [];
|
|
161
|
+
for (const tag of axis.tags) {
|
|
162
|
+
const opts = buckets.get(tag);
|
|
163
|
+
if (opts?.length) out.push({ name: tag, options: opts });
|
|
164
|
+
}
|
|
165
|
+
if (untagged.length) out.push({ name: "(untagged)", options: untagged });
|
|
166
|
+
return out;
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
/** Toggled groups flip their default (open for the first few; every group
|
|
170
|
+
* opens while a filter is on). */
|
|
171
|
+
const toggled = ref(new Set<string>());
|
|
172
|
+
function flip(set: Set<string>, key: string): Set<string> {
|
|
173
|
+
const next = new Set(set);
|
|
174
|
+
if (next.has(key)) next.delete(key);
|
|
175
|
+
else next.add(key);
|
|
176
|
+
return next;
|
|
177
|
+
}
|
|
178
|
+
function isOpen(name: string, index: number): boolean {
|
|
179
|
+
if (!name) return true;
|
|
180
|
+
const byDefault = filtering.value || index < OPEN_GROUPS;
|
|
181
|
+
return toggled.value.has(name) ? !byDefault : byDefault;
|
|
182
|
+
}
|
|
183
|
+
const fullGroups = ref(new Set<string>());
|
|
184
|
+
function rowsOf(g: Group): WildcardOption[] {
|
|
185
|
+
return fullGroups.value.has(g.name) ? g.options : g.options.slice(0, PAGE);
|
|
186
|
+
}
|
|
187
|
+
function share(g: Group): string {
|
|
188
|
+
if (!totalWeight.value) return "";
|
|
189
|
+
const w = g.options.reduce((n, o) => n + Math.max(0, o.weight), 0);
|
|
190
|
+
return `${pct(w)} of draws`;
|
|
191
|
+
}
|
|
192
|
+
function pct(w: number): string {
|
|
193
|
+
const p = (w / totalWeight.value) * 100;
|
|
194
|
+
if (p > 0 && p < 0.1) return "<0.1%";
|
|
195
|
+
return `${p < 10 ? p.toFixed(1) : p.toFixed(0)}%`;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// ─── rows ───
|
|
199
|
+
const view = ref<"list" | "chips">("list");
|
|
200
|
+
const VIEW_OPTIONS: SegmentedOption<"list" | "chips">[] = [
|
|
201
|
+
{ value: "list", label: "List" },
|
|
202
|
+
{ value: "chips", label: "Chips" },
|
|
203
|
+
];
|
|
204
|
+
const expanded = ref(new Set<string>());
|
|
205
|
+
function isLong(o: WildcardOption): boolean {
|
|
206
|
+
return o.value.length > LONG_CHARS;
|
|
207
|
+
}
|
|
208
|
+
</script>
|
|
209
|
+
|
|
210
|
+
<template>
|
|
211
|
+
<div class="wp-pv wp-wc">
|
|
212
|
+
<div class="wp-pv-stats">
|
|
213
|
+
<div class="wp-pv-stat">
|
|
214
|
+
<b>{{ options.length }}</b><span>option{{ options.length === 1 ? "" : "s" }}</span>
|
|
215
|
+
</div>
|
|
216
|
+
<div
|
|
217
|
+
v-if="axes.length"
|
|
218
|
+
class="wp-pv-stat"
|
|
219
|
+
>
|
|
220
|
+
<b>{{ axes.length }}</b><span>tag ax{{ axes.length === 1 ? "is" : "es" }} · {{ tagTotal }} tags</span>
|
|
221
|
+
</div>
|
|
222
|
+
<div
|
|
223
|
+
v-if="negCount"
|
|
224
|
+
class="wp-pv-stat"
|
|
225
|
+
>
|
|
226
|
+
<b>{{ negCount }}</b><span>with a negative</span>
|
|
227
|
+
</div>
|
|
228
|
+
<div
|
|
229
|
+
v-if="weightRange"
|
|
230
|
+
class="wp-pv-stat"
|
|
231
|
+
>
|
|
232
|
+
<b>{{ weightRange }}</b><span>weight range</span>
|
|
233
|
+
</div>
|
|
234
|
+
<div
|
|
235
|
+
v-if="refCount"
|
|
236
|
+
class="wp-pv-stat"
|
|
237
|
+
>
|
|
238
|
+
<b>{{ refCount }}</b><span>use picks / @refs</span>
|
|
239
|
+
</div>
|
|
240
|
+
</div>
|
|
241
|
+
|
|
242
|
+
<div class="wp-pv-tool">
|
|
243
|
+
<SearchInput
|
|
244
|
+
v-if="options.length > 8"
|
|
245
|
+
v-model="search"
|
|
246
|
+
:placeholder="`Filter ${options.length} options…`"
|
|
247
|
+
data-test="wildcard-search"
|
|
248
|
+
class="wp-pv-search"
|
|
249
|
+
/>
|
|
250
|
+
<template v-if="axes.length">
|
|
251
|
+
<span class="wp-pv-label">Group by</span>
|
|
252
|
+
<WpSegmented
|
|
253
|
+
:model-value="effectiveAxis?.name ?? 'none'"
|
|
254
|
+
:options="groupOptions"
|
|
255
|
+
size="sm"
|
|
256
|
+
aria-label="Group by"
|
|
257
|
+
test-prefix="groupby"
|
|
258
|
+
@update:model-value="(v) => (groupBy = v)"
|
|
259
|
+
/>
|
|
260
|
+
</template>
|
|
261
|
+
<Chip
|
|
262
|
+
v-if="negCount"
|
|
263
|
+
size="sm"
|
|
264
|
+
color="var(--wp-danger)"
|
|
265
|
+
:pressed="onlyNeg"
|
|
266
|
+
data-test="filter-negative"
|
|
267
|
+
@click="onlyNeg = !onlyNeg"
|
|
268
|
+
>Has negative</Chip>
|
|
269
|
+
<Chip
|
|
270
|
+
v-if="weighted"
|
|
271
|
+
size="sm"
|
|
272
|
+
:pressed="onlyWeighted"
|
|
273
|
+
@click="onlyWeighted = !onlyWeighted"
|
|
274
|
+
>Weighted</Chip>
|
|
275
|
+
<Chip
|
|
276
|
+
v-if="refCount"
|
|
277
|
+
size="sm"
|
|
278
|
+
:pressed="onlyRefs"
|
|
279
|
+
@click="onlyRefs = !onlyRefs"
|
|
280
|
+
>Uses picks / refs</Chip>
|
|
281
|
+
<span
|
|
282
|
+
v-if="filtering"
|
|
283
|
+
class="wp-pv-count"
|
|
284
|
+
data-test="wildcard-matches"
|
|
285
|
+
>
|
|
286
|
+
{{ visible.length }} of {{ options.length }}
|
|
287
|
+
<Button
|
|
288
|
+
variant="link"
|
|
289
|
+
size="sm"
|
|
290
|
+
@click="clearFilters"
|
|
291
|
+
>clear</Button>
|
|
292
|
+
</span>
|
|
293
|
+
<WpSegmented
|
|
294
|
+
v-model="view"
|
|
295
|
+
class="wp-pv-tool__end"
|
|
296
|
+
:options="VIEW_OPTIONS"
|
|
297
|
+
size="sm"
|
|
298
|
+
aria-label="Show as"
|
|
299
|
+
test-prefix="view"
|
|
300
|
+
/>
|
|
301
|
+
</div>
|
|
302
|
+
|
|
303
|
+
<div
|
|
304
|
+
class="wp-wc__split"
|
|
305
|
+
:data-facets="showFacets || null"
|
|
306
|
+
>
|
|
307
|
+
<aside
|
|
308
|
+
v-if="showFacets"
|
|
309
|
+
class="wp-wc__facets wp-pv-hide-narrow"
|
|
310
|
+
>
|
|
311
|
+
<div
|
|
312
|
+
v-for="a in axes"
|
|
313
|
+
:key="a.name"
|
|
314
|
+
class="wp-wc__facet"
|
|
315
|
+
>
|
|
316
|
+
<h5>{{ a.name }}</h5>
|
|
317
|
+
<ListRow
|
|
318
|
+
v-for="f in shownFacets(a)"
|
|
319
|
+
:key="f.tag"
|
|
320
|
+
size="sm"
|
|
321
|
+
toggle
|
|
322
|
+
class="wp-wc__facet-row"
|
|
323
|
+
:selected="isPicked(a.name, f.tag)"
|
|
324
|
+
:count="f.count"
|
|
325
|
+
:data-test="`facet-${f.tag}`"
|
|
326
|
+
@click="togglePick(a.name, f.tag)"
|
|
327
|
+
>
|
|
328
|
+
{{ f.tag }}
|
|
329
|
+
</ListRow>
|
|
330
|
+
<Button
|
|
331
|
+
v-if="facetTags(a).length > FACET_PAGE"
|
|
332
|
+
variant="link"
|
|
333
|
+
size="sm"
|
|
334
|
+
class="wp-wc__facet-more"
|
|
335
|
+
@click="toggleFacetMore(a.name)"
|
|
336
|
+
>
|
|
337
|
+
{{ facetOpen.has(a.name) ? "fewer" : `+ ${facetTags(a).length - FACET_PAGE} more` }}
|
|
338
|
+
</Button>
|
|
339
|
+
</div>
|
|
340
|
+
</aside>
|
|
341
|
+
|
|
342
|
+
<div class="wp-wildcard-groups">
|
|
343
|
+
<p
|
|
344
|
+
v-if="!groups.length"
|
|
345
|
+
class="wp-pv-empty"
|
|
346
|
+
>
|
|
347
|
+
No options match these filters.
|
|
348
|
+
</p>
|
|
349
|
+
<PreviewGroup
|
|
350
|
+
v-for="(g, gidx) in groups"
|
|
351
|
+
:key="g.name || '_'"
|
|
352
|
+
class="wp-wildcard-group"
|
|
353
|
+
:name="g.name"
|
|
354
|
+
:open="isOpen(g.name, gidx)"
|
|
355
|
+
:count="g.options.length"
|
|
356
|
+
:aside="share(g)"
|
|
357
|
+
:test-id="`group-${g.name}`"
|
|
358
|
+
@update:open="toggled = flip(toggled, g.name)"
|
|
359
|
+
>
|
|
360
|
+
<table
|
|
361
|
+
v-if="view === 'list'"
|
|
362
|
+
class="wp-pv-table"
|
|
363
|
+
>
|
|
364
|
+
<colgroup>
|
|
365
|
+
<col class="wp-pv-col-n">
|
|
366
|
+
<col>
|
|
367
|
+
<col class="wp-wc__col-chance">
|
|
368
|
+
<col
|
|
369
|
+
v-if="axes.length"
|
|
370
|
+
class="wp-pv-hide-narrow"
|
|
371
|
+
style="width: 22%"
|
|
372
|
+
>
|
|
373
|
+
<col
|
|
374
|
+
v-if="negCount"
|
|
375
|
+
style="width: 26%"
|
|
376
|
+
>
|
|
377
|
+
</colgroup>
|
|
378
|
+
<tbody>
|
|
379
|
+
<tr
|
|
380
|
+
v-for="(o, oidx) in rowsOf(g)"
|
|
381
|
+
:key="oidx"
|
|
382
|
+
class="wp-wildcard-option"
|
|
383
|
+
>
|
|
384
|
+
<td class="wp-pv-n">
|
|
385
|
+
{{ oidx + 1 }}
|
|
386
|
+
</td>
|
|
387
|
+
<td>
|
|
388
|
+
<div
|
|
389
|
+
:class="{ 'wp-pv-clamp': isLong(o) && !expanded.has(`${g.name}|${oidx}`) }"
|
|
390
|
+
class="wp-wildcard-option__value"
|
|
391
|
+
:data-long="isLong(o) || null"
|
|
392
|
+
>
|
|
393
|
+
<TokenizedValue
|
|
394
|
+
:value="o.value"
|
|
395
|
+
:uuid-to-name="refMap"
|
|
396
|
+
/>
|
|
397
|
+
</div>
|
|
398
|
+
<Button
|
|
399
|
+
v-if="isLong(o)"
|
|
400
|
+
variant="link"
|
|
401
|
+
size="sm"
|
|
402
|
+
@click="expanded = flip(expanded, `${g.name}|${oidx}`)"
|
|
403
|
+
>{{ expanded.has(`${g.name}|${oidx}`) ? "Show less" : "Show all" }}</Button>
|
|
404
|
+
</td>
|
|
405
|
+
<td class="wp-wc__chance">
|
|
406
|
+
<span>{{ totalWeight ? pct(Math.max(0, o.weight)) : "—" }}<template v-if="o.weight !== 1"> · ×{{ fmt(o.weight) }}</template></span>
|
|
407
|
+
<span
|
|
408
|
+
v-if="o.fallback"
|
|
409
|
+
class="wp-pv-fbtag"
|
|
410
|
+
data-test="option-fallback"
|
|
411
|
+
title="Rolls normally, and is also used when nothing else is left to pick."
|
|
412
|
+
>fallback</span>
|
|
413
|
+
<div class="wp-wc__bar">
|
|
414
|
+
<b :style="{ width: `${maxWeight ? (Math.max(0, o.weight) / maxWeight) * 100 : 0}%` }" />
|
|
415
|
+
</div>
|
|
416
|
+
</td>
|
|
417
|
+
<td
|
|
418
|
+
v-if="axes.length"
|
|
419
|
+
class="wp-pv-hide-narrow"
|
|
420
|
+
>
|
|
421
|
+
<span
|
|
422
|
+
v-for="t in o.tags"
|
|
423
|
+
:key="t"
|
|
424
|
+
class="wp-wc__tag"
|
|
425
|
+
>{{ t }}</span>
|
|
426
|
+
</td>
|
|
427
|
+
<td
|
|
428
|
+
v-if="negCount"
|
|
429
|
+
class="wp-pv-neg-cell"
|
|
430
|
+
>
|
|
431
|
+
<span
|
|
432
|
+
v-if="o.negative"
|
|
433
|
+
data-test="option-negative"
|
|
434
|
+
>
|
|
435
|
+
<span class="wp-pv-negtag">NEG</span>
|
|
436
|
+
<TokenizedValue
|
|
437
|
+
:value="o.negative"
|
|
438
|
+
:uuid-to-name="refMap"
|
|
439
|
+
/>
|
|
440
|
+
</span>
|
|
441
|
+
</td>
|
|
442
|
+
</tr>
|
|
443
|
+
</tbody>
|
|
444
|
+
</table>
|
|
445
|
+
<ul
|
|
446
|
+
v-else
|
|
447
|
+
class="wp-wc__chips"
|
|
448
|
+
>
|
|
449
|
+
<li
|
|
450
|
+
v-for="(o, oidx) in rowsOf(g)"
|
|
451
|
+
:key="oidx"
|
|
452
|
+
class="wp-wildcard-option"
|
|
453
|
+
:title="o.tags.join(' · ')"
|
|
454
|
+
>
|
|
455
|
+
<span :class="{ 'wp-pv-clamp': isLong(o) }">
|
|
456
|
+
<TokenizedValue
|
|
457
|
+
:value="o.value"
|
|
458
|
+
:uuid-to-name="refMap"
|
|
459
|
+
/>
|
|
460
|
+
</span>
|
|
461
|
+
<span
|
|
462
|
+
v-if="o.negative"
|
|
463
|
+
class="wp-wc__chipneg"
|
|
464
|
+
data-test="option-negative"
|
|
465
|
+
>
|
|
466
|
+
<span class="wp-pv-negtag">NEG</span>
|
|
467
|
+
<TokenizedValue
|
|
468
|
+
:value="o.negative"
|
|
469
|
+
:uuid-to-name="refMap"
|
|
470
|
+
/>
|
|
471
|
+
</span>
|
|
472
|
+
<span
|
|
473
|
+
v-if="o.fallback"
|
|
474
|
+
class="wp-pv-fbtag"
|
|
475
|
+
data-test="option-fallback"
|
|
476
|
+
title="Rolls normally, and is also used when nothing else is left to pick."
|
|
477
|
+
>fallback</span>
|
|
478
|
+
<span
|
|
479
|
+
v-if="o.weight !== 1"
|
|
480
|
+
class="wp-wc__w"
|
|
481
|
+
>×{{ fmt(o.weight) }}</span>
|
|
482
|
+
</li>
|
|
483
|
+
</ul>
|
|
484
|
+
<div
|
|
485
|
+
v-if="g.options.length > PAGE"
|
|
486
|
+
class="wp-pv-more"
|
|
487
|
+
>
|
|
488
|
+
<Button
|
|
489
|
+
variant="link"
|
|
490
|
+
size="sm"
|
|
491
|
+
:data-test="`more-${g.name || '_'}`"
|
|
492
|
+
@click="fullGroups = flip(fullGroups, g.name)"
|
|
493
|
+
>
|
|
494
|
+
{{
|
|
495
|
+
fullGroups.has(g.name)
|
|
496
|
+
? "Show fewer"
|
|
497
|
+
: `Show ${g.options.length - PAGE} more${g.name ? ` in ${g.name}` : ""}`
|
|
498
|
+
}}
|
|
499
|
+
</Button>
|
|
500
|
+
</div>
|
|
501
|
+
</PreviewGroup>
|
|
502
|
+
</div>
|
|
503
|
+
</div>
|
|
504
|
+
</div>
|
|
505
|
+
</template>
|
|
506
|
+
|
|
507
|
+
<style scoped src="./preview.css"></style>
|
|
508
|
+
<style scoped>
|
|
509
|
+
.wp-wc__split[data-facets] {
|
|
510
|
+
display: grid;
|
|
511
|
+
grid-template-columns: 190px minmax(0, 1fr);
|
|
512
|
+
}
|
|
513
|
+
@container (max-width: 640px) {
|
|
514
|
+
.wp-wc__split[data-facets] {
|
|
515
|
+
grid-template-columns: minmax(0, 1fr);
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
.wp-wc__facets {
|
|
519
|
+
border-right: 1px solid var(--wp-border);
|
|
520
|
+
padding: 6px 0 12px;
|
|
521
|
+
}
|
|
522
|
+
.wp-wc__facet h5 {
|
|
523
|
+
margin: 10px 16px 4px;
|
|
524
|
+
font-size: 10.5px;
|
|
525
|
+
font-weight: 600;
|
|
526
|
+
letter-spacing: 0.06em;
|
|
527
|
+
color: var(--wp-text-dim);
|
|
528
|
+
text-transform: uppercase;
|
|
529
|
+
}
|
|
530
|
+
.wp-wc__facet-more {
|
|
531
|
+
margin: 2px 12px 0;
|
|
532
|
+
}
|
|
533
|
+
.wp-wc__chance span {
|
|
534
|
+
font-family: var(--wp-font-mono);
|
|
535
|
+
font-size: 11px;
|
|
536
|
+
color: var(--wp-text-muted);
|
|
537
|
+
white-space: nowrap;
|
|
538
|
+
}
|
|
539
|
+
.wp-wc__chance .wp-pv-fbtag {
|
|
540
|
+
margin-left: 6px;
|
|
541
|
+
font-size: 10px;
|
|
542
|
+
color: var(--wp-accent-text);
|
|
543
|
+
}
|
|
544
|
+
.wp-wc__col-chance {
|
|
545
|
+
width: 112px;
|
|
546
|
+
}
|
|
547
|
+
@container (max-width: 640px) {
|
|
548
|
+
.wp-wc__col-chance {
|
|
549
|
+
width: 56px;
|
|
550
|
+
}
|
|
551
|
+
.wp-wc__bar {
|
|
552
|
+
display: none;
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
.wp-wc__bar {
|
|
556
|
+
height: 4px;
|
|
557
|
+
margin-top: 6px;
|
|
558
|
+
border-radius: min(2px, var(--wp-radius-cap, 999px));
|
|
559
|
+
background: var(--wp-bg-4);
|
|
560
|
+
}
|
|
561
|
+
.wp-wc__bar b {
|
|
562
|
+
display: block;
|
|
563
|
+
height: 4px;
|
|
564
|
+
border-radius: min(2px, var(--wp-radius-cap, 999px));
|
|
565
|
+
background: var(--wp-accent-500);
|
|
566
|
+
}
|
|
567
|
+
.wp-wc__tag {
|
|
568
|
+
display: inline-block;
|
|
569
|
+
margin: 0 3px 3px 0;
|
|
570
|
+
padding: 0 6px;
|
|
571
|
+
border-radius: min(4px, var(--wp-radius-cap, 999px));
|
|
572
|
+
background: var(--wp-bg-3);
|
|
573
|
+
color: var(--wp-text-muted);
|
|
574
|
+
font-size: 10.5px;
|
|
575
|
+
}
|
|
576
|
+
.wp-wc__chips {
|
|
577
|
+
list-style: none;
|
|
578
|
+
margin: 0;
|
|
579
|
+
padding: 10px 16px 12px;
|
|
580
|
+
display: flex;
|
|
581
|
+
flex-wrap: wrap;
|
|
582
|
+
gap: 6px;
|
|
583
|
+
border-bottom: 1px solid var(--wp-border);
|
|
584
|
+
}
|
|
585
|
+
.wp-wc__chips li {
|
|
586
|
+
display: inline-flex;
|
|
587
|
+
align-items: center;
|
|
588
|
+
gap: 6px;
|
|
589
|
+
max-width: 100%;
|
|
590
|
+
padding: 3px 10px;
|
|
591
|
+
border-radius: min(10px, var(--wp-radius-cap, 999px));
|
|
592
|
+
background: var(--wp-bg-3);
|
|
593
|
+
color: var(--wp-text);
|
|
594
|
+
font-size: 13px;
|
|
595
|
+
}
|
|
596
|
+
.wp-wc__chipneg {
|
|
597
|
+
padding-left: 6px;
|
|
598
|
+
border-left: 1px solid var(--wp-border);
|
|
599
|
+
color: var(--wp-danger-text, var(--wp-danger));
|
|
600
|
+
font-size: 12px;
|
|
601
|
+
}
|
|
602
|
+
.wp-wc__w {
|
|
603
|
+
font-family: var(--wp-font-mono);
|
|
604
|
+
font-size: 10.5px;
|
|
605
|
+
color: var(--wp-text-muted);
|
|
606
|
+
}
|
|
607
|
+
</style>
|