@dumiflex/wp-kit 0.3.0 → 0.4.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.
Files changed (128) hide show
  1. package/CHANGELOG.md +77 -51
  2. package/LICENSE +674 -674
  3. package/README.md +73 -76
  4. package/package.json +78 -77
  5. package/src/Avatar.vue +83 -83
  6. package/src/Badge.vue +97 -97
  7. package/src/Banner.vue +118 -118
  8. package/src/BookmarkButton.vue +27 -27
  9. package/src/BundleMeta.vue +51 -51
  10. package/src/Button.vue +103 -103
  11. package/src/Card.vue +68 -68
  12. package/src/Checkbox.vue +109 -107
  13. package/src/Chip.vue +163 -97
  14. package/src/ChoiceCards.vue +133 -133
  15. package/src/CloseButton.vue +69 -69
  16. package/src/CodeBlock.vue +119 -119
  17. package/src/ColorField.vue +188 -206
  18. package/src/ColorPicker.vue +201 -201
  19. package/src/Combobox.vue +331 -334
  20. package/src/ConfirmDialog.vue +165 -163
  21. package/src/ConfirmModal.vue +147 -147
  22. package/src/ConstraintMatrixPreview.vue +708 -708
  23. package/src/ContextMenu.vue +386 -379
  24. package/src/CopyButton.vue +156 -156
  25. package/src/DataTable.vue +292 -292
  26. package/src/DatePicker.vue +234 -234
  27. package/src/DescriptionList.vue +73 -73
  28. package/src/DiffView.vue +68 -68
  29. package/src/Disclosure.vue +166 -166
  30. package/src/DragHandle.vue +49 -49
  31. package/src/Drawer.vue +131 -131
  32. package/src/DropZone.vue +196 -196
  33. package/src/DropdownMenu.vue +72 -72
  34. package/src/EmptyState.vue +111 -111
  35. package/src/Field.vue +28 -28
  36. package/src/HsvPicker.vue +208 -208
  37. package/src/Icon.vue +39 -39
  38. package/src/IconPicker.vue +209 -209
  39. package/src/ImageGrid.vue +125 -125
  40. package/src/InlineEdit.vue +183 -183
  41. package/src/Input.vue +99 -99
  42. package/src/Kbd.vue +56 -56
  43. package/src/Lightbox.vue +173 -173
  44. package/src/ListFilter.vue +85 -85
  45. package/src/ListRow.vue +204 -204
  46. package/src/LoadingMark.vue +175 -161
  47. package/src/Logo.vue +60 -60
  48. package/src/Modal.vue +78 -76
  49. package/src/ModalShell.vue +109 -107
  50. package/src/Pagination.vue +106 -106
  51. package/src/Popover.vue +180 -171
  52. package/src/PostCard.vue +210 -211
  53. package/src/ProgressBar.vue +90 -90
  54. package/src/RadioGroup.vue +177 -177
  55. package/src/RangeSlider.vue +183 -183
  56. package/src/RelativeDate.vue +35 -35
  57. package/src/ResizeHandle.vue +143 -143
  58. package/src/SearchInput.vue +192 -199
  59. package/src/Select.vue +644 -641
  60. package/src/Separator.vue +54 -54
  61. package/src/Skeleton.vue +82 -82
  62. package/src/Slider.vue +198 -198
  63. package/src/SortableList.vue +260 -260
  64. package/src/SplitPane.vue +68 -68
  65. package/src/StarButton.vue +40 -40
  66. package/src/Stepper.vue +141 -141
  67. package/src/SupportLinks.vue +93 -93
  68. package/src/TagInput.vue +297 -300
  69. package/src/Textarea.vue +57 -57
  70. package/src/Timeline.vue +92 -92
  71. package/src/ToastCard.vue +119 -119
  72. package/src/ToastHost.vue +40 -40
  73. package/src/Toggle.vue +36 -36
  74. package/src/TokenizedValue.vue +310 -310
  75. package/src/Toolbar.vue +110 -110
  76. package/src/VirtualList.vue +87 -87
  77. package/src/VisuallyHidden.vue +29 -29
  78. package/src/WpButton.vue +75 -75
  79. package/src/WpCheck.vue +104 -102
  80. package/src/WpIconButton.vue +107 -107
  81. package/src/WpImageZoom.vue +544 -544
  82. package/src/WpNumber.vue +178 -183
  83. package/src/WpSegmented.vue +175 -175
  84. package/src/WpSelect.vue +93 -89
  85. package/src/WpSwitch.vue +163 -163
  86. package/src/WpTabs.vue +188 -188
  87. package/src/_modal-motion.css +33 -33
  88. package/src/_skeleton.css +50 -50
  89. package/src/axis-color.ts +42 -42
  90. package/src/button.css +169 -169
  91. package/src/calendar.ts +59 -59
  92. package/src/clipboard.ts +50 -50
  93. package/src/colour-presets.ts +12 -12
  94. package/src/diff.ts +51 -51
  95. package/src/drop-zone.ts +41 -41
  96. package/src/field.css +50 -0
  97. package/src/flip.ts +237 -237
  98. package/src/floating-layer.ts +48 -0
  99. package/src/floating.ts +99 -99
  100. package/src/hsv.ts +100 -100
  101. package/src/icon-catalog.ts +52 -52
  102. package/src/image-zoom.ts +93 -93
  103. package/src/kind-colour.ts +28 -0
  104. package/src/logo-paths.ts +21 -21
  105. package/src/matrix-axis.ts +91 -91
  106. package/src/pagination.ts +27 -27
  107. package/src/platform-keys.ts +29 -29
  108. package/src/rich-tokenize.ts +357 -357
  109. package/src/select-types.ts +32 -32
  110. package/src/slider-math.ts +45 -45
  111. package/src/sortable.ts +22 -22
  112. package/src/theme/fonts/OFL-Fraunces.txt +93 -93
  113. package/src/theme/fonts/OFL-JetBrainsMono.txt +93 -93
  114. package/src/theme/fonts/OFL-Manrope.txt +93 -93
  115. package/src/theme/looks.css +428 -428
  116. package/src/theme/palettes.css +254 -254
  117. package/src/theme/tokens.css +2896 -2894
  118. package/src/tokens.ts +56 -56
  119. package/src/tooltip.css +43 -43
  120. package/src/tooltip.ts +146 -146
  121. package/src/useDialogFocus.ts +73 -73
  122. package/src/useGrowableField.ts +357 -357
  123. package/src/useModalKeyShield.ts +116 -116
  124. package/src/useToast.ts +48 -48
  125. package/src/wp-logo.svg +14 -14
  126. package/src/ChipToggle.vue +0 -92
  127. package/src/KindChip.vue +0 -53
  128. package/src/RatingBadge.vue +0 -42
