@dumiflex/wp-kit 0.6.0 → 0.7.1

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 CHANGED
@@ -1,5 +1,27 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.7.1](https://github.com/DumiFlex/wp-kit/compare/v0.7.0...v0.7.1) (2026-11-03)
4
+
5
+
6
+ ### Bug Fixes
7
+
8
+ * payload previews load their sheet once; SelectionBar says "1 report selected" ([#75](https://github.com/DumiFlex/wp-kit/issues/75)) ([d2774da](https://github.com/DumiFlex/wp-kit/commit/d2774dae3970f3b093778b0d84c5480a43961f9d))
9
+
10
+ ## [0.7.0](https://github.com/DumiFlex/wp-kit/compare/v0.6.0...v0.7.0) (2026-11-03)
11
+
12
+
13
+ ### Features
14
+
15
+ * IconPicker all (every PrimeIcons glyph, searchable), inline, color and allow-none ([#70](https://github.com/DumiFlex/wp-kit/issues/70)) ([a0dc837](https://github.com/DumiFlex/wp-kit/commit/a0dc83716441c8061428e69faf751ab7b52ed8ae))
16
+ * RichText, the module payload previews and the post loading shapes move into the kit ([#71](https://github.com/DumiFlex/wp-kit/issues/71)) ([b92d62f](https://github.com/DumiFlex/wp-kit/commit/b92d62f87ecd44cb205d2265d7d56c9018e29144))
17
+ * SelectionBar, the one bulk-action bar over a list of ticked rows ([#69](https://github.com/DumiFlex/wp-kit/issues/69)) ([13a51e2](https://github.com/DumiFlex/wp-kit/commit/13a51e22b0e27b404700dc6f3fb2f34fd912d10a))
18
+ * SettingGroup and SettingRow, the settings page rows both apps use ([#68](https://github.com/DumiFlex/wp-kit/issues/68)) ([20f4ff8](https://github.com/DumiFlex/wp-kit/commit/20f4ff8a8c28da63fcabfd9f2c0c2bf28a4f60dd))
19
+
20
+
21
+ ### Bug Fixes
22
+
23
+ * draw WorkingLine's marks on a canvas so they glide in Firefox ([#67](https://github.com/DumiFlex/wp-kit/issues/67)) ([6565f15](https://github.com/DumiFlex/wp-kit/commit/6565f1575ecb224ad9e94efdef611f5c31b12849))
24
+
3
25
  ## [0.6.0](https://github.com/DumiFlex/wp-kit/compare/v0.5.0...v0.6.0) (2026-11-02)
4
26
 
5
27
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dumiflex/wp-kit",
3
- "version": "0.6.0",
3
+ "version": "0.7.1",
4
4
  "description": "Vue 3 component kit shared by the Wildcard Pipeline manager, canvas and community site.",
5
5
  "keywords": [
6
6
  "vue",
@@ -0,0 +1,255 @@
1
+ <script setup lang="ts">
2
+ // The shared preview sheet (wp-pv-* classes), loaded once for every preview.
3
+ import "./preview.css";
4
+ /**
5
+ * Bundle: what the bundle needs from outside vs what it sets (folded from
6
+ * its children), then a child list beside the selected child's own full
7
+ * preview. Narrow hosts stack the list above the preview.
8
+ */
9
+ import { computed, ref } from "vue";
10
+ import PayloadPreview from "./PayloadPreview.vue";
11
+ import ListRow from "./ListRow.vue";
12
+ import Chip from "./Chip.vue";
13
+ import { itemType, kindColour, kindIcon, moduleSubtype } from "./kind-colour";
14
+ import {
15
+ derivationRules,
16
+ fixedValues,
17
+ innerOf,
18
+ readArray,
19
+ readGroups,
20
+ readObject,
21
+ readString,
22
+ varFlow,
23
+ wildcardOptions,
24
+ type Obj,
25
+ } from "./preview-readers";
26
+
27
+ const props = defineProps<{
28
+ payload: Obj;
29
+ refNames: Record<string, string>;
30
+ refGroups: Record<string, Record<string, string[]>>;
31
+ }>();
32
+
33
+ interface Child {
34
+ key: string;
35
+ id: string;
36
+ name: string;
37
+ type: string;
38
+ enabled: boolean;
39
+ raw: Obj;
40
+ }
41
+ function childName(c: unknown): string | null {
42
+ const meta = readObject(c, "meta");
43
+ return (
44
+ readString(meta, "name") ??
45
+ readString(meta, "library_name") ??
46
+ readString(c, "name") ??
47
+ readString(readObject(c, "payload"), "var_binding")
48
+ );
49
+ }
50
+ const children = computed<Child[]>(() =>
51
+ readArray(props.payload, "children").map((c, i) => ({
52
+ key: readString(c, "id") ?? String(i),
53
+ id: readString(c, "id") ?? "",
54
+ name: childName(c) ?? "—",
55
+ type: readString(c, "type") ?? "—",
56
+ enabled: (c as Obj | null)?.["enabled"] !== false,
57
+ raw: (c && typeof c === "object" ? c : {}) as Obj,
58
+ })),
59
+ );
60
+ /** Sibling names + wildcard axes so a child constraint resolves its
61
+ * wildcards the same way the list does. */
62
+ const childRefNames = computed(() => {
63
+ const out: Record<string, string> = { ...props.refNames };
64
+ for (const c of children.value) if (c.id) out[c.id] = c.name;
65
+ return out;
66
+ });
67
+ const childRefGroups = computed(() => {
68
+ const out: Record<string, Record<string, string[]>> = { ...props.refGroups };
69
+ for (const c of children.value) {
70
+ const g = readGroups(innerOf(c.raw), "tag_groups");
71
+ if (c.id && g) out[c.id] = g;
72
+ }
73
+ return out;
74
+ });
75
+ function summary(c: Child): string {
76
+ const inner = innerOf(c.raw);
77
+ const label = c.type.replace(/_/g, " ");
78
+ switch (c.type) {
79
+ case "wildcard": {
80
+ const n = wildcardOptions(inner).length;
81
+ const b = readString(inner, "var_binding");
82
+ return `${label} · ${n} option${n === 1 ? "" : "s"}${b ? ` · $${b}` : ""}`;
83
+ }
84
+ case "fixed_values": {
85
+ const n = fixedValues(inner).length;
86
+ return `${label} · ${n} value${n === 1 ? "" : "s"}`;
87
+ }
88
+ case "derivation": {
89
+ const n = derivationRules(inner).length;
90
+ return `${label} · ${n} rule${n === 1 ? "" : "s"}`;
91
+ }
92
+ case "combine": {
93
+ const out = readString(inner, "output_var");
94
+ return out ? `${label} · $${out}` : label;
95
+ }
96
+ case "constraint": {
97
+ const s = readString(inner, "source_wildcard_id");
98
+ const t = readString(inner, "target_wildcard_id");
99
+ const name = (id: string | null): string => (id ? (childRefNames.value[id] ?? id) : "?");
100
+ return `${label} · ${name(s)} → ${name(t)}`;
101
+ }
102
+ default:
103
+ return label;
104
+ }
105
+ }
106
+ const flow = computed(() => varFlow(props.payload));
107
+ const provide = computed(() => flow.value.reads.filter((v) => !flow.value.sets.includes(v)));
108
+
109
+ const selected = ref<string | null>(null);
110
+ const current = computed(
111
+ () => children.value.find((c) => c.key === selected.value) ?? children.value[0] ?? null,
112
+ );
113
+ const counts = computed(() => {
114
+ const out: Record<string, number> = {};
115
+ for (const c of children.value) out[c.type] = (out[c.type] ?? 0) + 1;
116
+ return out;
117
+ });
118
+ </script>
119
+
120
+ <template>
121
+ <div class="wp-pv wp-bundle">
122
+ <div class="wp-pv-stats">
123
+ <div class="wp-pv-stat">
124
+ <b>{{ children.length }}</b><span>module{{ children.length === 1 ? "" : "s" }}</span>
125
+ </div>
126
+ <div
127
+ v-for="(n, t) in counts"
128
+ :key="t"
129
+ class="wp-pv-stat wp-pv-hide-narrow"
130
+ >
131
+ <b>{{ n }}</b><span>{{ String(t).replace(/_/g, " ") }}</span>
132
+ </div>
133
+ </div>
134
+ <div
135
+ v-if="provide.length || flow.sets.length"
136
+ class="wp-bd-flow"
137
+ >
138
+ <template v-if="provide.length">
139
+ <span class="wp-pv-label">You provide</span>
140
+ <div>
141
+ <code
142
+ v-for="v in provide"
143
+ :key="`p-${v}`"
144
+ class="wp-bd-var"
145
+ >${{ v }}</code>
146
+ <span class="wp-bd-hint">read inside, not set by any module here</span>
147
+ </div>
148
+ </template>
149
+ <template v-if="flow.sets.length">
150
+ <span class="wp-pv-label">It sets</span>
151
+ <div>
152
+ <code
153
+ v-for="v in flow.sets"
154
+ :key="`s-${v}`"
155
+ class="wp-bd-var"
156
+ >${{ v }}</code>
157
+ </div>
158
+ </template>
159
+ </div>
160
+ <div class="wp-bd-split">
161
+ <nav
162
+ class="wp-bd-kids wp-bundle-children"
163
+ data-wp-rows
164
+ >
165
+ <ListRow
166
+ v-for="c in children"
167
+ :key="c.key"
168
+ class="wp-bd-kid wp-bundle-child"
169
+ :selected="current?.key === c.key"
170
+ :icon="kindIcon(c.type)"
171
+ :description="`${summary(c)}${c.enabled ? '' : ' · disabled'}`"
172
+ :muted="!c.enabled"
173
+ data-test="bundle-child"
174
+ @click="selected = c.key"
175
+ >
176
+ {{ c.name }}
177
+ </ListRow>
178
+ </nav>
179
+ <div
180
+ v-if="current"
181
+ class="wp-bd-pane"
182
+ data-test="bundle-child-body"
183
+ >
184
+ <div class="wp-bd-pane__head">
185
+ <Chip v-if="moduleSubtype(current.type)" :color="kindColour(itemType('module', moduleSubtype(current.type)))">{{ itemType('module', moduleSubtype(current.type)) }}</Chip>
186
+ <Chip v-else>{{ current.type }}</Chip>
187
+ <b>{{ current.name }}</b>
188
+ </div>
189
+ <PayloadPreview
190
+ :payload="current.raw"
191
+ :ref-names="childRefNames"
192
+ :ref-groups="childRefGroups"
193
+ nested
194
+ />
195
+ </div>
196
+ </div>
197
+ </div>
198
+ </template>
199
+
200
+ <style scoped>
201
+ .wp-bd-flow {
202
+ display: grid;
203
+ grid-template-columns: auto minmax(0, 1fr);
204
+ gap: 6px 14px;
205
+ align-items: baseline;
206
+ padding: 12px 16px;
207
+ border-bottom: 1px solid var(--wp-border);
208
+ }
209
+ .wp-bd-flow > div {
210
+ display: flex;
211
+ flex-wrap: wrap;
212
+ gap: 6px;
213
+ align-items: baseline;
214
+ }
215
+ .wp-bd-var {
216
+ font-family: var(--wp-font-mono);
217
+ font-size: 12px;
218
+ color: var(--wp-success);
219
+ }
220
+ .wp-bd-hint {
221
+ font-size: 11.5px;
222
+ color: var(--wp-text-dim);
223
+ }
224
+ .wp-bd-split {
225
+ display: grid;
226
+ grid-template-columns: 240px minmax(0, 1fr);
227
+ }
228
+ @container (max-width: 700px) {
229
+ .wp-bd-split {
230
+ grid-template-columns: minmax(0, 1fr);
231
+ }
232
+ .wp-bd-kids {
233
+ border-right: 0;
234
+ border-bottom: 1px solid var(--wp-border);
235
+ }
236
+ }
237
+ .wp-bd-kids {
238
+ display: flex;
239
+ flex-direction: column;
240
+ gap: 2px;
241
+ padding: 8px;
242
+ border-right: 1px solid var(--wp-border);
243
+ }
244
+ .wp-bd-pane {
245
+ min-width: 0;
246
+ }
247
+ .wp-bd-pane__head {
248
+ display: flex;
249
+ align-items: center;
250
+ gap: 10px;
251
+ padding: 12px 16px;
252
+ border-bottom: 1px solid var(--wp-border);
253
+ font-size: 13px;
254
+ }
255
+ </style>
@@ -0,0 +1,151 @@
1
+ <script setup lang="ts">
2
+ // The shared preview sheet (wp-pv-* classes), loaded once for every preview.
3
+ import "./preview.css";
4
+ /** Combine: the template in its own block, its negative under it, and a
5
+ * list of the variables it reads with where each one is used. */
6
+ import { computed, ref } from "vue";
7
+ import TokenizedValue from "./TokenizedValue.vue";
8
+ import Button from "./Button.vue";
9
+ import { readString, readStrings, scanText, type Obj } from "./preview-readers";
10
+
11
+ const props = defineProps<{
12
+ inner: Obj | null;
13
+ refMap: ReadonlyMap<string, string>;
14
+ }>();
15
+
16
+ const PAGE = 6;
17
+
18
+ const template = computed(() => readString(props.inner, "template") ?? "");
19
+ const negative = computed(() => readString(props.inner, "negative") ?? "");
20
+ const output = computed(() => readString(props.inner, "output_var"));
21
+ const tplScan = computed(() => scanText(template.value));
22
+ const negScan = computed(() => scanText(negative.value));
23
+
24
+ interface InputRow {
25
+ name: string;
26
+ where: string;
27
+ }
28
+ const inputs = computed<InputRow[]>(() => {
29
+ const names = [
30
+ ...new Set([
31
+ ...readStrings(props.inner, "input_vars"),
32
+ ...tplScan.value.vars,
33
+ ...negScan.value.vars,
34
+ ...negScan.value.negVars,
35
+ ]),
36
+ ];
37
+ return names.map((name) => {
38
+ const where: string[] = [];
39
+ if (tplScan.value.vars.includes(name)) where.push("template");
40
+ if (negScan.value.vars.includes(name)) where.push("negative");
41
+ if (negScan.value.negVars.includes(name) || tplScan.value.negVars.includes(name))
42
+ where.push("negative (.neg)");
43
+ return { name, where: where.join(", ") || "declared, not used" };
44
+ });
45
+ });
46
+ const showAll = ref(false);
47
+ const shown = computed(() => (showAll.value ? inputs.value : inputs.value.slice(0, PAGE)));
48
+ </script>
49
+
50
+ <template>
51
+ <div class="wp-pv">
52
+ <div class="wp-pv-stats">
53
+ <div class="wp-pv-stat">
54
+ <b>{{ inputs.length }}</b><span>input{{ inputs.length === 1 ? "" : "s" }}</span>
55
+ </div>
56
+ <div
57
+ v-if="tplScan.picks"
58
+ class="wp-pv-stat"
59
+ >
60
+ <b>{{ tplScan.picks }}</b><span>inline pick{{ tplScan.picks === 1 ? "" : "s" }}</span>
61
+ </div>
62
+ <div class="wp-pv-stat">
63
+ <b>{{ negative ? "yes" : "no" }}</b><span>negative</span>
64
+ </div>
65
+ </div>
66
+ <div class="wp-pv-sec">
67
+ Template<template v-if="output">
68
+ → <span class="wp-cb__out">${{ output }}</span>
69
+ </template>
70
+ </div>
71
+ <div
72
+ class="wp-pv-block wp-combine__template"
73
+ data-test="combine-template"
74
+ >
75
+ <TokenizedValue
76
+ :value="template"
77
+ :uuid-to-name="refMap"
78
+ />
79
+ </div>
80
+ <template v-if="negative">
81
+ <div class="wp-pv-sec wp-cb__neglabel">
82
+ Negative
83
+ </div>
84
+ <div
85
+ class="wp-pv-block wp-pv-block--neg"
86
+ data-test="combine-negative"
87
+ >
88
+ <TokenizedValue
89
+ :value="negative"
90
+ :uuid-to-name="refMap"
91
+ />
92
+ </div>
93
+ </template>
94
+ <template v-if="inputs.length">
95
+ <div class="wp-pv-sec">
96
+ Inputs
97
+ </div>
98
+ <table class="wp-pv-table">
99
+ <colgroup>
100
+ <col style="width: 180px">
101
+ <col>
102
+ </colgroup>
103
+ <tbody>
104
+ <tr
105
+ v-for="i in shown"
106
+ :key="i.name"
107
+ data-test="combine-input"
108
+ >
109
+ <td class="wp-pv-var">
110
+ ${{ i.name }}
111
+ </td>
112
+ <td :class="{ 'wp-cb__unused': i.where === 'declared, not used' }">
113
+ {{ i.where }}
114
+ </td>
115
+ </tr>
116
+ </tbody>
117
+ </table>
118
+ <div
119
+ v-if="inputs.length > PAGE"
120
+ class="wp-pv-more"
121
+ >
122
+ <Button
123
+ variant="link"
124
+ size="sm"
125
+ @click="showAll = !showAll"
126
+ >
127
+ {{ showAll ? "Show fewer" : `Show ${inputs.length - PAGE} more` }}
128
+ </Button>
129
+ </div>
130
+ </template>
131
+ <div class="wp-cb__pad" />
132
+ </div>
133
+ </template>
134
+
135
+ <style scoped>
136
+ .wp-cb__out {
137
+ font-family: var(--wp-font-mono);
138
+ color: var(--wp-success);
139
+ text-transform: none;
140
+ letter-spacing: 0;
141
+ }
142
+ .wp-cb__neglabel {
143
+ color: var(--wp-danger-text, var(--wp-danger));
144
+ }
145
+ .wp-cb__unused {
146
+ color: var(--wp-text-dim);
147
+ }
148
+ .wp-cb__pad {
149
+ height: 6px;
150
+ }
151
+ </style>
@@ -29,8 +29,12 @@ import {
29
29
  groupHue as tagHue,
30
30
  groupLabel,
31
31
  isBucket,
32
+ MODE_GLYPH,
33
+ MODE_LABEL,
34
+ normalizeMode,
32
35
  orderByGroups,
33
36
  toGroups,
37
+ type CellMode,
34
38
  } from "./matrix-axis";
35
39
 
36
40
  const props = defineProps<{
@@ -53,27 +57,7 @@ const colGroups = computed(() => toGroups(orderedCols.value));
53
57
  const hasColBands = computed(() => colGroups.value.some((g) => !isBucket(g)));
54
58
  const hasRowGroups = computed(() => rowGroups.value.some((g) => !isBucket(g)));
55
59
 
56
- // `only` (schema v6, linked picks): when the source fires, targets without a
57
- // rule of their own in that row are excluded.
58
- type CnMode = "allow" | "boost" | "reduce" | "exclude" | "only";
59
- const MODE_GLYPH: Record<string, string> = {
60
- only: "✓",
61
- boost: "↑",
62
- reduce: "↓",
63
- exclude: "×",
64
- allow: "·",
65
- };
66
- const MODE_LABEL: Record<CnMode, string> = {
67
- boost: "Boost",
68
- reduce: "Reduce",
69
- exclude: "Exclude",
70
- allow: "Neutral",
71
- only: "Only",
72
- };
73
-
74
- function normalizeMode(raw: string | null | undefined): CnMode {
75
- return raw === "exclude" || raw === "boost" || raw === "reduce" || raw === "only" ? raw : "allow";
76
- }
60
+ type CnMode = CellMode;
77
61
 
78
62
  /** A row holding an `only` cell shuts out its neutral cells at runtime; the
79
63
  * preview marks them so the link reads the same as in the extension. */
@@ -296,7 +280,7 @@ function fmtFactor(f: number): string {
296
280
  class="wp-cn-exc__mode"
297
281
  :class="`wp-cn-exc__mode--${exc.mode}`"
298
282
  >
299
- <span class="wp-cn-exc__glyph">{{ MODE_GLYPH[exc.mode] }}</span>
283
+ <span class="wp-cn-exc__glyph">{{ MODE_GLYPH[normalizeMode(exc.mode)] }}</span>
300
284
  {{ MODE_LABEL[normalizeMode(exc.mode)] }}
301
285
  </span>
302
286
  <span