@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.
Files changed (89) hide show
  1. package/CHANGELOG.md +47 -0
  2. package/README.md +5 -0
  3. package/package.json +4 -3
  4. package/src/Avatar.vue +1 -1
  5. package/src/Banner.vue +4 -4
  6. package/src/BundlePreview.vue +254 -0
  7. package/src/Button.vue +73 -44
  8. package/src/Checkbox.vue +201 -63
  9. package/src/Chip.vue +5 -2
  10. package/src/CodeBlock.vue +1 -1
  11. package/src/ColorField.vue +60 -6
  12. package/src/CombinePreview.vue +150 -0
  13. package/src/Combobox.vue +10 -14
  14. package/src/ConfirmModal.vue +12 -22
  15. package/src/ConstraintMatrixPreview.vue +6 -22
  16. package/src/ConstraintPreview.vue +259 -0
  17. package/src/ContextMenu.vue +11 -30
  18. package/src/CopyButton.vue +1 -1
  19. package/src/DataTable.vue +0 -2
  20. package/src/DatePicker.vue +1 -1
  21. package/src/DerivationCondition.vue +68 -0
  22. package/src/DerivationPreview.vue +345 -0
  23. package/src/Disclosure.vue +2 -1
  24. package/src/Drawer.vue +2 -2
  25. package/src/DropZone.vue +2 -2
  26. package/src/FixedValuesPreview.vue +184 -0
  27. package/src/HsvPicker.vue +2 -2
  28. package/src/IconPicker.vue +32 -103
  29. package/src/IconPickerPanel.vue +132 -0
  30. package/src/ImageGrid.vue +2 -2
  31. package/src/Lightbox.vue +2 -2
  32. package/src/LoadingMark.vue +6 -61
  33. package/src/Modal.vue +109 -44
  34. package/src/ModalShell.vue +26 -5
  35. package/src/Pagination.vue +4 -4
  36. package/src/PayloadPreview.vue +154 -0
  37. package/src/Popover.vue +68 -12
  38. package/src/PostDetailSkeleton.vue +65 -0
  39. package/src/PostGridSkeleton.vue +61 -0
  40. package/src/PreviewGroup.vue +75 -0
  41. package/src/ProfileSkeleton.vue +73 -0
  42. package/src/RadioGroup.vue +5 -4
  43. package/src/RichText.vue +61 -0
  44. package/src/SearchInput.vue +1 -1
  45. package/src/Select.vue +53 -158
  46. package/src/SelectionBar.vue +90 -0
  47. package/src/SettingGroup.vue +57 -0
  48. package/src/SettingRow.vue +127 -0
  49. package/src/Skeleton.vue +1 -1
  50. package/src/Slider.vue +166 -55
  51. package/src/SortableList.vue +1 -1
  52. package/src/TagInput.vue +1 -1
  53. package/src/Timeline.vue +2 -2
  54. package/src/WildcardPreview.vue +607 -0
  55. package/src/WorkingLine.vue +234 -0
  56. package/src/WpIconButton.vue +3 -2
  57. package/src/WpImageZoom.vue +7 -7
  58. package/src/WpSegmented.vue +3 -1
  59. package/src/WpSwitch.vue +1 -1
  60. package/src/WpTabs.vue +1 -1
  61. package/src/_modal-motion.css +1 -1
  62. package/src/button.css +8 -6
  63. package/src/canvas-motion.ts +251 -0
  64. package/src/floating.ts +5 -0
  65. package/src/hsv.ts +1 -1
  66. package/src/kind-colour.ts +24 -1
  67. package/src/linkify.ts +142 -0
  68. package/src/loading-mark-motion.ts +12 -155
  69. package/src/matrix-axis.ts +28 -0
  70. package/src/menu.css +51 -0
  71. package/src/motion.css +73 -0
  72. package/src/pi-icons.ts +51 -0
  73. package/src/preview-readers.ts +401 -0
  74. package/src/preview.css +194 -0
  75. package/src/slider-math.ts +45 -1
  76. package/src/theme/looks.css +6 -19
  77. package/src/theme/tokens.css +12 -1108
  78. package/src/tokens.ts +15 -4
  79. package/src/tooltip.css +8 -5
  80. package/src/useDialogFocus.ts +11 -2
  81. package/src/working-line-motion.ts +135 -0
  82. package/src/CloseButton.vue +0 -69
  83. package/src/ColorPicker.vue +0 -201
  84. package/src/ConfirmDialog.vue +0 -165
  85. package/src/RangeSlider.vue +0 -183
  86. package/src/Toggle.vue +0 -36
  87. package/src/WpButton.vue +0 -75
  88. package/src/WpCheck.vue +0 -104
  89. package/src/_skeleton.css +0 -50