package/src/image-zoom.ts CHANGED
@@ -1,93 +1,93 @@
1
- /**
2
- * The maths behind `WpImageZoom`: one transform, `translate(x, y) scale(s)`
3
- * with its origin at the image's top-left corner. Pure, so it is tested on
4
- * its own.
5
- */
6
-
7
- export interface Size { width: number; height: number }
8
- export interface View { scale: number; x: number; y: number }
9
-
10
- /** Zoom never goes below this share of Fit (or of 100% for a tiny image), nor above MAX_SCALE. */
11
- export const MAX_SCALE = 8;
12
- /** The steps the − / + buttons jump through, as real-size factors. */
13
- export const ZOOM_STEPS = [0.1, 0.25, 0.5, 0.75, 1, 1.5, 2, 3, 4, 6, 8] as const;
14
- /** How long `WpImageZoom`'s bar stays after the last pointer move or key, with `autohide`. */
15
- export const IDLE_MS = 2000;
16
- /** One wheel notch. */
17
- export const WHEEL_FACTOR = 1.2;
18
-
19
- /** The scale that shows the whole image in the stage (it may be above 1 for a small image). */
20
- export function fitScale(image: Size, stage: Size): number {
21
- if (image.width <= 0 || image.height <= 0 || stage.width <= 0 || stage.height <= 0) return 1;
22
- return Math.min(stage.width / image.width, stage.height / image.height);
23
- }
24
-
25
- export function minScale(image: Size, stage: Size): number {
26
- return Math.min(fitScale(image, stage), 1) / 2;
27
- }
28
-
29
- export function clampScale(scale: number, image: Size, stage: Size): number {
30
- return Math.min(MAX_SCALE, Math.max(minScale(image, stage), scale));
31
- }
32
-
33
- /** Keep the image in view: centred on an axis where it is smaller than the
34
- * stage, otherwise never showing an empty edge. */
35
- export function clampView(view: View, image: Size, stage: Size): View {
36
- const axis = (pos: number, img: number, room: number): number => {
37
- const size = img * view.scale;
38
- if (size <= room) return (room - size) / 2;
39
- return Math.min(0, Math.max(room - size, pos));
40
- };
41
- return { scale: view.scale, x: axis(view.x, image.width, stage.width), y: axis(view.y, image.height, stage.height) };
42
- }
43
-
44
- /** Set the scale, keeping the image point under `at` (stage px) where it is. */
45
- export function zoomAt(view: View, scale: number, at: { x: number; y: number }, image: Size, stage: Size): View {
46
- const s = clampScale(scale, image, stage);
47
- const k = s / view.scale;
48
- return clampView({ scale: s, x: at.x - (at.x - view.x) * k, y: at.y - (at.y - view.y) * k }, image, stage);
49
- }
50
-
51
- /** The view at `scale` centred on the image point `centre` (fractions 0..1). */
52
- export function viewAround(scale: number, centre: { x: number; y: number }, image: Size, stage: Size): View {
53
- const s = clampScale(scale, image, stage);
54
- return clampView({
55
- scale: s,
56
- x: stage.width / 2 - centre.x * image.width * s,
57
- y: stage.height / 2 - centre.y * image.height * s,
58
- }, image, stage);
59
- }
60
-
61
- /** The image point (fractions 0..1) at the middle of the stage. */
62
- export function centreOf(view: View, image: Size, stage: Size): { x: number; y: number } {
63
- if (image.width <= 0 || image.height <= 0) return { x: 0.5, y: 0.5 };
64
- const f = (mid: number, pos: number, img: number): number => Math.min(1, Math.max(0, (mid - pos) / (img * view.scale)));
65
- return { x: f(stage.width / 2, view.x, image.width), y: f(stage.height / 2, view.y, image.height) };
66
- }
67
-
68
- /** The next step up (dir 1) or down (dir -1) from `scale`, with Fit slotted in among the steps. */
69
- export function stepScale(scale: number, dir: 1 | -1, fit: number): number {
70
- const steps = [...ZOOM_STEPS, fit].sort((a, b) => a - b);
71
- const eps = 1e-3;
72
- if (dir > 0) return steps.find((s) => s > scale + eps) ?? MAX_SCALE;
73
- return [...steps].reverse().find((s) => s < scale - eps) ?? steps[0];
74
- }
75
-
76
- /** "100%", "35%", "250%". */
77
- export function percent(scale: number): string {
78
- return `${Math.round(scale * 100)}%`;
79
- }
80
-
81
- /** The part of the image the stage shows, as fractions 0..1 of the image (for a mini-map). */
82
- export function visibleRect(view: View, image: Size, stage: Size): { x: number; y: number; w: number; h: number } {
83
- if (image.width <= 0 || image.height <= 0) return { x: 0, y: 0, w: 1, h: 1 };
84
- const axis = (pos: number, img: number, room: number): [number, number] => {
85
- const size = img * view.scale;
86
- const a = Math.min(1, Math.max(0, -pos / size));
87
- const b = Math.min(1, Math.max(0, (room - pos) / size));
88
- return [a, b - a];
89
- };
90
- const [x, w] = axis(view.x, image.width, stage.width);
91
- const [y, h] = axis(view.y, image.height, stage.height);
92
- return { x, y, w, h };
93
- }
1
+ /**
2
+ * The maths behind `WpImageZoom`: one transform, `translate(x, y) scale(s)`
3
+ * with its origin at the image's top-left corner. Pure, so it is tested on
4
+ * its own.
5
+ */
6
+
7
+ export interface Size { width: number; height: number }
8
+ export interface View { scale: number; x: number; y: number }
9
+
10
+ /** Zoom never goes below this share of Fit (or of 100% for a tiny image), nor above MAX_SCALE. */
11
+ export const MAX_SCALE = 8;
12
+ /** The steps the − / + buttons jump through, as real-size factors. */
13
+ export const ZOOM_STEPS = [0.1, 0.25, 0.5, 0.75, 1, 1.5, 2, 3, 4, 6, 8] as const;
14
+ /** How long `WpImageZoom`'s bar stays after the last pointer move or key, with `autohide`. */
15
+ export const IDLE_MS = 2000;
16
+ /** One wheel notch. */
17
+ export const WHEEL_FACTOR = 1.2;
18
+
19
+ /** The scale that shows the whole image in the stage (it may be above 1 for a small image). */
20
+ export function fitScale(image: Size, stage: Size): number {
21
+ if (image.width <= 0 || image.height <= 0 || stage.width <= 0 || stage.height <= 0) return 1;
22
+ return Math.min(stage.width / image.width, stage.height / image.height);
23
+ }
24
+
25
+ export function minScale(image: Size, stage: Size): number {
26
+ return Math.min(fitScale(image, stage), 1) / 2;
27
+ }
28
+
29
+ export function clampScale(scale: number, image: Size, stage: Size): number {
30
+ return Math.min(MAX_SCALE, Math.max(minScale(image, stage), scale));
31
+ }
32
+
33
+ /** Keep the image in view: centred on an axis where it is smaller than the
34
+ * stage, otherwise never showing an empty edge. */
35
+ export function clampView(view: View, image: Size, stage: Size): View {
36
+ const axis = (pos: number, img: number, room: number): number => {
37
+ const size = img * view.scale;
38
+ if (size <= room) return (room - size) / 2;
39
+ return Math.min(0, Math.max(room - size, pos));
40
+ };
41
+ return { scale: view.scale, x: axis(view.x, image.width, stage.width), y: axis(view.y, image.height, stage.height) };
42
+ }
43
+
44
+ /** Set the scale, keeping the image point under `at` (stage px) where it is. */
45
+ export function zoomAt(view: View, scale: number, at: { x: number; y: number }, image: Size, stage: Size): View {
46
+ const s = clampScale(scale, image, stage);
47
+ const k = s / view.scale;
48
+ return clampView({ scale: s, x: at.x - (at.x - view.x) * k, y: at.y - (at.y - view.y) * k }, image, stage);
49
+ }
50
+
51
+ /** The view at `scale` centred on the image point `centre` (fractions 0..1). */
52
+ export function viewAround(scale: number, centre: { x: number; y: number }, image: Size, stage: Size): View {
53
+ const s = clampScale(scale, image, stage);
54
+ return clampView({
55
+ scale: s,
56
+ x: stage.width / 2 - centre.x * image.width * s,
57
+ y: stage.height / 2 - centre.y * image.height * s,
58
+ }, image, stage);
59
+ }
60
+
61
+ /** The image point (fractions 0..1) at the middle of the stage. */
62
+ export function centreOf(view: View, image: Size, stage: Size): { x: number; y: number } {
63
+ if (image.width <= 0 || image.height <= 0) return { x: 0.5, y: 0.5 };
64
+ const f = (mid: number, pos: number, img: number): number => Math.min(1, Math.max(0, (mid - pos) / (img * view.scale)));
65
+ return { x: f(stage.width / 2, view.x, image.width), y: f(stage.height / 2, view.y, image.height) };
66
+ }
67
+
68
+ /** The next step up (dir 1) or down (dir -1) from `scale`, with Fit slotted in among the steps. */
69
+ export function stepScale(scale: number, dir: 1 | -1, fit: number): number {
70
+ const steps = [...ZOOM_STEPS, fit].sort((a, b) => a - b);
71
+ const eps = 1e-3;
72
+ if (dir > 0) return steps.find((s) => s > scale + eps) ?? MAX_SCALE;
73
+ return [...steps].reverse().find((s) => s < scale - eps) ?? steps[0];
74
+ }
75
+
76
+ /** "100%", "35%", "250%". */
77
+ export function percent(scale: number): string {
78
+ return `${Math.round(scale * 100)}%`;
79
+ }
80
+
81
+ /** The part of the image the stage shows, as fractions 0..1 of the image (for a mini-map). */
82
+ export function visibleRect(view: View, image: Size, stage: Size): { x: number; y: number; w: number; h: number } {
83
+ if (image.width <= 0 || image.height <= 0) return { x: 0, y: 0, w: 1, h: 1 };
84
+ const axis = (pos: number, img: number, room: number): [number, number] => {
85
+ const size = img * view.scale;
86
+ const a = Math.min(1, Math.max(0, -pos / size));
87
+ const b = Math.min(1, Math.max(0, (room - pos) / size));
88
+ return [a, b - a];
89
+ };
90
+ const [x, w] = axis(view.x, image.width, stage.width);
91
+ const [y, h] = axis(view.y, image.height, stage.height);
92
+ return { x, y, w, h };
93
+ }
@@ -0,0 +1,28 @@
1
+ /**
2
+ * The colour of each module type, for a Chip (or anything else) that names
3
+ * one: the `--wp-kind-*` colour the extension uses for that type, bundles
4
+ * slate. There is no kind chip: a kind is a Chip with this colour.
5
+ *
6
+ * <Chip :color="kindColour(itemType(post.kind, post.subtype))">{{ itemType(post.kind, post.subtype) }}</Chip>
7
+ */
8
+ export type ModuleSubtype = "fixed_values" | "combine" | "wildcard" | "constraint" | "derivation";
9
+ export type ItemType = ModuleSubtype | "bundle";
10
+
11
+ const COLOURS: Record<ItemType, string> = {
12
+ fixed_values: "var(--wp-kind-fixed)",
13
+ combine: "var(--wp-kind-combine)",
14
+ wildcard: "var(--wp-kind-wildcard)",
15
+ constraint: "var(--wp-kind-constraint)",
16
+ derivation: "var(--wp-kind-derivation)",
17
+ bundle: "var(--wp-bundle-default)",
18
+ };
19
+
20
+ /** A post or library item's type. A module with no subtype reads as
21
+ * `fixed_values` (the oldest type); a `"bundle"` subtype reads as a bundle. */
22
+ export function itemType(kind: "module" | "bundle", subtype?: ItemType | null): ItemType {
23
+ return kind === "bundle" ? "bundle" : (subtype ?? "fixed_values");
24
+ }
25
+
26
+ export function kindColour(type: ItemType): string {
27
+ return COLOURS[type];
28
+ }
package/src/logo-paths.ts CHANGED
@@ -1,21 +1,21 @@
1
- /** The Wildcard Pipeline logo, split into its parts so a drawing can move them
2
- * on their own (viewBox 0 0 1024 1024). Same shapes as `wp-logo.svg`. */
3
- export const RING_A =
4
- "m512,2c-53.6,0-98.47,38.16-108.84,88.73-75.07,19.29-143.32,58.26-198.74,113.68-55.42,55.42-94.39,123.68-113.68,198.74-50.58,10.38-88.73,55.24-88.73,108.84s38.16,98.47,88.73,108.84c13.62,53.14,37.03,102.64,69.77,147.47,37.14,50.86,85.83,93.86,140.81,124.33,5.54,3.08,11.56,4.54,17.48,4.54,12.7,0,25.02-6.72,31.61-18.61,9.68-17.44,3.38-39.42-14.07-49.09-85.32-47.32-147.22-125.71-173.38-218.21,36.3-18.29,61.27-55.92,61.27-99.28s-24.94-80.97-61.23-99.27c34.22-119.96,129.79-215.52,249.74-249.74,18.3,36.29,55.92,61.23,99.27,61.23,61.27,0,111.11-49.84,111.11-111.11S573.27,2,512,2ZM113.11,473.11c21.44,0,38.89,17.44,38.89,38.89,0,20.1-15.32,36.69-34.91,38.69h-.01c-.18.02-.36.03-.53.04-1.13.1-2.28.16-3.43.16-21.44,0-38.89-17.44-38.89-38.89s17.44-38.89,38.89-38.89Zm398.89-321.11c-21.44,0-38.89-17.44-38.89-38.89s17.44-38.89,38.89-38.89,38.89,17.44,38.89,38.89-17.44,38.89-38.89,38.89Z";
5
- export const RING_B =
6
- "m933.27,403.16c-13.62-53.14-37.03-102.64-69.77-147.47-37.14-50.86-85.83-93.86-140.81-124.33-17.44-9.68-39.42-3.38-49.09,14.07-9.68,17.44-3.38,39.42,14.07,49.09,85.32,47.32,147.22,125.71,173.38,218.21-36.3,18.29-61.27,55.92-61.27,99.28s24.94,80.97,61.23,99.27c-34.22,119.96-129.79,215.52-249.74,249.74-18.3-36.29-55.92-61.23-99.27-61.23-61.27,0-111.11,49.84-111.11,111.11s49.84,111.11,111.11,111.11c53.6,0,98.47-38.16,108.84-88.73,75.07-19.29,143.32-58.26,198.74-113.68,55.42-55.42,94.39-123.68,113.68-198.74,50.58-10.38,88.73-55.24,88.73-108.84s-38.16-98.47-88.73-108.84Zm-421.27,546.62c-21.44,0-38.89-17.44-38.89-38.89s17.44-38.89,38.89-38.89,38.89,17.44,38.89,38.89-17.44,38.89-38.89,38.89Zm398.89-398.89c-21.44,0-38.89-17.44-38.89-38.89,0-20.03,15.22-36.58,34.72-38.67.38-.04.77-.08,1.16-.11h.02c.99-.08,1.99-.11,2.99-.11,21.44,0,38.89,17.44,38.89,38.89s-17.44,38.89-38.89,38.89Z";
7
- export const STAR_BIG =
8
- "m735.02,517.56c-153.82,30.87-186.59,63.64-217.46,217.46-1.22,6.08-9.9,6.08-11.12,0-30.87-153.82-63.64-186.59-217.46-217.46-6.08-1.22-6.08-9.9,0-11.12,153.82-30.87,186.59-63.64,217.46-217.46,1.22-6.08,9.9-6.08,11.12,0,30.87,153.82,63.64,186.59,217.46,217.46,6.08,1.22,6.08,9.9,0,11.12Z";
9
- export const STAR_A =
10
- "m434.06,665.85c-51.08,10.25-61.96,21.13-72.22,72.22-.41,2.02-3.29,2.02-3.69,0-10.25-51.08-21.13-61.96-72.22-72.22-2.02-.41-2.02-3.29,0-3.69,51.08-10.25,61.96-21.13,72.22-72.22.41-2.02,3.29-2.02,3.69,0,10.25,51.08,21.13,61.96,72.22,72.22,2.02.41,2.02,3.29,0,3.69Z";
11
- export const STAR_B =
12
- "m738.07,361.84c-51.08,10.25-61.96,21.13-72.22,72.22-.41,2.02-3.29,2.02-3.69,0-10.25-51.08-21.13-61.96-72.22-72.22-2.02-.41-2.02-3.29,0-3.69,51.08-10.25,61.96-21.13,72.22-72.22.41-2.02,3.29-2.02,3.69,0,10.25,51.08,21.13,61.96,72.22,72.22,2.02.41,2.02,3.29,0,3.69Z";
13
-
14
- /** The logo's colours: [big star and first ring start, middle, small star and
15
- * second ring end]. Brand colours, not theme tokens: the mark looks the same
16
- * in light and dark. `community` is the community site's rust counterpart. */
17
- export const BRAND_COLOURS = {
18
- extension: ["#a970ff", "#6366f1", "#33d6c6"],
19
- community: ["#da7550", "#f1636c", "#ad33d6"],
20
- } as const;
21
- export type LogoBrand = keyof typeof BRAND_COLOURS;
1
+ /** The Wildcard Pipeline logo, split into its parts so a drawing can move them
2
+ * on their own (viewBox 0 0 1024 1024). Same shapes as `wp-logo.svg`. */
3
+ export const RING_A =
4
+ "m512,2c-53.6,0-98.47,38.16-108.84,88.73-75.07,19.29-143.32,58.26-198.74,113.68-55.42,55.42-94.39,123.68-113.68,198.74-50.58,10.38-88.73,55.24-88.73,108.84s38.16,98.47,88.73,108.84c13.62,53.14,37.03,102.64,69.77,147.47,37.14,50.86,85.83,93.86,140.81,124.33,5.54,3.08,11.56,4.54,17.48,4.54,12.7,0,25.02-6.72,31.61-18.61,9.68-17.44,3.38-39.42-14.07-49.09-85.32-47.32-147.22-125.71-173.38-218.21,36.3-18.29,61.27-55.92,61.27-99.28s-24.94-80.97-61.23-99.27c34.22-119.96,129.79-215.52,249.74-249.74,18.3,36.29,55.92,61.23,99.27,61.23,61.27,0,111.11-49.84,111.11-111.11S573.27,2,512,2ZM113.11,473.11c21.44,0,38.89,17.44,38.89,38.89,0,20.1-15.32,36.69-34.91,38.69h-.01c-.18.02-.36.03-.53.04-1.13.1-2.28.16-3.43.16-21.44,0-38.89-17.44-38.89-38.89s17.44-38.89,38.89-38.89Zm398.89-321.11c-21.44,0-38.89-17.44-38.89-38.89s17.44-38.89,38.89-38.89,38.89,17.44,38.89,38.89-17.44,38.89-38.89,38.89Z";
5
+ export const RING_B =
6
+ "m933.27,403.16c-13.62-53.14-37.03-102.64-69.77-147.47-37.14-50.86-85.83-93.86-140.81-124.33-17.44-9.68-39.42-3.38-49.09,14.07-9.68,17.44-3.38,39.42,14.07,49.09,85.32,47.32,147.22,125.71,173.38,218.21-36.3,18.29-61.27,55.92-61.27,99.28s24.94,80.97,61.23,99.27c-34.22,119.96-129.79,215.52-249.74,249.74-18.3-36.29-55.92-61.23-99.27-61.23-61.27,0-111.11,49.84-111.11,111.11s49.84,111.11,111.11,111.11c53.6,0,98.47-38.16,108.84-88.73,75.07-19.29,143.32-58.26,198.74-113.68,55.42-55.42,94.39-123.68,113.68-198.74,50.58-10.38,88.73-55.24,88.73-108.84s-38.16-98.47-88.73-108.84Zm-421.27,546.62c-21.44,0-38.89-17.44-38.89-38.89s17.44-38.89,38.89-38.89,38.89,17.44,38.89,38.89-17.44,38.89-38.89,38.89Zm398.89-398.89c-21.44,0-38.89-17.44-38.89-38.89,0-20.03,15.22-36.58,34.72-38.67.38-.04.77-.08,1.16-.11h.02c.99-.08,1.99-.11,2.99-.11,21.44,0,38.89,17.44,38.89,38.89s-17.44,38.89-38.89,38.89Z";
7
+ export const STAR_BIG =
8
+ "m735.02,517.56c-153.82,30.87-186.59,63.64-217.46,217.46-1.22,6.08-9.9,6.08-11.12,0-30.87-153.82-63.64-186.59-217.46-217.46-6.08-1.22-6.08-9.9,0-11.12,153.82-30.87,186.59-63.64,217.46-217.46,1.22-6.08,9.9-6.08,11.12,0,30.87,153.82,63.64,186.59,217.46,217.46,6.08,1.22,6.08,9.9,0,11.12Z";
9
+ export const STAR_A =
10
+ "m434.06,665.85c-51.08,10.25-61.96,21.13-72.22,72.22-.41,2.02-3.29,2.02-3.69,0-10.25-51.08-21.13-61.96-72.22-72.22-2.02-.41-2.02-3.29,0-3.69,51.08-10.25,61.96-21.13,72.22-72.22.41-2.02,3.29-2.02,3.69,0,10.25,51.08,21.13,61.96,72.22,72.22,2.02.41,2.02,3.29,0,3.69Z";
11
+ export const STAR_B =
12
+ "m738.07,361.84c-51.08,10.25-61.96,21.13-72.22,72.22-.41,2.02-3.29,2.02-3.69,0-10.25-51.08-21.13-61.96-72.22-72.22-2.02-.41-2.02-3.29,0-3.69,51.08-10.25,61.96-21.13,72.22-72.22.41-2.02,3.29-2.02,3.69,0,10.25,51.08,21.13,61.96,72.22,72.22,2.02.41,2.02,3.29,0,3.69Z";
13
+
14
+ /** The logo's colours: [big star and first ring start, middle, small star and
15
+ * second ring end]. Brand colours, not theme tokens: the mark looks the same
16
+ * in light and dark. `community` is the community site's rust counterpart. */
17
+ export const BRAND_COLOURS = {
18
+ extension: ["#a970ff", "#6366f1", "#33d6c6"],
19
+ community: ["#da7550", "#f1636c", "#ad33d6"],
20
+ } as const;
21
+ export type LogoBrand = keyof typeof BRAND_COLOURS;
@@ -1,91 +1,91 @@
1
- /**
2
- * Constraint-matrix axis grouping — the pure logic shared by both matrix
3
- * surfaces (the SPA `ConstraintMatrix.vue` library editor and the canvas
4
- * `MatrixSection.vue` instance modal). Keeping it here means the two grids
5
- * order, group, label and colour their axes identically; only their template
6
- * + CSS differ.
7
- *
8
- * A wildcard's `tag_groups` (axis name → member sub-categories) drives the
9
- * layout: tags are ordered by axis, each axis becomes a coloured band
10
- * (columns) or pinned header chip (rows), and the leftover run of tags with
11
- * no axis becomes the labelled "uncategorized" bucket instead of a blank.
12
- */
13
- import { axisHueAt } from "./axis-color";
14
-
15
- /** One tag stamped with its axis + whether it opens a new group. Cell lookups
16
- * stay keyed by tag name, so reordering for display is safe. */
17
- export interface TagMeta {
18
- tag: string;
19
- axisIndex: number; // -1 = ungrouped
20
- axisName: string;
21
- groupStart: boolean; // first tag of its axis
22
- }
23
-
24
- /** A contiguous run of tags belonging to one axis (or the trailing ungrouped
25
- * run). Columns render one band per group; row groups with >1 tag get a
26
- * header chip, solo named groups fold the name into the tag. */
27
- export interface AxisGroup {
28
- axisIndex: number; // -1 = ungrouped
29
- axisName: string;
30
- tags: string[];
31
- grouped: boolean;
32
- }
33
-
34
- /** Order `tags` by axis (grouped first, in axis order; ungrouped last) and
35
- * stamp each with its axis index + whether it opens a new group. */
36
- export function orderByGroups(tags: string[], groups: Record<string, string[]>): TagMeta[] {
37
- const out: TagMeta[] = [];
38
- const seen = new Set<string>();
39
- Object.keys(groups).forEach((axis, ai) => {
40
- let first = true;
41
- for (const t of groups[axis] ?? []) {
42
- if (seen.has(t) || !tags.includes(t)) continue;
43
- seen.add(t);
44
- out.push({ tag: t, axisIndex: ai, axisName: axis, groupStart: first });
45
- first = false;
46
- }
47
- });
48
- let firstUngrouped = true;
49
- for (const t of tags) {
50
- if (seen.has(t)) continue;
51
- out.push({ tag: t, axisIndex: -1, axisName: "", groupStart: firstUngrouped });
52
- firstUngrouped = false;
53
- }
54
- return out;
55
- }
56
-
57
- /** Collapse an ordered `TagMeta[]` into contiguous axis groups. */
58
- export function toGroups(ordered: TagMeta[]): AxisGroup[] {
59
- const groups: AxisGroup[] = [];
60
- for (const m of ordered) {
61
- if (m.groupStart || groups.length === 0) {
62
- groups.push({ axisIndex: m.axisIndex, axisName: m.axisName, tags: [], grouped: m.axisIndex >= 0 });
63
- }
64
- groups[groups.length - 1].tags.push(m.tag);
65
- }
66
- return groups;
67
- }
68
-
69
- /** A "bucket" is the catch-all uncategorized run: no axis, or a blank axis
70
- * name. Buckets are labelled "uncategorized" + tinted neutral rather than
71
- * rendered blank. */
72
- export function isBucket(grp: AxisGroup): boolean {
73
- return !grp.grouped || grp.axisName.trim().length === 0;
74
- }
75
-
76
- /** Display label for a group's band / chip. */
77
- export function groupLabel(grp: AxisGroup): string {
78
- return isBucket(grp) ? "uncategorized" : grp.axisName;
79
- }
80
-
81
- /** Neutral grey for the uncategorized bucket — distinct from the per-axis
82
- * palette so it doesn't read as a real named axis. */
83
- export const UNGROUPED_HUE = "var(--wp-text-muted, #8a8a9a)";
84
-
85
- /** Hue for a group's chips / band / tags. Named axes take the shared palette;
86
- * the uncategorized bucket goes neutral grey when it's labelled (mixed
87
- * layout), else keeps the legacy source/target tint (fully-flat matrix). */
88
- export function groupHue(grp: AxisGroup, role: "source" | "target", labeled: boolean): string {
89
- if (!isBucket(grp)) return axisHueAt(grp.axisIndex);
90
- return labeled ? UNGROUPED_HUE : role === "source" ? "var(--wp-constraint-source)" : "var(--wp-constraint-target)";
91
- }
1
+ /**
2
+ * Constraint-matrix axis grouping — the pure logic shared by both matrix
3
+ * surfaces (the SPA `ConstraintMatrix.vue` library editor and the canvas
4
+ * `MatrixSection.vue` instance modal). Keeping it here means the two grids
5
+ * order, group, label and colour their axes identically; only their template
6
+ * + CSS differ.
7
+ *
8
+ * A wildcard's `tag_groups` (axis name → member sub-categories) drives the
9
+ * layout: tags are ordered by axis, each axis becomes a coloured band
10
+ * (columns) or pinned header chip (rows), and the leftover run of tags with
11
+ * no axis becomes the labelled "uncategorized" bucket instead of a blank.
12
+ */
13
+ import { axisHueAt } from "./axis-color";
14
+
15
+ /** One tag stamped with its axis + whether it opens a new group. Cell lookups
16
+ * stay keyed by tag name, so reordering for display is safe. */
17
+ export interface TagMeta {
18
+ tag: string;
19
+ axisIndex: number; // -1 = ungrouped
20
+ axisName: string;
21
+ groupStart: boolean; // first tag of its axis
22
+ }
23
+
24
+ /** A contiguous run of tags belonging to one axis (or the trailing ungrouped
25
+ * run). Columns render one band per group; row groups with >1 tag get a
26
+ * header chip, solo named groups fold the name into the tag. */
27
+ export interface AxisGroup {
28
+ axisIndex: number; // -1 = ungrouped
29
+ axisName: string;
30
+ tags: string[];
31
+ grouped: boolean;
32
+ }
33
+
34
+ /** Order `tags` by axis (grouped first, in axis order; ungrouped last) and
35
+ * stamp each with its axis index + whether it opens a new group. */
36
+ export function orderByGroups(tags: string[], groups: Record<string, string[]>): TagMeta[] {
37
+ const out: TagMeta[] = [];
38
+ const seen = new Set<string>();
39
+ Object.keys(groups).forEach((axis, ai) => {
40
+ let first = true;
41
+ for (const t of groups[axis] ?? []) {
42
+ if (seen.has(t) || !tags.includes(t)) continue;
43
+ seen.add(t);
44
+ out.push({ tag: t, axisIndex: ai, axisName: axis, groupStart: first });
45
+ first = false;
46
+ }
47
+ });
48
+ let firstUngrouped = true;
49
+ for (const t of tags) {
50
+ if (seen.has(t)) continue;
51
+ out.push({ tag: t, axisIndex: -1, axisName: "", groupStart: firstUngrouped });
52
+ firstUngrouped = false;
53
+ }
54
+ return out;
55
+ }
56
+
57
+ /** Collapse an ordered `TagMeta[]` into contiguous axis groups. */
58
+ export function toGroups(ordered: TagMeta[]): AxisGroup[] {
59
+ const groups: AxisGroup[] = [];
60
+ for (const m of ordered) {
61
+ if (m.groupStart || groups.length === 0) {
62
+ groups.push({ axisIndex: m.axisIndex, axisName: m.axisName, tags: [], grouped: m.axisIndex >= 0 });
63
+ }
64
+ groups[groups.length - 1].tags.push(m.tag);
65
+ }
66
+ return groups;
67
+ }
68
+
69
+ /** A "bucket" is the catch-all uncategorized run: no axis, or a blank axis
70
+ * name. Buckets are labelled "uncategorized" + tinted neutral rather than
71
+ * rendered blank. */
72
+ export function isBucket(grp: AxisGroup): boolean {
73
+ return !grp.grouped || grp.axisName.trim().length === 0;
74
+ }
75
+
76
+ /** Display label for a group's band / chip. */
77
+ export function groupLabel(grp: AxisGroup): string {
78
+ return isBucket(grp) ? "uncategorized" : grp.axisName;
79
+ }
80
+
81
+ /** Neutral grey for the uncategorized bucket — distinct from the per-axis
82
+ * palette so it doesn't read as a real named axis. */
83
+ export const UNGROUPED_HUE = "var(--wp-text-muted, #8a8a9a)";
84
+
85
+ /** Hue for a group's chips / band / tags. Named axes take the shared palette;
86
+ * the uncategorized bucket goes neutral grey when it's labelled (mixed
87
+ * layout), else keeps the legacy source/target tint (fully-flat matrix). */
88
+ export function groupHue(grp: AxisGroup, role: "source" | "target", labeled: boolean): string {
89
+ if (!isBucket(grp)) return axisHueAt(grp.axisIndex);
90
+ return labeled ? UNGROUPED_HUE : role === "source" ? "var(--wp-constraint-source)" : "var(--wp-constraint-target)";
91
+ }
package/src/pagination.ts CHANGED
@@ -1,27 +1,27 @@
1
- /**
2
- * The page numbers a Pagination shows: always the first and last page, the
3
- * current page with `siblings` on each side, and "…" where pages are left
4
- * out. A gap of exactly one page shows that page instead of "…", so the row
5
- * keeps the same length as you move through it.
6
- */
7
- export type PageItem = number | "gap-start" | "gap-end";
8
-
9
- export function pageItems(page: number, pages: number, siblings = 1): PageItem[] {
10
- if (pages <= 0) return [];
11
- const slots = siblings * 2 + 5;
12
- if (pages <= slots) return Array.from({ length: pages }, (_, i) => i + 1);
13
- const cur = Math.min(Math.max(1, page), pages);
14
- const left = Math.max(cur - siblings, 1);
15
- const right = Math.min(cur + siblings, pages);
16
- const showStart = left > 3;
17
- const showEnd = right < pages - 2;
18
- if (!showStart) {
19
- const n = siblings * 2 + 3;
20
- return [...Array.from({ length: n }, (_, i) => i + 1), "gap-end", pages];
21
- }
22
- if (!showEnd) {
23
- const n = siblings * 2 + 3;
24
- return [1, "gap-start", ...Array.from({ length: n }, (_, i) => pages - n + 1 + i)];
25
- }
26
- return [1, "gap-start", ...Array.from({ length: right - left + 1 }, (_, i) => left + i), "gap-end", pages];
27
- }
1
+ /**
2
+ * The page numbers a Pagination shows: always the first and last page, the
3
+ * current page with `siblings` on each side, and "…" where pages are left
4
+ * out. A gap of exactly one page shows that page instead of "…", so the row
5
+ * keeps the same length as you move through it.
6
+ */
7
+ export type PageItem = number | "gap-start" | "gap-end";
8
+
9
+ export function pageItems(page: number, pages: number, siblings = 1): PageItem[] {
10
+ if (pages <= 0) return [];
11
+ const slots = siblings * 2 + 5;
12
+ if (pages <= slots) return Array.from({ length: pages }, (_, i) => i + 1);
13
+ const cur = Math.min(Math.max(1, page), pages);
14
+ const left = Math.max(cur - siblings, 1);
15
+ const right = Math.min(cur + siblings, pages);
16
+ const showStart = left > 3;
17
+ const showEnd = right < pages - 2;
18
+ if (!showStart) {
19
+ const n = siblings * 2 + 3;
20
+ return [...Array.from({ length: n }, (_, i) => i + 1), "gap-end", pages];
21
+ }
22
+ if (!showEnd) {
23
+ const n = siblings * 2 + 3;
24
+ return [1, "gap-start", ...Array.from({ length: n }, (_, i) => pages - n + 1 + i)];
25
+ }
26
+ return [1, "gap-start", ...Array.from({ length: right - left + 1 }, (_, i) => left + i), "gap-end", pages];
27
+ }
@@ -1,29 +1,29 @@
1
- /**
2
- * Platform-correct labels for the modifier we actually listen for.
3
- *
4
- * The save shortcut in our modals is `(ev.ctrlKey || ev.metaKey) && Enter`, so
5
- * it has always worked on both platforms — but the hint rendered a hardcoded
6
- * `⌘`, which reads as a Mac-only binding to the ~90% of ComfyUI users on
7
- * Windows and Linux.
8
- */
9
-
10
- /** True on macOS/iPadOS, where the command key is the accelerator. */
11
- export function isApplePlatform(): boolean {
12
- if (typeof navigator === "undefined") return false;
13
- const nav = navigator as Navigator & { userAgentData?: { platform?: string } };
14
- // `navigator.platform` is deprecated; prefer the UA-CH hint and fall back to
15
- // the user-agent string, which still carries "Macintosh" / "iPhone".
16
- const hint = nav.userAgentData?.platform ?? "";
17
- if (hint) return /mac/i.test(hint);
18
- return /mac|iphone|ipad|ipod/i.test(nav.userAgent ?? "");
19
- }
20
-
21
- /** The accelerator key's symbol: `⌘` on Apple platforms, `Ctrl` elsewhere. */
22
- export function accelLabel(): string {
23
- return isApplePlatform() ? "⌘" : "Ctrl";
24
- }
25
-
26
- /** The full "save" chord as shown in modal footers — `⌘↵` or `Ctrl+↵`. */
27
- export function saveChordLabel(): string {
28
- return isApplePlatform() ? "⌘↵" : "Ctrl+↵";
29
- }
1
+ /**
2
+ * Platform-correct labels for the modifier we actually listen for.
3
+ *
4
+ * The save shortcut in our modals is `(ev.ctrlKey || ev.metaKey) && Enter`, so
5
+ * it has always worked on both platforms — but the hint rendered a hardcoded
6
+ * `⌘`, which reads as a Mac-only binding to the ~90% of ComfyUI users on
7
+ * Windows and Linux.
8
+ */
9
+
10
+ /** True on macOS/iPadOS, where the command key is the accelerator. */
11
+ export function isApplePlatform(): boolean {
12
+ if (typeof navigator === "undefined") return false;
13
+ const nav = navigator as Navigator & { userAgentData?: { platform?: string } };
14
+ // `navigator.platform` is deprecated; prefer the UA-CH hint and fall back to
15
+ // the user-agent string, which still carries "Macintosh" / "iPhone".
16
+ const hint = nav.userAgentData?.platform ?? "";
17
+ if (hint) return /mac/i.test(hint);
18
+ return /mac|iphone|ipad|ipod/i.test(nav.userAgent ?? "");
19
+ }
20
+
21
+ /** The accelerator key's symbol: `⌘` on Apple platforms, `Ctrl` elsewhere. */
22
+ export function accelLabel(): string {
23
+ return isApplePlatform() ? "⌘" : "Ctrl";
24
+ }
25
+
26
+ /** The full "save" chord as shown in modal footers — `⌘↵` or `Ctrl+↵`. */
27
+ export function saveChordLabel(): string {
28
+ return isApplePlatform() ? "⌘↵" : "Ctrl+↵";
29
+ }