@dumiflex/wp-kit 0.5.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 +47 -0
- package/README.md +5 -0
- package/package.json +4 -3
- package/src/Avatar.vue +1 -1
- package/src/Banner.vue +4 -4
- package/src/BundlePreview.vue +254 -0
- package/src/Button.vue +73 -44
- package/src/Checkbox.vue +201 -63
- package/src/Chip.vue +5 -2
- package/src/CodeBlock.vue +1 -1
- package/src/ColorField.vue +60 -6
- package/src/CombinePreview.vue +150 -0
- package/src/Combobox.vue +10 -14
- package/src/ConfirmModal.vue +12 -22
- package/src/ConstraintMatrixPreview.vue +6 -22
- package/src/ConstraintPreview.vue +259 -0
- package/src/ContextMenu.vue +11 -30
- package/src/CopyButton.vue +1 -1
- package/src/DataTable.vue +0 -2
- package/src/DatePicker.vue +1 -1
- package/src/DerivationCondition.vue +68 -0
- package/src/DerivationPreview.vue +345 -0
- package/src/Disclosure.vue +2 -1
- package/src/Drawer.vue +2 -2
- package/src/DropZone.vue +2 -2
- package/src/FixedValuesPreview.vue +184 -0
- package/src/HsvPicker.vue +2 -2
- package/src/IconPicker.vue +32 -103
- package/src/IconPickerPanel.vue +132 -0
- package/src/ImageGrid.vue +2 -2
- package/src/Lightbox.vue +2 -2
- package/src/LoadingMark.vue +6 -61
- package/src/Modal.vue +109 -44
- package/src/ModalShell.vue +26 -5
- package/src/Pagination.vue +4 -4
- package/src/PayloadPreview.vue +154 -0
- package/src/Popover.vue +68 -12
- 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/RadioGroup.vue +5 -4
- package/src/RichText.vue +61 -0
- package/src/SearchInput.vue +1 -1
- package/src/Select.vue +53 -158
- package/src/SelectionBar.vue +90 -0
- package/src/SettingGroup.vue +57 -0
- package/src/SettingRow.vue +127 -0
- package/src/Skeleton.vue +1 -1
- package/src/Slider.vue +166 -55
- package/src/SortableList.vue +1 -1
- package/src/TagInput.vue +1 -1
- package/src/Timeline.vue +2 -2
- package/src/WildcardPreview.vue +607 -0
- package/src/WorkingLine.vue +234 -0
- package/src/WpIconButton.vue +3 -2
- package/src/WpImageZoom.vue +7 -7
- package/src/WpSegmented.vue +3 -1
- package/src/WpSwitch.vue +1 -1
- package/src/WpTabs.vue +1 -1
- package/src/_modal-motion.css +1 -1
- package/src/button.css +8 -6
- package/src/canvas-motion.ts +251 -0
- package/src/floating.ts +5 -0
- package/src/hsv.ts +1 -1
- 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/menu.css +51 -0
- package/src/motion.css +73 -0
- package/src/pi-icons.ts +51 -0
- package/src/preview-readers.ts +401 -0
- package/src/preview.css +194 -0
- package/src/slider-math.ts +45 -1
- package/src/theme/looks.css +6 -19
- package/src/theme/tokens.css +12 -1108
- package/src/tokens.ts +15 -4
- package/src/tooltip.css +8 -5
- package/src/useDialogFocus.ts +11 -2
- package/src/working-line-motion.ts +135 -0
- package/src/CloseButton.vue +0 -69
- package/src/ColorPicker.vue +0 -201
- package/src/ConfirmDialog.vue +0 -165
- package/src/RangeSlider.vue +0 -183
- package/src/Toggle.vue +0 -36
- package/src/WpButton.vue +0 -75
- package/src/WpCheck.vue +0 -104
- package/src/_skeleton.css +0 -50
package/src/Combobox.vue
CHANGED
|
@@ -175,7 +175,7 @@ function setOpen(v: boolean): void {
|
|
|
175
175
|
<!-- Popover's autofocus prop is off here: focus stays in the box you type in. -->
|
|
176
176
|
<!-- eslint-disable vuejs-accessibility/no-autofocus -->
|
|
177
177
|
<template>
|
|
178
|
-
<Popover :open="open" role="group" match-width :autofocus="false" :label="`${ariaLabel} choices`" @update:open="setOpen">
|
|
178
|
+
<Popover :open="open" variant="menu" role="group" match-width :max-height="280" :autofocus="false" :label="`${ariaLabel} choices`" @update:open="setOpen">
|
|
179
179
|
<template #trigger>
|
|
180
180
|
<!-- A click on the box's padding focuses the text field, which has the keys. -->
|
|
181
181
|
<!-- eslint-disable-next-line vuejs-accessibility/click-events-have-key-events, vuejs-accessibility/no-static-element-interactions -->
|
|
@@ -245,7 +245,7 @@ function setOpen(v: boolean): void {
|
|
|
245
245
|
v-for="(o, i) in matches"
|
|
246
246
|
:id="`${listId}-${i}`"
|
|
247
247
|
:key="String(o.value)"
|
|
248
|
-
class="wp-combo__opt"
|
|
248
|
+
class="wp-combo__opt wp-menu__item"
|
|
249
249
|
role="option"
|
|
250
250
|
:aria-selected="isPicked(o.value)"
|
|
251
251
|
:aria-disabled="o.disabled || undefined"
|
|
@@ -265,6 +265,7 @@ function setOpen(v: boolean): void {
|
|
|
265
265
|
</template>
|
|
266
266
|
|
|
267
267
|
<style scoped src="./field.css"></style>
|
|
268
|
+
<style scoped src="./menu.css"></style>
|
|
268
269
|
|
|
269
270
|
<style scoped>
|
|
270
271
|
.wp-combo {
|
|
@@ -284,7 +285,7 @@ function setOpen(v: boolean): void {
|
|
|
284
285
|
color: var(--wp-text); font-size: var(--wp-text-xs); line-height: 1;
|
|
285
286
|
}
|
|
286
287
|
.wp-combo[data-size="sm"] .wp-combo__chip { height: 16px; padding-left: 5px; font-size: 10.5px; }
|
|
287
|
-
.wp-combo__chip-text { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
288
|
+
.wp-combo__chip-text { min-width: 0; line-height: 1.35; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
288
289
|
.wp-combo__remove, .wp-combo__clear {
|
|
289
290
|
display: inline-grid; place-items: center; width: 16px; height: 16px; padding: 0;
|
|
290
291
|
border: 0; border-radius: min(50%, var(--wp-radius-cap, 999px)); background: transparent; color: var(--wp-text-muted); cursor: pointer;
|
|
@@ -299,7 +300,7 @@ function setOpen(v: boolean): void {
|
|
|
299
300
|
position: absolute; right: 10px; top: 50%; transform: translateY(-50%);
|
|
300
301
|
color: var(--wp-text-muted); font-size: 10px; pointer-events: none;
|
|
301
302
|
}
|
|
302
|
-
.wp-combo[data-size="sm"] .wp-combo__caret { right: 8px; font-size:
|
|
303
|
+
.wp-combo[data-size="sm"] .wp-combo__caret { right: 8px; font-size: 10px; }
|
|
303
304
|
.wp-combo__field {
|
|
304
305
|
flex: 1 0 6ch; min-width: 6ch; height: calc(var(--_h) - 8px); margin: 0; padding: 0 2px;
|
|
305
306
|
border: 0; outline: none; background: transparent; color: inherit; font: inherit;
|
|
@@ -311,20 +312,15 @@ function setOpen(v: boolean): void {
|
|
|
311
312
|
position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0; overflow: hidden;
|
|
312
313
|
clip: rect(0 0 0 0); white-space: nowrap; border: 0;
|
|
313
314
|
}
|
|
314
|
-
|
|
315
|
-
.
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
color: var(--wp-text); font-size: var(--wp-text-sm); cursor: pointer;
|
|
319
|
-
}
|
|
320
|
-
.wp-combo__opt:hover, .wp-combo__opt[data-active] { background: var(--wp-row-hover); }
|
|
321
|
-
.wp-combo__opt[data-active] { box-shadow: inset 2px 0 0 var(--wp-accent); }
|
|
322
|
-
.wp-combo__opt[aria-disabled="true"] { opacity: 0.45; cursor: not-allowed; }
|
|
315
|
+
/* The panel and the rows' padding, corners, text and states are the shared
|
|
316
|
+
* menu look (Popover variant="menu" + menu.css); the list scrolls in the panel. */
|
|
317
|
+
.wp-combo__list { margin: 0; padding: 0; list-style: none; }
|
|
318
|
+
.wp-combo__opt { display: flex; align-items: flex-start; gap: var(--wp-space-2); }
|
|
323
319
|
.wp-combo__tick { width: 14px; margin-top: 2px; font-size: 10px; color: var(--wp-accent); visibility: hidden; }
|
|
324
320
|
.wp-combo__tick[data-on] { visibility: visible; }
|
|
325
321
|
.wp-combo__opt-text { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
|
|
326
322
|
.wp-combo__hint { color: var(--wp-text-muted); font-size: var(--wp-text-xs); }
|
|
327
|
-
.wp-combo__empty { padding: 6px
|
|
323
|
+
.wp-combo__empty { padding: 6px 8px; color: var(--wp-text-muted); font-size: 12px; }
|
|
328
324
|
@media (prefers-reduced-motion: reduce) {
|
|
329
325
|
.wp-combo { transition: none; }
|
|
330
326
|
}
|
package/src/ConfirmModal.vue
CHANGED
|
@@ -2,22 +2,23 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* Ask before an action, in the manager's own dialog.
|
|
4
4
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
5
|
+
* Built on Modal: the same backdrop, focus trap, Escape and focus return as
|
|
6
|
+
* every other dialog, with a Cancel button and a confirm button that names
|
|
7
|
+
* the action. One confirm for the manager, the canvas and the community site
|
|
8
|
+
* (ConfirmDialog was folded in: `visible` → `v-model:open`/`open`,
|
|
9
|
+
* `variant="danger"` → `tone="danger"`); it wears the colours of its side.
|
|
9
10
|
*
|
|
10
11
|
* Escape, the backdrop, the ✕ and Cancel all emit `cancel`. Enter confirms
|
|
11
12
|
* unless focus is on a button or a text box (which then gets the key itself).
|
|
12
|
-
*
|
|
13
|
-
*
|
|
13
|
+
* Data-test names: `confirm-title`, `confirm-body`, `confirm-cancel`,
|
|
14
|
+
* `confirm-confirm`.
|
|
14
15
|
*
|
|
15
16
|
* `requireType` makes the user type that exact text before Confirm works
|
|
16
17
|
* (deleting an account); `confirmDisabled` holds Confirm off for a gate the
|
|
17
18
|
* page owns (a required reason in the slot). `warn` asks about something
|
|
18
19
|
* that can be undone but matters: an outlined button and an info mark.
|
|
19
20
|
*/
|
|
20
|
-
import { computed,
|
|
21
|
+
import { computed, ref, watch } from "vue";
|
|
21
22
|
import Modal from "./Modal.vue";
|
|
22
23
|
import Button from "./Button.vue";
|
|
23
24
|
import Icon from "./Icon.vue";
|
|
@@ -70,26 +71,15 @@ function onKeydown(ev: KeyboardEvent): void {
|
|
|
70
71
|
emit("confirm");
|
|
71
72
|
}
|
|
72
73
|
|
|
73
|
-
watch(
|
|
74
|
-
() => props.open,
|
|
75
|
-
(open) => {
|
|
76
|
-
if (open) {
|
|
77
|
-
typed.value = "";
|
|
78
|
-
window.addEventListener("keydown", onKeydown);
|
|
79
|
-
}
|
|
80
|
-
else window.removeEventListener("keydown", onKeydown);
|
|
81
|
-
},
|
|
82
|
-
{ immediate: true },
|
|
83
|
-
);
|
|
84
|
-
onBeforeUnmount(() => window.removeEventListener("keydown", onKeydown));
|
|
74
|
+
watch(() => props.open, (open) => { if (open) typed.value = ""; }, { immediate: true });
|
|
85
75
|
</script>
|
|
86
76
|
|
|
87
77
|
<template>
|
|
88
|
-
<Modal :open="open" size="sm" @update:open="onOpen">
|
|
78
|
+
<Modal :open="open" size="sm" @update:open="onOpen" @keydown="onKeydown">
|
|
89
79
|
<template #header="{ titleId }">
|
|
90
80
|
<span class="wp-confirm-modal__head" :class="`wp-confirm-modal__head--${tone}`">
|
|
91
81
|
<Icon :name="tone === 'danger' ? 'exclamation-triangle' : 'info-circle'" />
|
|
92
|
-
<h3 :id="titleId" class="wp-modal__title" data-test="confirm-title">{{ title }}</h3>
|
|
82
|
+
<h3 :id="titleId" class="wp-confirm-modal__title" data-test="confirm-title">{{ title }}</h3>
|
|
93
83
|
</span>
|
|
94
84
|
</template>
|
|
95
85
|
<div class="wp-confirm-modal__body">
|
|
@@ -123,7 +113,7 @@ onBeforeUnmount(() => window.removeEventListener("keydown", onKeydown));
|
|
|
123
113
|
}
|
|
124
114
|
.wp-confirm-modal__head--danger { color: var(--wp-danger); }
|
|
125
115
|
.wp-confirm-modal__head--warn { color: var(--wp-warn); }
|
|
126
|
-
.wp-confirm-
|
|
116
|
+
.wp-confirm-modal__title { margin: 0; font-size: var(--wp-text-md); font-weight: 600; color: var(--wp-text); }
|
|
127
117
|
.wp-confirm-modal__text {
|
|
128
118
|
margin: 0;
|
|
129
119
|
color: var(--wp-text-muted);
|
|
@@ -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
|
-
|
|
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
|
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* Constraint: counts up top, then the grouped matrix (ConstraintMatrixPreview)
|
|
4
|
+
* or, for a big matrix, a searchable list of only the cells that do
|
|
5
|
+
* something. The search narrows rows / columns in both views.
|
|
6
|
+
*/
|
|
7
|
+
import { computed, ref } from "vue";
|
|
8
|
+
import ConstraintMatrixPreview from "./ConstraintMatrixPreview.vue";
|
|
9
|
+
import { MODE_GLYPH as GLYPH, MODE_LABEL as LABEL, normalizeMode, type CellMode as Mode } from "./matrix-axis";
|
|
10
|
+
import {
|
|
11
|
+
readArray,
|
|
12
|
+
readGroups,
|
|
13
|
+
readNumber,
|
|
14
|
+
readObject,
|
|
15
|
+
readString,
|
|
16
|
+
type Obj,
|
|
17
|
+
} from "./preview-readers";
|
|
18
|
+
import WpSegmented, { type SegmentedOption } from "./WpSegmented.vue";
|
|
19
|
+
import SearchInput from "./SearchInput.vue";
|
|
20
|
+
|
|
21
|
+
const props = defineProps<{
|
|
22
|
+
inner: Obj | null;
|
|
23
|
+
refMap: ReadonlyMap<string, string>;
|
|
24
|
+
refNames: Record<string, string>;
|
|
25
|
+
refGroups: Record<string, Record<string, string[]>>;
|
|
26
|
+
}>();
|
|
27
|
+
|
|
28
|
+
interface Cell {
|
|
29
|
+
mode: Mode;
|
|
30
|
+
factor: number;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const matrix = computed<Record<string, Record<string, Cell>>>(() => {
|
|
34
|
+
const m = readObject(props.inner, "matrix");
|
|
35
|
+
if (!m) return {};
|
|
36
|
+
const out: Record<string, Record<string, Cell>> = {};
|
|
37
|
+
for (const row of Object.keys(m)) {
|
|
38
|
+
const byCol = readObject(m, row);
|
|
39
|
+
if (!byCol) continue;
|
|
40
|
+
out[row] = {};
|
|
41
|
+
for (const col of Object.keys(byCol)) {
|
|
42
|
+
const e = readObject(byCol, col);
|
|
43
|
+
if (!e) continue;
|
|
44
|
+
out[row][col] = { mode: normalizeMode(readString(e, "mode")), factor: readNumber(e, "factor") ?? 1 };
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
return out;
|
|
48
|
+
});
|
|
49
|
+
const rows = computed(() => Object.keys(matrix.value));
|
|
50
|
+
/** Union of every row's columns, first-seen order. */
|
|
51
|
+
const cols = computed(() => {
|
|
52
|
+
const seen = new Set<string>();
|
|
53
|
+
for (const r of rows.value) for (const c of Object.keys(matrix.value[r] ?? {})) seen.add(c);
|
|
54
|
+
return [...seen];
|
|
55
|
+
});
|
|
56
|
+
/** Axis names: cached name → dependency name → raw id → role word. */
|
|
57
|
+
function axisName(idKey: string, nameKey: string, role: string): string {
|
|
58
|
+
const id = readString(props.inner, idKey);
|
|
59
|
+
return readString(props.inner, nameKey) ?? (id ? props.refNames[id] : undefined) ?? id ?? role;
|
|
60
|
+
}
|
|
61
|
+
const sourceName = computed(() => axisName("source_wildcard_id", "source_wildcard_name", "source"));
|
|
62
|
+
const targetName = computed(() => axisName("target_wildcard_id", "target_wildcard_name", "target"));
|
|
63
|
+
/** Embedded axis snapshot first, then the dependency's tag_groups. */
|
|
64
|
+
function axisGroups(embeddedKey: string, idKey: string): Record<string, string[]> {
|
|
65
|
+
const embedded = readGroups(props.inner, embeddedKey);
|
|
66
|
+
if (embedded) return embedded;
|
|
67
|
+
const id = readString(props.inner, idKey);
|
|
68
|
+
return (id ? props.refGroups[id] : undefined) ?? {};
|
|
69
|
+
}
|
|
70
|
+
const sourceGroups = computed(() => axisGroups("source_groups", "source_wildcard_id"));
|
|
71
|
+
const targetGroups = computed(() => axisGroups("target_groups", "target_wildcard_id"));
|
|
72
|
+
/** Library exceptions only; instance `extra_exceptions` never publish. */
|
|
73
|
+
const exceptions = computed(() =>
|
|
74
|
+
readArray(props.inner, "exceptions").map((e) => ({
|
|
75
|
+
source: readString(e, "source_value") ?? readString(e, "source") ?? "",
|
|
76
|
+
target: readString(e, "target_value") ?? readString(e, "target") ?? "",
|
|
77
|
+
mode: normalizeMode(readString(e, "mode")),
|
|
78
|
+
factor: readNumber(e, "factor") ?? 1,
|
|
79
|
+
})),
|
|
80
|
+
);
|
|
81
|
+
const targetSelect = computed(() => {
|
|
82
|
+
const sel = readObject(props.inner, "target_select");
|
|
83
|
+
const mode = readString(sel, "mode");
|
|
84
|
+
if (!mode) return null;
|
|
85
|
+
const count = readNumber(sel, "count");
|
|
86
|
+
return count !== null ? `${mode} ${count}` : mode;
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
const ruleCells = computed(() => {
|
|
90
|
+
const out: { row: string; col: string; cell: Cell }[] = [];
|
|
91
|
+
for (const r of rows.value)
|
|
92
|
+
for (const c of cols.value) {
|
|
93
|
+
const cell = matrix.value[r]?.[c];
|
|
94
|
+
if (cell && cell.mode !== "allow") out.push({ row: r, col: c, cell });
|
|
95
|
+
}
|
|
96
|
+
return out;
|
|
97
|
+
});
|
|
98
|
+
const excludeCount = computed(() => ruleCells.value.filter((x) => x.cell.mode === "exclude").length);
|
|
99
|
+
|
|
100
|
+
// ─── search + view ───
|
|
101
|
+
const search = ref("");
|
|
102
|
+
const q = computed(() => search.value.trim().toLowerCase());
|
|
103
|
+
const shownRows = computed(() => {
|
|
104
|
+
if (!q.value) return rows.value;
|
|
105
|
+
const hit = rows.value.filter((r) => r.toLowerCase().includes(q.value));
|
|
106
|
+
return hit.length ? hit : rows.value;
|
|
107
|
+
});
|
|
108
|
+
const shownCols = computed(() => {
|
|
109
|
+
if (!q.value) return cols.value;
|
|
110
|
+
const hit = cols.value.filter((c) => c.toLowerCase().includes(q.value));
|
|
111
|
+
return hit.length ? hit : cols.value;
|
|
112
|
+
});
|
|
113
|
+
const big = computed(() => rows.value.length * cols.value.length > 60);
|
|
114
|
+
const view = ref<"matrix" | "list">("matrix");
|
|
115
|
+
const VIEW_OPTIONS: SegmentedOption<"matrix" | "list">[] = [
|
|
116
|
+
{ value: "matrix", label: "Matrix" },
|
|
117
|
+
{ value: "list", label: "Rules list" },
|
|
118
|
+
];
|
|
119
|
+
const listRows = computed(() =>
|
|
120
|
+
ruleCells.value.filter(
|
|
121
|
+
(x) => !q.value || x.row.toLowerCase().includes(q.value) || x.col.toLowerCase().includes(q.value),
|
|
122
|
+
),
|
|
123
|
+
);
|
|
124
|
+
function fmtFactor(f: number): string {
|
|
125
|
+
return Number.isInteger(f) ? String(f) : f.toFixed(2).replace(/\.?0+$/, "");
|
|
126
|
+
}
|
|
127
|
+
</script>
|
|
128
|
+
|
|
129
|
+
<template>
|
|
130
|
+
<div class="wp-pv">
|
|
131
|
+
<div class="wp-pv-stats">
|
|
132
|
+
<div class="wp-pv-stat">
|
|
133
|
+
<b>{{ rows.length }} × {{ cols.length }}</b><span>sources × targets</span>
|
|
134
|
+
</div>
|
|
135
|
+
<div class="wp-pv-stat">
|
|
136
|
+
<b>{{ ruleCells.length }}</b><span>rules set</span>
|
|
137
|
+
</div>
|
|
138
|
+
<div
|
|
139
|
+
v-if="excludeCount"
|
|
140
|
+
class="wp-pv-stat"
|
|
141
|
+
>
|
|
142
|
+
<b>{{ excludeCount }}</b><span>excludes</span>
|
|
143
|
+
</div>
|
|
144
|
+
<div
|
|
145
|
+
v-if="exceptions.length"
|
|
146
|
+
class="wp-pv-stat"
|
|
147
|
+
>
|
|
148
|
+
<b>{{ exceptions.length }}</b><span>exception{{ exceptions.length === 1 ? "" : "s" }}</span>
|
|
149
|
+
</div>
|
|
150
|
+
<div
|
|
151
|
+
v-if="targetSelect"
|
|
152
|
+
class="wp-pv-stat"
|
|
153
|
+
>
|
|
154
|
+
<b>{{ targetSelect }}</b><span>reach</span>
|
|
155
|
+
</div>
|
|
156
|
+
</div>
|
|
157
|
+
<div
|
|
158
|
+
v-if="big"
|
|
159
|
+
class="wp-pv-tool"
|
|
160
|
+
>
|
|
161
|
+
<SearchInput
|
|
162
|
+
v-model="search"
|
|
163
|
+
placeholder="Filter rows or columns…"
|
|
164
|
+
data-test="constraint-search"
|
|
165
|
+
class="wp-pv-search"
|
|
166
|
+
/>
|
|
167
|
+
<span class="wp-pv-label">Show</span>
|
|
168
|
+
<WpSegmented
|
|
169
|
+
v-model="view"
|
|
170
|
+
:options="VIEW_OPTIONS"
|
|
171
|
+
size="sm"
|
|
172
|
+
aria-label="Show"
|
|
173
|
+
test-prefix="constraint"
|
|
174
|
+
/>
|
|
175
|
+
</div>
|
|
176
|
+
<div
|
|
177
|
+
v-if="view === 'matrix'"
|
|
178
|
+
class="wp-cn-wrap"
|
|
179
|
+
>
|
|
180
|
+
<ConstraintMatrixPreview
|
|
181
|
+
:rows="shownRows"
|
|
182
|
+
:cols="shownCols"
|
|
183
|
+
:matrix="matrix"
|
|
184
|
+
:source-name="sourceName"
|
|
185
|
+
:target-name="targetName"
|
|
186
|
+
:source-groups="sourceGroups"
|
|
187
|
+
:target-groups="targetGroups"
|
|
188
|
+
:exceptions="exceptions"
|
|
189
|
+
:target-select="targetSelect"
|
|
190
|
+
:ref-map="refMap"
|
|
191
|
+
/>
|
|
192
|
+
</div>
|
|
193
|
+
<template v-else>
|
|
194
|
+
<table class="wp-pv-table">
|
|
195
|
+
<thead>
|
|
196
|
+
<tr>
|
|
197
|
+
<th>{{ sourceName }}</th>
|
|
198
|
+
<th>{{ targetName }}</th>
|
|
199
|
+
<th>Rule</th>
|
|
200
|
+
</tr>
|
|
201
|
+
</thead>
|
|
202
|
+
<tbody>
|
|
203
|
+
<tr
|
|
204
|
+
v-for="x in listRows"
|
|
205
|
+
:key="`${x.row}|${x.col}`"
|
|
206
|
+
data-test="constraint-rule"
|
|
207
|
+
>
|
|
208
|
+
<td>{{ x.row }}</td>
|
|
209
|
+
<td>{{ x.col }}</td>
|
|
210
|
+
<td>
|
|
211
|
+
<span
|
|
212
|
+
class="wp-cn-rule"
|
|
213
|
+
:data-mode="x.cell.mode"
|
|
214
|
+
>{{ GLYPH[x.cell.mode] }} {{ LABEL[x.cell.mode] }}<template v-if="x.cell.mode === 'boost' || x.cell.mode === 'reduce'"> ×{{ fmtFactor(x.cell.factor) }}</template></span>
|
|
215
|
+
</td>
|
|
216
|
+
</tr>
|
|
217
|
+
</tbody>
|
|
218
|
+
</table>
|
|
219
|
+
<p
|
|
220
|
+
v-if="!listRows.length"
|
|
221
|
+
class="wp-pv-empty"
|
|
222
|
+
>
|
|
223
|
+
No rules match.
|
|
224
|
+
</p>
|
|
225
|
+
</template>
|
|
226
|
+
</div>
|
|
227
|
+
</template>
|
|
228
|
+
|
|
229
|
+
<style scoped src="./preview.css"></style>
|
|
230
|
+
<style scoped>
|
|
231
|
+
.wp-cn-wrap {
|
|
232
|
+
padding: 14px 16px 16px;
|
|
233
|
+
min-width: 0;
|
|
234
|
+
}
|
|
235
|
+
.wp-cn-rule {
|
|
236
|
+
font-family: var(--wp-font-mono);
|
|
237
|
+
font-size: 11.5px;
|
|
238
|
+
font-weight: 600;
|
|
239
|
+
padding: 2px 7px;
|
|
240
|
+
border-radius: min(5px, var(--wp-radius-cap, 999px));
|
|
241
|
+
background: var(--wp-bg-3);
|
|
242
|
+
}
|
|
243
|
+
.wp-cn-rule[data-mode="boost"] {
|
|
244
|
+
color: var(--wp-success);
|
|
245
|
+
background: color-mix(in oklab, var(--wp-success) 14%, transparent);
|
|
246
|
+
}
|
|
247
|
+
.wp-cn-rule[data-mode="reduce"] {
|
|
248
|
+
color: var(--wp-warn);
|
|
249
|
+
background: color-mix(in oklab, var(--wp-warn) 14%, transparent);
|
|
250
|
+
}
|
|
251
|
+
.wp-cn-rule[data-mode="exclude"] {
|
|
252
|
+
color: var(--wp-danger-text, var(--wp-danger));
|
|
253
|
+
background: color-mix(in oklab, var(--wp-danger) 15%, transparent);
|
|
254
|
+
}
|
|
255
|
+
.wp-cn-rule[data-mode="only"] {
|
|
256
|
+
color: color-mix(in oklab, var(--wp-info) 55%, white);
|
|
257
|
+
background: color-mix(in oklab, var(--wp-info) 16%, transparent);
|
|
258
|
+
}
|
|
259
|
+
</style>
|
package/src/ContextMenu.vue
CHANGED
|
@@ -204,7 +204,7 @@ function isSectionEntry(entry: ContextMenuEntry): entry is ContextMenuSection {
|
|
|
204
204
|
<ul
|
|
205
205
|
v-if="visible"
|
|
206
206
|
ref="menuEl"
|
|
207
|
-
class="wp-ctxmenu"
|
|
207
|
+
class="wp-ctxmenu wp-menu"
|
|
208
208
|
:style="{ left: `${pos.x}px`, top: `${pos.y}px` }"
|
|
209
209
|
role="menu"
|
|
210
210
|
@click.stop
|
|
@@ -235,7 +235,8 @@ function isSectionEntry(entry: ContextMenuEntry): entry is ContextMenuSection {
|
|
|
235
235
|
can't see it); keys come from the menu's own keydown. -->
|
|
236
236
|
<!-- eslint-disable-next-line vuejs-accessibility/no-static-element-interactions, vuejs-accessibility/click-events-have-key-events, vuejs-accessibility/mouse-events-have-key-events -->
|
|
237
237
|
<li
|
|
238
|
-
class="wp-ctxmenu__item"
|
|
238
|
+
class="wp-ctxmenu__item wp-menu__item"
|
|
239
|
+
:data-active="activeIndex === i || undefined"
|
|
239
240
|
:class="{
|
|
240
241
|
'wp-ctxmenu__item--danger': entry.danger,
|
|
241
242
|
'wp-ctxmenu__item--accent': entry.accent,
|
|
@@ -268,26 +269,16 @@ function isSectionEntry(entry: ContextMenuEntry): entry is ContextMenuSection {
|
|
|
268
269
|
</Teleport>
|
|
269
270
|
</template>
|
|
270
271
|
|
|
272
|
+
<style scoped src="./menu.css"></style>
|
|
273
|
+
|
|
271
274
|
<style scoped>
|
|
275
|
+
/* The panel (background, border, corners, padding, shadow, layer, text) and
|
|
276
|
+
* the rows' padding, corners and hover / active / disabled states are the
|
|
277
|
+
* shared menu look (menu.css), the same as Select's and Combobox's lists. */
|
|
272
278
|
.wp-ctxmenu {
|
|
273
279
|
position: fixed;
|
|
274
|
-
|
|
275
|
-
background: var(--wp-bg-2);
|
|
276
|
-
border: 1px solid var(--wp-border-strong);
|
|
277
|
-
border-radius: var(--wp-radius-sm);
|
|
278
|
-
box-shadow: var(--wp-shadow-md);
|
|
279
|
-
/* Tighter padding than the old 4px-0 to match the v2 mockup. Items
|
|
280
|
-
* carry their own 6px padding + rounded corners so the menu reads
|
|
281
|
-
* as a stack of pills, not a flat list. */
|
|
282
|
-
padding: 4px;
|
|
283
|
-
/* Bumped from 180px to 250px — subtitles wrap awkwardly at narrower
|
|
284
|
-
* widths. Existing callers without subtitles still fit comfortably. */
|
|
280
|
+
/* Subtitles wrap awkwardly below 250px. */
|
|
285
281
|
min-width: 250px;
|
|
286
|
-
list-style: none;
|
|
287
|
-
margin: 0;
|
|
288
|
-
font-family: var(--wp-font-sans, sans-serif);
|
|
289
|
-
font-size: 12px;
|
|
290
|
-
color: var(--wp-text);
|
|
291
282
|
user-select: none;
|
|
292
283
|
}
|
|
293
284
|
/* Header — scope label at the top of the menu. Border-bottom acts as
|
|
@@ -319,21 +310,16 @@ function isSectionEntry(entry: ContextMenuEntry): entry is ContextMenuSection {
|
|
|
319
310
|
padding: 6px 8px 4px 8px;
|
|
320
311
|
}
|
|
321
312
|
.wp-ctxmenu__item {
|
|
322
|
-
padding: 6px 8px;
|
|
323
|
-
border-radius: min(4px, var(--wp-radius-cap, 999px));
|
|
324
|
-
cursor: pointer;
|
|
325
313
|
white-space: nowrap;
|
|
326
314
|
display: flex;
|
|
327
315
|
align-items: center;
|
|
328
316
|
gap: 8px;
|
|
329
317
|
}
|
|
330
|
-
.wp-ctxmenu__item--active,
|
|
331
|
-
.wp-ctxmenu__item:hover { background: var(--wp-accent-glow); color: var(--wp-accent); }
|
|
332
318
|
/* A checkable item's tick: always takes its room, shows only when on. */
|
|
333
319
|
.wp-ctxmenu__check { width: 14px; font-size: 11px; text-align: center; visibility: hidden; color: var(--wp-accent); }
|
|
334
320
|
.wp-ctxmenu__check[data-on] { visibility: visible; }
|
|
335
321
|
.wp-ctxmenu__item--danger { color: var(--wp-red); }
|
|
336
|
-
.wp-ctxmenu__item--danger
|
|
322
|
+
.wp-ctxmenu__item--danger[data-active="true"],
|
|
337
323
|
.wp-ctxmenu__item--danger:hover { background: var(--wp-red-bg); color: var(--wp-red); }
|
|
338
324
|
/* Accent variant — recommended action highlight. Tinted background +
|
|
339
325
|
* accent text in idle state so the eye lands on it; hover uses the
|
|
@@ -342,16 +328,11 @@ function isSectionEntry(entry: ContextMenuEntry): entry is ContextMenuSection {
|
|
|
342
328
|
color: var(--wp-accent);
|
|
343
329
|
background: color-mix(in oklab, var(--wp-accent) 12%, transparent);
|
|
344
330
|
}
|
|
345
|
-
.wp-ctxmenu__item--accent
|
|
331
|
+
.wp-ctxmenu__item--accent[data-active="true"],
|
|
346
332
|
.wp-ctxmenu__item--accent:hover {
|
|
347
333
|
background: var(--wp-accent-glow);
|
|
348
334
|
color: var(--wp-accent);
|
|
349
335
|
}
|
|
350
|
-
.wp-ctxmenu__item--disabled {
|
|
351
|
-
color: var(--wp-text-dim);
|
|
352
|
-
cursor: not-allowed;
|
|
353
|
-
}
|
|
354
|
-
.wp-ctxmenu__item--disabled:hover { background: transparent; color: var(--wp-text-dim); }
|
|
355
336
|
.wp-ctxmenu__icon,
|
|
356
337
|
.wp-ctxmenu__icon-spacer {
|
|
357
338
|
width: 14px;
|
package/src/CopyButton.vue
CHANGED
|
@@ -119,7 +119,7 @@ onBeforeUnmount(() => clearTimeout(timer));
|
|
|
119
119
|
.wp-copy-btn[data-size="lg"] { min-width: var(--wp-control-h-lg); height: var(--wp-control-h-lg); }
|
|
120
120
|
.wp-copy-btn[data-variant="secondary"] { background: var(--wp-bg-3); border-color: var(--wp-border); color: var(--wp-text); }
|
|
121
121
|
.wp-copy-btn:not([data-inline]) .pi { font-size: 12px; }
|
|
122
|
-
.wp-copy-btn[data-size="sm"]:not([data-inline]) .pi { font-size:
|
|
122
|
+
.wp-copy-btn[data-size="sm"]:not([data-inline]) .pi { font-size: 12px; }
|
|
123
123
|
.wp-copy-btn:not([data-inline]):hover:not(:disabled) { background: var(--wp-bg-3); border-color: var(--wp-border); color: var(--wp-text); }
|
|
124
124
|
.wp-copy-btn[data-variant="secondary"]:hover:not(:disabled) { background: var(--wp-bg-4); border-color: var(--wp-border-strong); }
|
|
125
125
|
.wp-copy-btn:not([data-inline]):focus-visible { outline: none; box-shadow: 0 0 0 2px var(--wp-border-focus); }
|
package/src/DataTable.vue
CHANGED
|
@@ -165,7 +165,6 @@ const span = computed(() => props.columns.length + (props.selectable ? 1 : 0));
|
|
|
165
165
|
<tr>
|
|
166
166
|
<th v-if="selectable" class="wp-data-table__check" scope="col">
|
|
167
167
|
<Checkbox
|
|
168
|
-
class="wp-checkbox--inline"
|
|
169
168
|
:model-value="allPicked"
|
|
170
169
|
:indeterminate="somePicked"
|
|
171
170
|
:disabled="loading || allKeys.length === 0"
|
|
@@ -227,7 +226,6 @@ const span = computed(() => props.columns.length + (props.selectable ? 1 : 0));
|
|
|
227
226
|
>
|
|
228
227
|
<td v-if="selectable" class="wp-data-table__check" @click.stop>
|
|
229
228
|
<Checkbox
|
|
230
|
-
class="wp-checkbox--inline"
|
|
231
229
|
:model-value="picked.has(keyOf(row))"
|
|
232
230
|
aria-label="Select row"
|
|
233
231
|
@update:model-value="(on: boolean) => toggleRow(row, on)"
|
package/src/DatePicker.vue
CHANGED
|
@@ -209,7 +209,7 @@ function clear(): void {
|
|
|
209
209
|
padding: 0 var(--wp-space-2); border: 0; border-radius: var(--wp-radius-sm);
|
|
210
210
|
background: transparent; color: var(--wp-text-muted); cursor: pointer; font: inherit;
|
|
211
211
|
}
|
|
212
|
-
.wp-date__nav .pi { font-size:
|
|
212
|
+
.wp-date__nav .pi { font-size: 12px; }
|
|
213
213
|
.wp-date__nav:hover, .wp-date__link:hover:not(:disabled) { background: var(--wp-row-hover); color: var(--wp-text); }
|
|
214
214
|
.wp-date__nav:focus-visible, .wp-date__link:focus-visible { outline: none; box-shadow: 0 0 0 2px var(--wp-border-focus); }
|
|
215
215
|
.wp-date__link:disabled { opacity: 0.45; cursor: not-allowed; }
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
/** One derivation condition: a single test, or an ALL OF / ANY OF group
|
|
3
|
+
* drawn as a nested box (schema v7 AND / OR). */
|
|
4
|
+
import type { Condition } from "./preview-readers";
|
|
5
|
+
|
|
6
|
+
defineOptions({ name: "DerivationCondition" });
|
|
7
|
+
defineProps<{ cond: Condition }>();
|
|
8
|
+
</script>
|
|
9
|
+
|
|
10
|
+
<template>
|
|
11
|
+
<span
|
|
12
|
+
v-if="cond.kind === 'test'"
|
|
13
|
+
class="wp-dv-test"
|
|
14
|
+
data-test="derivation-test"
|
|
15
|
+
>
|
|
16
|
+
{{ cond.var }} <em>{{ cond.op.replace(/_/g, " ") }}</em>{{ cond.value !== null ? ` ${cond.value}` : "" }}
|
|
17
|
+
</span>
|
|
18
|
+
<div
|
|
19
|
+
v-else
|
|
20
|
+
class="wp-dv-group"
|
|
21
|
+
:data-match="cond.match"
|
|
22
|
+
data-test="derivation-group"
|
|
23
|
+
>
|
|
24
|
+
<span class="wp-dv-group__head">{{ cond.match === "any" ? "ANY OF" : "ALL OF" }}</span>
|
|
25
|
+
<DerivationCondition
|
|
26
|
+
v-for="(c, i) in cond.conditions"
|
|
27
|
+
:key="i"
|
|
28
|
+
:cond="c"
|
|
29
|
+
/>
|
|
30
|
+
</div>
|
|
31
|
+
</template>
|
|
32
|
+
|
|
33
|
+
<style scoped>
|
|
34
|
+
.wp-dv-test {
|
|
35
|
+
display: inline-block;
|
|
36
|
+
align-self: flex-start;
|
|
37
|
+
font-family: var(--wp-font-mono);
|
|
38
|
+
font-size: 12px;
|
|
39
|
+
padding: 2px 8px;
|
|
40
|
+
border-radius: min(5px, var(--wp-radius-cap, 999px));
|
|
41
|
+
background: var(--wp-bg-3);
|
|
42
|
+
color: var(--wp-text);
|
|
43
|
+
overflow-wrap: anywhere;
|
|
44
|
+
}
|
|
45
|
+
.wp-dv-test em {
|
|
46
|
+
font-style: normal;
|
|
47
|
+
color: var(--wp-text-muted);
|
|
48
|
+
}
|
|
49
|
+
.wp-dv-group {
|
|
50
|
+
display: inline-flex;
|
|
51
|
+
flex-direction: column;
|
|
52
|
+
gap: 5px;
|
|
53
|
+
max-width: 100%;
|
|
54
|
+
padding: 6px 8px;
|
|
55
|
+
border: 1px dashed var(--wp-border-strong);
|
|
56
|
+
border-radius: min(8px, var(--wp-radius-cap, 999px));
|
|
57
|
+
}
|
|
58
|
+
.wp-dv-group__head {
|
|
59
|
+
font-family: var(--wp-font-mono);
|
|
60
|
+
font-size: 9.5px;
|
|
61
|
+
font-weight: 700;
|
|
62
|
+
letter-spacing: 0.08em;
|
|
63
|
+
color: var(--wp-constraint-source-text);
|
|
64
|
+
}
|
|
65
|
+
.wp-dv-group[data-match="any"] > .wp-dv-group__head {
|
|
66
|
+
color: var(--wp-constraint-target-text);
|
|
67
|
+
}
|
|
68
|
+
</style>
|