@dumiflex/wp-kit 0.6.0 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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
+ }
@@ -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);