package/src/linkify.ts ADDED
@@ -0,0 +1,142 @@
1
+ /**
2
+ * Dependency-free, pure rich-text segmenter for prose (post descriptions).
3
+ *
4
+ * Splits a plain-text string into an ordered list of {@link RichSeg}s so the
5
+ * renderer (`RichText.vue`) can paint each piece as a Vue node, never via
6
+ * `v-html`. Because every segment is interpolated as a text node
7
+ * or a plain `<a>`/`<code>` with attribute-bound values, the output is
8
+ * XSS-safe by construction: a `<script>` in the input is just text.
9
+ *
10
+ * A single left-to-right scan recognises, in priority order:
11
+ * 1. `@{...}` wildcard-ref code token (e.g. `@{color:cool and dark}`)
12
+ * 2. `$name(.field)` var/accessor code token (e.g. `$color`, `$color.2`)
13
+ * 3. `https?://…` explicit URL
14
+ * 4. bare URL a `domain.tld/path` or `www.…` (must carry a `/path`)
15
+ * Everything else accumulates as literal text. Adjacent text runs collapse
16
+ * into one `text` segment.
17
+ *
18
+ * URL handling:
19
+ * - Trailing punctuation `.,;:!?)` is peeled off the match and pushed back
20
+ * as text, so `(see https://x.com).` doesn't swallow the `).`.
21
+ * - http(s) matches keep their scheme as the href; bare/`www.` matches are
22
+ * prefixed with `https://`.
23
+ * - A `link` segment is emitted ONLY when the resulting href starts with
24
+ * `http://` or `https://`; anything else degrades to text.
25
+ *
26
+ * Lossless invariant: concatenating every `.text` reproduces the input.
27
+ *
28
+ * Why not rich-tokenize.ts: that is the wildcard GRAMMAR lexer (picks,
29
+ * escapes, `@{8hex}` refs only), and a description is prose: `{a|b}` must
30
+ * stay text, `$$` is just two dollars, and a mention like `@{color:cool}`
31
+ * names a wildcard in words. This scanner only marks mentions and links.
32
+ */
33
+
34
+ export type RichSeg = {
35
+ kind: "text" | "link" | "code";
36
+ text: string;
37
+ /** Present only on `link` segments — always an `http(s)://` absolute URL. */
38
+ href?: string;
39
+ };
40
+
41
+ // Code token: `@{...}` (ref). Non-greedy body, no nested `}`.
42
+ const RE_REF = /^@\{[^}]*\}/;
43
+ // Code token: `$name` with an optional single `.field` accessor.
44
+ const RE_VAR = /^\$[A-Za-z_][A-Za-z0-9_]*(?:\.[A-Za-z0-9_]+)?/;
45
+ // Explicit URL with scheme — runs to the next whitespace.
46
+ const RE_URL = /^https?:\/\/[^\s]+/;
47
+ // Bare URL: either a `www.` host, OR a `domain.tld/` (a dotted host directly
48
+ // followed by a `/path`). Requires the `/` so prose like `e.g.` or `node.com`
49
+ // without a path is NOT linkified (kept conservative on purpose).
50
+ const RE_BARE = /^(?:www\.|[a-z0-9-]+(?:\.[a-z0-9-]+)+\/)[^\s]+/i;
51
+
52
+ /** Trailing punctuation peeled off a matched URL and returned as text. */
53
+ const TRAILING = new Set([".", ",", ";", ":", "!", "?", ")"]);
54
+
55
+ /**
56
+ * Segment `input` into ordered text / link / code runs. Returns an empty
57
+ * array for empty input; the caller decides what to render for `[]`.
58
+ */
59
+ export function parseRichText(input: string): RichSeg[] {
60
+ if (!input) return [];
61
+
62
+ const segs: RichSeg[] = [];
63
+ let buf = "";
64
+
65
+ /** Flush the pending literal-text run (if any) as one `text` segment. */
66
+ const flush = (): void => {
67
+ if (buf) {
68
+ segs.push({ kind: "text", text: buf });
69
+ buf = "";
70
+ }
71
+ };
72
+
73
+ let i = 0;
74
+ const n = input.length;
75
+ while (i < n) {
76
+ const rest = input.slice(i);
77
+ const ch = input[i];
78
+
79
+ // 1. @{...} ref code token.
80
+ if (ch === "@") {
81
+ const m = rest.match(RE_REF);
82
+ if (m) {
83
+ flush();
84
+ segs.push({ kind: "code", text: m[0] });
85
+ i += m[0].length;
86
+ continue;
87
+ }
88
+ }
89
+
90
+ // 2. $var code token.
91
+ if (ch === "$") {
92
+ const m = rest.match(RE_VAR);
93
+ if (m) {
94
+ flush();
95
+ segs.push({ kind: "code", text: m[0] });
96
+ i += m[0].length;
97
+ continue;
98
+ }
99
+ }
100
+
101
+ // 3 + 4. URLs — explicit scheme first, then bare/`www.`.
102
+ //
103
+ // A bare match is only attempted at a word boundary (start of input or
104
+ // after whitespace) so we don't linkify the tail of a longer token.
105
+ const atBoundary = i === 0 || /\s/.test(input[i - 1] ?? "");
106
+ const urlMatch =
107
+ rest.match(RE_URL) ?? (atBoundary ? rest.match(RE_BARE) : null);
108
+ if (urlMatch) {
109
+ let raw = urlMatch[0];
110
+ // Peel trailing punctuation back into the text stream.
111
+ let trailer = "";
112
+ let last = raw.slice(-1);
113
+ while (last && TRAILING.has(last)) {
114
+ trailer = last + trailer;
115
+ raw = raw.slice(0, -1);
116
+ last = raw.slice(-1);
117
+ }
118
+ if (raw.length > 0) {
119
+ const href = /^https?:\/\//i.test(raw) ? raw : `https://${raw}`;
120
+ // Only emit a link when the href is genuinely http(s); otherwise the
121
+ // whole match degrades to text (defensive — the prefix above always
122
+ // satisfies this, but keeps the contract explicit).
123
+ if (/^https?:\/\//i.test(href)) {
124
+ flush();
125
+ segs.push({ kind: "link", text: raw, href });
126
+ } else {
127
+ buf += raw;
128
+ }
129
+ if (trailer) buf += trailer;
130
+ i += raw.length + trailer.length;
131
+ continue;
132
+ }
133
+ }
134
+
135
+ // Default: accumulate one literal character.
136
+ buf += ch;
137
+ i += 1;
138
+ }
139
+
140
+ flush();
141
+ return segs;
142
+ }
@@ -1,66 +1,12 @@
1
1
  /**
2
- * LoadingMark's motion, drawn on a canvas.
3
- *
4
- * The mark used to move its SVG paths with CSS. A keyframed `transform` /
5
- * `opacity` goes to the compositor, which blurs the path and snaps it to whole
6
- * pixels as it starts; animating registered custom properties fixed that in
7
- * Chromium, but Firefox still lays each frame of an SVG transform on the pixel
8
- * grid, so the rings stepped and twitched as they slid. A canvas puts every
9
- * shape at its exact fractional position each frame, sharp and smooth in both.
10
- *
11
- * One `requestAnimationFrame` loop draws every mark on the page. Each mark
12
- * keeps its own clock, so a host can slow or pause the marks inside it with
13
- * `data-wp-motion-speed` (a number) and `data-wp-motion-paused` on an
14
- * ancestor, as the kit site's Animations section does.
2
+ * LoadingMark's motion, drawn on a canvas (see `canvas-motion.ts` for why).
3
+ * The same keyframes the mark had in CSS, as eased tracks.
15
4
  */
16
5
  import { RING_A, RING_B, STAR_A, STAR_B, STAR_BIG } from "./logo-paths";
6
+ import { EASE_IN_OUT, SMOOTH, begin, fillAround, track, type Stop } from "./canvas-motion";
17
7
 
18
8
  /** One loop of the logo, in ms; the sparkle's swap takes two. */
19
9
  export const LOOP_MS = 2400;
20
- /** Room around the 1024 box for the rings as they slide apart (70 units)
21
- * and for anti-aliasing at the edge. */
22
- export const PAD = 82;
23
- export const BOX = 1024 + 2 * PAD;
24
-
25
- /** CSS `cubic-bezier(x1, y1, x2, y2)` as a function of progress. */
26
- export function cubicBezier(x1: number, y1: number, x2: number, y2: number): (x: number) => number {
27
- return (x) => {
28
- if (x <= 0) return 0;
29
- if (x >= 1) return 1;
30
- let lo = 0;
31
- let hi = 1;
32
- let t = x;
33
- for (let k = 0; k < 30; k++) {
34
- t = (lo + hi) / 2;
35
- const cx = 3 * x1 * t * (1 - t) ** 2 + 3 * x2 * t * t * (1 - t) + t ** 3;
36
- if (cx < x) lo = t;
37
- else hi = t;
38
- }
39
- return 3 * y1 * t * (1 - t) ** 2 + 3 * y2 * t * t * (1 - t) + t ** 3;
40
- };
41
- }
42
-
43
- const SMOOTH = cubicBezier(0.65, 0, 0.35, 1);
44
- const EASE_IN_OUT = cubicBezier(0.42, 0, 0.58, 1);
45
-
46
- type Stop = readonly [at: number, values: readonly number[]];
47
-
48
- /** Value of a keyframe track at progress `p` (0..1), eased per segment the
49
- * way CSS eases between two keyframes. */
50
- export function track(stops: readonly Stop[], p: number, ease: (x: number) => number): number[] {
51
- for (let k = 0; k < stops.length - 1; k++) {
52
- const a = stops[k];
53
- const b = stops[k + 1];
54
- if (!a || !b) break;
55
- if (p >= a[0] && p <= b[0]) {
56
- if (b[0] === a[0]) return [...b[1]];
57
- const e = ease((p - a[0]) / (b[0] - a[0]));
58
- return a[1].map((v, j) => v + ((b[1][j] ?? v) - v) * e);
59
- }
60
- }
61
- return [...(stops[stops.length - 1]?.[1] ?? [])];
62
- }
63
-
64
10
  /** The rings' slide apart (units of the 1024 box). */
65
11
  const SPLIT: readonly Stop[] = [[0, [0]], [0.15, [0]], [0.4, [70]], [0.6, [70]], [0.85, [0]], [1, [0]]];
66
12
  /** The big star's dip: [scale, turn in degrees]. */
@@ -91,12 +37,12 @@ export function poseAt(ms: number): Pose {
91
37
  }
92
38
 
93
39
  /** Centres of the stars in the 1024 box (from their bounding boxes). */
94
- const C_BIG = [512, 512] as const;
95
- const C_A = [360, 664] as const;
96
- const C_B = [664, 360] as const;
40
+ export const C_BIG = [512, 512] as const;
41
+ export const C_A = [360, 664] as const;
42
+ export const C_B = [664, 360] as const;
97
43
 
98
44
  let paths: Record<"ra" | "rb" | "big" | "a" | "b", Path2D> | null = null;
99
- function shapes(): Record<"ra" | "rb" | "big" | "a" | "b", Path2D> {
45
+ export function shapes(): Record<"ra" | "rb" | "big" | "a" | "b", Path2D> {
100
46
  paths ??= { ra: new Path2D(RING_A), rb: new Path2D(RING_B), big: new Path2D(STAR_BIG), a: new Path2D(STAR_A), b: new Path2D(STAR_B) };
101
47
  return paths;
102
48
  }
@@ -109,26 +55,11 @@ export interface Look {
109
55
  flat: boolean;
110
56
  }
111
57
 
112
- function around(ctx: CanvasRenderingContext2D, [x, y]: readonly [number, number], scale: number, turn: number, path: Path2D, fill: string, alpha: number): void {
113
- ctx.save();
114
- ctx.translate(x, y);
115
- ctx.rotate((turn * Math.PI) / 180);
116
- ctx.scale(scale, scale);
117
- ctx.translate(-x, -y);
118
- ctx.globalAlpha = alpha;
119
- ctx.fillStyle = fill;
120
- ctx.fill(path);
121
- ctx.restore();
122
- }
123
-
124
58
  /** Draws one frame on a canvas `BOX` units square (scaled to its pixels). */
125
59
  export function drawPose(ctx: CanvasRenderingContext2D, width: number, look: Look, pose: Pose): void {
126
- const k = width / BOX;
127
60
  const [violet, indigo, teal] = look.colours;
128
61
  const p = shapes();
129
- ctx.setTransform(1, 0, 0, 1, 0, 0);
130
- ctx.clearRect(0, 0, width, width);
131
- ctx.setTransform(k, 0, 0, k, PAD * k, PAD * k);
62
+ begin(ctx, width);
132
63
  if (look.variant === "logo") {
133
64
  const ringA = (() => {
134
65
  if (look.flat) return violet;
@@ -154,15 +85,15 @@ export function drawPose(ctx: CanvasRenderingContext2D, width: number, look: Loo
154
85
  ctx.fillStyle = ringB;
155
86
  ctx.fill(p.rb);
156
87
  ctx.restore();
157
- around(ctx, C_BIG, pose.bigScale, pose.bigTurn, p.big, violet, 1);
158
- around(ctx, C_A, pose.twinkleScale, 0, p.a, indigo, pose.twinkleAlpha);
159
- around(ctx, C_B, pose.twinkleScale, 0, p.b, teal, pose.twinkleAlpha);
88
+ fillAround(ctx, C_BIG, pose.bigScale, pose.bigTurn, p.big, violet, 1);
89
+ fillAround(ctx, C_A, pose.twinkleScale, 0, p.a, indigo, pose.twinkleAlpha);
90
+ fillAround(ctx, C_B, pose.twinkleScale, 0, p.b, teal, pose.twinkleAlpha);
160
91
  return;
161
92
  }
162
93
  ctx.translate(512, 512);
163
94
  ctx.scale(1.45, 1.45);
164
95
  ctx.translate(-512, -512);
165
- around(ctx, C_BIG, pose.bigScale, pose.bigTurn, p.big, violet, 1);
96
+ fillAround(ctx, C_BIG, pose.bigScale, pose.bigTurn, p.big, violet, 1);
166
97
  ctx.save();
167
98
  ctx.translate(512, 512);
168
99
  ctx.rotate((pose.swap * Math.PI) / 180);
@@ -173,77 +104,3 @@ export function drawPose(ctx: CanvasRenderingContext2D, width: number, look: Loo
173
104
  ctx.fill(p.b);
174
105
  ctx.restore();
175
106
  }
176
-
177
- /** A mark the loop draws. */
178
- export interface Animated {
179
- canvas: HTMLCanvasElement;
180
- look: () => Look;
181
- /** ms of animation shown so far, advanced by the loop. */
182
- clock: number;
183
- /** False while off screen: the clock still runs, nothing is drawn. */
184
- visible: boolean;
185
- }
186
-
187
- const marks = new Set<Animated>();
188
- let frame = 0;
189
- let last = 0;
190
-
191
- /** Speed and pause from the nearest host that sets them. */
192
- export function motionOf(el: Element): { speed: number; paused: boolean } {
193
- const host = el.closest("[data-wp-motion-speed], [data-wp-motion-paused]");
194
- if (!host) return { speed: 1, paused: false };
195
- const paused = host.closest("[data-wp-motion-paused]") !== null;
196
- const speedHost = host.closest("[data-wp-motion-speed]");
197
- const speed = Number(speedHost?.getAttribute("data-wp-motion-speed") ?? "1");
198
- return { speed: Number.isFinite(speed) && speed >= 0 ? speed : 1, paused };
199
- }
200
-
201
- function fit(c: HTMLCanvasElement): void {
202
- const css = c.clientWidth;
203
- const want = Math.max(1, Math.round(css * (window.devicePixelRatio || 1)));
204
- if (c.width !== want) {
205
- c.width = want;
206
- c.height = want;
207
- }
208
- }
209
-
210
- function tick(now: number): void {
211
- const dt = last ? Math.min(now - last, 100) : 0;
212
- last = now;
213
- for (const m of marks) {
214
- const { speed, paused } = motionOf(m.canvas);
215
- if (!paused) m.clock += dt * speed;
216
- if (!m.visible) continue;
217
- const ctx = m.canvas.getContext("2d");
218
- if (!ctx) continue;
219
- fit(m.canvas);
220
- drawPose(ctx, m.canvas.width, m.look(), poseAt(m.clock));
221
- }
222
- frame = marks.size ? requestAnimationFrame(tick) : 0;
223
- }
224
-
225
- /** Starts drawing a mark; returns the function that stops it. */
226
- export function animate(mark: Animated): () => void {
227
- marks.add(mark);
228
- if (!frame) {
229
- last = 0;
230
- frame = requestAnimationFrame(tick);
231
- }
232
- return () => {
233
- marks.delete(mark);
234
- if (!marks.size && frame) {
235
- cancelAnimationFrame(frame);
236
- frame = 0;
237
- }
238
- };
239
- }
240
-
241
- /** Draws one still frame right away (the first frame, before the loop runs,
242
- * so the mark never shows blank). */
243
- export function drawNow(mark: Animated): boolean {
244
- const ctx = mark.canvas.getContext("2d");
245
- if (!ctx) return false;
246
- fit(mark.canvas);
247
- drawPose(ctx, mark.canvas.width, mark.look(), poseAt(mark.clock));
248
- return true;
249
- }
@@ -89,3 +89,31 @@ export function groupHue(grp: AxisGroup, role: "source" | "target", labeled: boo
89
89
  if (!isBucket(grp)) return axisHueAt(grp.axisIndex);
90
90
  return labeled ? UNGROUPED_HUE : role === "source" ? "var(--wp-constraint-source)" : "var(--wp-constraint-target)";
91
91
  }
92
+
93
+ /** A constraint cell's rule. `only` (schema v6, linked picks): when the
94
+ * source fires, targets without a rule of their own in that row are
95
+ * excluded. `allow` is the neutral cell. */
96
+ export type CellMode = "allow" | "boost" | "reduce" | "exclude" | "only";
97
+
98
+ /** The glyph each rule draws in a matrix cell, a legend or a rules list. */
99
+ export const MODE_GLYPH: Record<CellMode, string> = {
100
+ only: "✓",
101
+ boost: "↑",
102
+ reduce: "↓",
103
+ exclude: "×",
104
+ allow: "·",
105
+ };
106
+
107
+ /** The word for each rule. */
108
+ export const MODE_LABEL: Record<CellMode, string> = {
109
+ boost: "Boost",
110
+ reduce: "Reduce",
111
+ exclude: "Exclude",
112
+ allow: "Neutral",
113
+ only: "Only",
114
+ };
115
+
116
+ /** A published mode string as a rule: anything unknown reads as neutral. */
117
+ export function normalizeMode(raw: string | null | undefined): CellMode {
118
+ return raw === "exclude" || raw === "boost" || raw === "reduce" || raw === "only" ? raw : "allow";
119
+ }
package/src/menu.css ADDED
@@ -0,0 +1,51 @@
1
+ /* Menu panel: the one look of every floating list in the kit (Select's list,
2
+ * ContextMenu / DropdownMenu and Combobox's list). Pulled in scoped by
3
+ * Popover (whose `variant="menu"` panel is the menu), ContextMenu, Select and
4
+ * Combobox. It reads only the SHARED_TOKENS names, so a menu wears the
5
+ * manager colours on a page and the canvas colours in a node.
6
+ *
7
+ * <div class="wp-menu"> the panel
8
+ * <li class="wp-menu__item" data-active="true" aria-selected="true">
9
+ *
10
+ * Item states: hover and `[data-active="true"]` (the keyboard's row) share one
11
+ * tint; `[aria-selected="true"]` is a lighter one; `[aria-disabled="true"]` is
12
+ * dim and takes no hover. A piece keeps its own row layout (icons, meta,
13
+ * subtitles, ticks); the sheet sets everything you see around it.
14
+ */
15
+
16
+ .wp-menu {
17
+ z-index: 10020; /* above every modal overlay (they run up to 10010) */
18
+ box-sizing: border-box;
19
+ margin: 0;
20
+ padding: 4px;
21
+ list-style: none;
22
+ background: var(--wp-bg-2);
23
+ color: var(--wp-text);
24
+ border: 1px solid var(--wp-border-strong);
25
+ border-radius: min(8px, var(--wp-radius-cap, 999px));
26
+ box-shadow: var(--wp-shadow-md);
27
+ font-family: var(--wp-font-sans, sans-serif);
28
+ font-size: 12px;
29
+ }
30
+
31
+ .wp-menu__item {
32
+ padding: 6px 8px;
33
+ border-radius: min(6px, var(--wp-radius-cap, 999px));
34
+ color: var(--wp-text);
35
+ font-size: 12px;
36
+ cursor: pointer;
37
+ user-select: none;
38
+ }
39
+ .wp-menu__item[aria-selected="true"] {
40
+ background: color-mix(in oklab, var(--wp-accent) 12%, transparent);
41
+ }
42
+ .wp-menu__item:hover,
43
+ .wp-menu__item[data-active="true"] {
44
+ background: var(--wp-accent-glow);
45
+ color: var(--wp-text);
46
+ }
47
+ .wp-menu__item[aria-disabled="true"] {
48
+ color: var(--wp-text-dim);
49
+ background: transparent;
50
+ cursor: not-allowed;
51
+ }
package/src/motion.css ADDED
@@ -0,0 +1,73 @@
1
+ /*
2
+ * Entrances and nudges for things that appear while you watch: a streamed
3
+ * reply's new words, a reaction, a chip by the selection, a question that
4
+ * needs an answer. Add the class to the element as it mounts.
5
+ *
6
+ * .wp-enter-fade fades in (150 ms): each new run of streamed text.
7
+ * .wp-enter-pop pops in with a small overshoot (320 ms): a reaction.
8
+ * .wp-enter-rise rises 4 px into place (140 ms): a chip above a
9
+ * selection; add data-from="below" when it sits under it.
10
+ * .wp-enter-drop drops 6 px into place (500 ms): a panel that asks for
11
+ * something.
12
+ * .wp-attention a soft accent ring that pulses three times (after
13
+ * .wp-enter-drop when both are set).
14
+ *
15
+ * The moving element keeps its own position: set --wp-enter-x for an extra
16
+ * horizontal shift (a chip centred with -50%), and it goes into the same
17
+ * transform.
18
+ *
19
+ * Every entrance animates registered custom properties, never `opacity` or
20
+ * `transform` themselves: those go to the compositor, which lifts the element
21
+ * onto its own layer while it moves and drops it after, so text went soft
22
+ * during the motion and snapped half a pixel at its end.
23
+ */
24
+ @property --wp-enter-o { syntax: "<number>"; inherits: false; initial-value: 1; }
25
+ @property --wp-enter-s { syntax: "<number>"; inherits: false; initial-value: 1; }
26
+ @property --wp-enter-y { syntax: "<length>"; inherits: false; initial-value: 0px; }
27
+
28
+ @layer wp-extension {
29
+ .wp-enter-fade {
30
+ opacity: var(--wp-enter-o);
31
+ animation: wp-enter-fade 150ms ease-out;
32
+ }
33
+ .wp-enter-pop {
34
+ opacity: var(--wp-enter-o);
35
+ transform: translateX(var(--wp-enter-x, 0px)) scale(var(--wp-enter-s));
36
+ animation: wp-enter-pop 0.32s cubic-bezier(0.3, 1.6, 0.5, 1) both;
37
+ }
38
+ .wp-enter-rise {
39
+ opacity: var(--wp-enter-o);
40
+ transform: translate(var(--wp-enter-x, 0px), var(--wp-enter-y));
41
+ animation: wp-enter-rise 0.14s ease-out both;
42
+ }
43
+ .wp-enter-rise[data-from="below"] { animation-name: wp-enter-rise-below; }
44
+ .wp-enter-drop {
45
+ opacity: var(--wp-enter-o);
46
+ transform: translate(var(--wp-enter-x, 0px), var(--wp-enter-y));
47
+ animation: wp-enter-drop 0.5s ease-out;
48
+ }
49
+ .wp-attention { animation: wp-attention 2.4s ease-in-out 3; }
50
+ .wp-enter-drop.wp-attention {
51
+ animation: wp-enter-drop 0.5s ease-out, wp-attention 2.4s ease-in-out 0.5s 3;
52
+ }
53
+
54
+ @keyframes wp-enter-fade { from { --wp-enter-o: 0; } to { --wp-enter-o: 1; } }
55
+ @keyframes wp-enter-pop {
56
+ from { --wp-enter-s: 0.3; --wp-enter-o: 0; }
57
+ to { --wp-enter-s: 1; --wp-enter-o: 1; }
58
+ }
59
+ @keyframes wp-enter-rise {
60
+ from { --wp-enter-o: 0; --wp-enter-y: 4px; }
61
+ to { --wp-enter-o: 1; --wp-enter-y: 0px; }
62
+ }
63
+ @keyframes wp-enter-rise-below {
64
+ from { --wp-enter-o: 0; --wp-enter-y: -4px; }
65
+ to { --wp-enter-o: 1; --wp-enter-y: 0px; }
66
+ }
67
+ @keyframes wp-enter-drop { from { --wp-enter-o: 0; --wp-enter-y: -6px; } }
68
+ @keyframes wp-attention { 50% { box-shadow: 0 0 0 6px var(--wp-accent-glow); } }
69
+
70
+ @media (prefers-reduced-motion: reduce) {
71
+ .wp-enter-fade, .wp-enter-pop, .wp-enter-rise, .wp-enter-drop, .wp-attention { animation: none; }
72
+ }
73
+ }
@@ -0,0 +1,51 @@
1
+ /**
2
+ * Every PrimeIcons glyph in the installed `primeicons.css`, minus the `pi-`
3
+ * prefix: what IconPicker `all` offers (searchable), and what an app can
4
+ * check a typed `:pi-name:` against. IconPicker without `all` offers the
5
+ * curated groups in `icon-catalog.ts`. `pi-icons.test.ts` keeps this list
6
+ * equal to the stylesheet.
7
+ */
8
+ export const PI_ICONS: readonly string[] = [
9
+ "address-book", "align-center", "align-justify", "align-left", "align-right", "amazon", "android", "angle-double-down",
10
+ "angle-double-left", "angle-double-right", "angle-double-up", "angle-down", "angle-left", "angle-right", "angle-up", "apple",
11
+ "arrow-circle-down", "arrow-circle-left", "arrow-circle-right", "arrow-circle-up", "arrow-down", "arrow-down-left", "arrow-down-left-and-arrow-up-right-to-center", "arrow-down-right",
12
+ "arrow-left", "arrow-right", "arrow-right-arrow-left", "arrow-up", "arrow-up-left", "arrow-up-right", "arrow-up-right-and-arrow-down-left-from-center", "arrows-alt",
13
+ "arrows-h", "arrows-v", "asterisk", "at", "backward", "ban", "barcode", "bars",
14
+ "bell", "bell-slash", "bitcoin", "bolt", "book", "bookmark", "bookmark-fill", "box",
15
+ "briefcase", "building", "building-columns", "bullseye", "calculator", "calendar", "calendar-clock", "calendar-minus",
16
+ "calendar-plus", "calendar-times", "camera", "car", "caret-down", "caret-left", "caret-right", "caret-up",
17
+ "cart-arrow-down", "cart-minus", "cart-plus", "chart-bar", "chart-line", "chart-pie", "chart-scatter", "check",
18
+ "check-circle", "check-square", "chevron-circle-down", "chevron-circle-left", "chevron-circle-right", "chevron-circle-up", "chevron-down", "chevron-left",
19
+ "chevron-right", "chevron-up", "circle", "circle-fill", "circle-off", "circle-on", "clipboard", "clock",
20
+ "clone", "cloud", "cloud-download", "cloud-upload", "code", "cog", "comment", "comments",
21
+ "compass", "copy", "credit-card", "crown", "database", "delete-left", "desktop", "directions",
22
+ "directions-alt", "discord", "dollar", "download", "eject", "ellipsis-h", "ellipsis-v", "envelope",
23
+ "equals", "eraser", "ethereum", "euro", "exclamation-circle", "exclamation-triangle", "expand", "external-link",
24
+ "eye", "eye-slash", "face-smile", "facebook", "fast-backward", "fast-forward", "file", "file-arrow-up",
25
+ "file-check", "file-edit", "file-excel", "file-export", "file-import", "file-o", "file-pdf", "file-plus",
26
+ "file-word", "filter", "filter-fill", "filter-slash", "flag", "flag-fill", "folder", "folder-open",
27
+ "folder-plus", "forward", "gauge", "gift", "github", "globe", "google", "graduation-cap",
28
+ "hammer", "hashtag", "headphones", "heart", "heart-fill", "history", "home", "hourglass",
29
+ "id-card", "image", "images", "inbox", "indian-rupee", "info", "info-circle", "instagram",
30
+ "key", "language", "lightbulb", "link", "linkedin", "list", "list-check", "lock",
31
+ "lock-open", "map", "map-marker", "mars", "megaphone", "microchip", "microchip-ai", "microphone",
32
+ "microsoft", "minus", "minus-circle", "mobile", "money-bill", "moon", "objects-column", "palette",
33
+ "paperclip", "pause", "pause-circle", "paypal", "pen-to-square", "pencil", "percentage", "phone",
34
+ "pinterest", "play", "play-circle", "plus", "plus-circle", "pound", "power-off", "prime",
35
+ "print", "qrcode", "question", "question-circle", "receipt", "reddit", "refresh", "replay",
36
+ "reply", "save", "search", "search-minus", "search-plus", "send", "server", "share-alt",
37
+ "shield", "shop", "shopping-bag", "shopping-cart", "sign-in", "sign-out", "sitemap", "slack",
38
+ "sliders-h", "sliders-v", "sort", "sort-alpha-down", "sort-alpha-down-alt", "sort-alpha-up", "sort-alpha-up-alt", "sort-alt",
39
+ "sort-alt-slash", "sort-amount-down", "sort-amount-down-alt", "sort-amount-up", "sort-amount-up-alt", "sort-down", "sort-down-fill", "sort-numeric-down",
40
+ "sort-numeric-down-alt", "sort-numeric-up", "sort-numeric-up-alt", "sort-up", "sort-up-fill", "sparkles", "spinner", "spinner-dotted",
41
+ "star", "star-fill", "star-half", "star-half-fill", "step-backward", "step-backward-alt", "step-forward", "step-forward-alt",
42
+ "stop", "stop-circle", "stopwatch", "sun", "sync", "table", "tablet", "tag",
43
+ "tags", "telegram", "th-large", "thumbs-down", "thumbs-down-fill", "thumbs-up", "thumbs-up-fill", "thumbtack",
44
+ "ticket", "tiktok", "times", "times-circle", "trash", "trophy", "truck", "turkish-lira",
45
+ "twitch", "twitter", "undo", "unlock", "upload", "user", "user-edit", "user-minus",
46
+ "user-plus", "users", "venus", "verified", "video", "vimeo", "volume-down", "volume-off",
47
+ "volume-up", "wallet", "warehouse", "wave-pulse", "whatsapp", "wifi", "window-maximize", "window-minimize",
48
+ "wrench", "youtube",
49
+ ];
50
+
51
+ export const PI_ICON_SET: ReadonlySet<string> = new Set(PI_ICONS);