mtrl 0.10.1 → 0.10.2
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/dist/components/chips/api.js +4 -0
- package/dist/components/chips/features/controller.js +6 -1
- package/dist/components/chips/types.d.ts +10 -0
- package/dist/components/loading-indicator/constants.js +2 -2
- package/dist/components/loading-indicator/features/renderer.js +13 -2
- package/dist/components/tabs/utils.js +3 -2
- package/dist/core/shapes/cubic.d.ts +2 -0
- package/dist/core/shapes/cubic.js +14 -0
- package/dist/core/shapes/index.d.ts +3 -3
- package/dist/core/shapes/index.js +3 -3
- package/dist/core/shapes/material.d.ts +136 -6
- package/dist/core/shapes/material.js +226 -25
- package/dist/core/shapes/polygon.d.ts +2 -0
- package/dist/core/shapes/polygon.js +27 -10
- package/dist/elements/preupgrade.css +1 -1
- package/dist/index.cjs +0 -0
- package/dist/package.json +1 -1
- package/dist/styles/badge.css +1 -1
- package/dist/styles/base.css +1 -1
- package/dist/styles/bottom-app-bar.css +1 -1
- package/dist/styles/bottom-sheet.css +1 -1
- package/dist/styles/button-group.css +1 -1
- package/dist/styles/button.css +1 -1
- package/dist/styles/card.css +1 -1
- package/dist/styles/carousel.css +1 -1
- package/dist/styles/checkbox.css +1 -1
- package/dist/styles/chips.css +1 -1
- package/dist/styles/datepicker.css +1 -1
- package/dist/styles/dialog.css +1 -1
- package/dist/styles/divider.css +1 -1
- package/dist/styles/drawer.css +1 -1
- package/dist/styles/extended-fab.css +1 -1
- package/dist/styles/fab-menu.css +1 -1
- package/dist/styles/fab.css +1 -1
- package/dist/styles/icon-button.css +1 -1
- package/dist/styles/list.css +1 -1
- package/dist/styles/loading-indicator.css +1 -1
- package/dist/styles/menu.css +1 -1
- package/dist/styles/navigation-rail.css +1 -1
- package/dist/styles/progress.css +1 -1
- package/dist/styles/radios.css +1 -1
- package/dist/styles/search.css +1 -1
- package/dist/styles/segmented-button.css +1 -1
- package/dist/styles/select.css +1 -1
- package/dist/styles/side-sheet.css +1 -1
- package/dist/styles/slider.css +1 -1
- package/dist/styles/snackbar.css +1 -1
- package/dist/styles/split-button.css +1 -1
- package/dist/styles/switch.css +1 -1
- package/dist/styles/tabs.css +1 -1
- package/dist/styles/textfield.css +1 -1
- package/dist/styles/timepicker.css +1 -1
- package/dist/styles/toolbar.css +1 -1
- package/dist/styles/tooltip.css +1 -1
- package/dist/styles/top-app-bar.css +1 -1
- package/dist/styles/utilities.css +1 -1
- package/dist/styles.css +1 -1
- package/dist/themes/autumn.css +1 -1
- package/dist/themes/baseline.css +1 -1
- package/dist/themes/brownbeige.css +1 -1
- package/dist/themes/browngreen.css +1 -1
- package/dist/themes/content.css +1 -1
- package/dist/themes/desert.css +1 -1
- package/dist/themes/expressive.css +1 -1
- package/dist/themes/fidelity.css +1 -1
- package/dist/themes/forest.css +1 -1
- package/dist/themes/fruit-salad.css +1 -1
- package/dist/themes/highcontrast.css +1 -1
- package/dist/themes/legacy.css +1 -1
- package/dist/themes/material.css +1 -1
- package/dist/themes/monochrome.css +1 -1
- package/dist/themes/neutral.css +1 -1
- package/dist/themes/ocean.css +1 -1
- package/dist/themes/rainbow.css +1 -1
- package/dist/themes/sageivory.css +1 -1
- package/dist/themes/spring.css +1 -1
- package/dist/themes/summer.css +1 -1
- package/dist/themes/sunset.css +1 -1
- package/dist/themes/tealcaramel.css +1 -1
- package/dist/themes/vibrant.css +1 -1
- package/dist/themes/winter.css +1 -1
- package/package.json +1 -1
|
@@ -133,6 +133,10 @@ export const withAPI = (options) => (component) => {
|
|
|
133
133
|
}
|
|
134
134
|
return this;
|
|
135
135
|
},
|
|
136
|
+
keyboard: {
|
|
137
|
+
enable: () => options.keyboard?.enableKeyboardNavigation?.(),
|
|
138
|
+
disable: () => options.keyboard?.disableKeyboardNavigation?.(),
|
|
139
|
+
},
|
|
136
140
|
on(event, handler) {
|
|
137
141
|
if (options.events && typeof options.events.on === "function") {
|
|
138
142
|
options.events.on(event, handler);
|
|
@@ -9,6 +9,7 @@ export const withController = (config) => (component) => {
|
|
|
9
9
|
remove: [],
|
|
10
10
|
};
|
|
11
11
|
let focusedChipIndex = -1;
|
|
12
|
+
let keyboardEnabled = true;
|
|
12
13
|
const dispatchEvent = (eventName, ...args) => {
|
|
13
14
|
if (eventListeners[eventName]) {
|
|
14
15
|
eventListeners[eventName].forEach((handler) => handler(...args));
|
|
@@ -58,11 +59,13 @@ export const withController = (config) => (component) => {
|
|
|
58
59
|
current.tabIndex = 0;
|
|
59
60
|
};
|
|
60
61
|
const handleKeyboardNavigation = (event) => {
|
|
62
|
+
if (!keyboardEnabled)
|
|
63
|
+
return;
|
|
61
64
|
const list = targets();
|
|
62
65
|
if (list.length === 0)
|
|
63
66
|
return;
|
|
64
67
|
const isVertical = !!(component.layout && component.layout.isVertical());
|
|
65
|
-
const rtl = component.element.
|
|
68
|
+
const rtl = component.element.matches(":dir(rtl)");
|
|
66
69
|
const back = isVertical ? "ArrowUp" : rtl ? "ArrowRight" : "ArrowLeft";
|
|
67
70
|
const forward = isVertical ? "ArrowDown" : rtl ? "ArrowLeft" : "ArrowRight";
|
|
68
71
|
const from = list.indexOf(event.target);
|
|
@@ -236,11 +239,13 @@ export const withController = (config) => (component) => {
|
|
|
236
239
|
}
|
|
237
240
|
};
|
|
238
241
|
const enableKeyboardNavigation = () => {
|
|
242
|
+
keyboardEnabled = true;
|
|
239
243
|
component.chipInstances.forEach((chip) => {
|
|
240
244
|
chip.element.addEventListener("keydown", handleKeyboardNavigation);
|
|
241
245
|
});
|
|
242
246
|
};
|
|
243
247
|
const disableKeyboardNavigation = () => {
|
|
248
|
+
keyboardEnabled = false;
|
|
244
249
|
component.chipInstances.forEach((chip) => {
|
|
245
250
|
chip.element.removeEventListener("keydown", handleKeyboardNavigation);
|
|
246
251
|
});
|
|
@@ -345,6 +345,16 @@ export interface ChipsComponent {
|
|
|
345
345
|
* @returns The chips instance for chaining
|
|
346
346
|
*/
|
|
347
347
|
enableKeyboardNavigation: () => ChipsComponent;
|
|
348
|
+
/**
|
|
349
|
+
* Turns keyboard navigation between the chips on and off: while disabled,
|
|
350
|
+
* the arrows, Home and End no longer move focus in the set (FLO-352)
|
|
351
|
+
*/
|
|
352
|
+
keyboard: {
|
|
353
|
+
/** Moves focus with the arrows, Home and End again */
|
|
354
|
+
enable: () => void;
|
|
355
|
+
/** Stops moving focus with the arrows, Home and End */
|
|
356
|
+
disable: () => void;
|
|
357
|
+
};
|
|
348
358
|
/**
|
|
349
359
|
* Destroys the chips container and all contained chips
|
|
350
360
|
*/
|
|
@@ -16,11 +16,11 @@ export const LOADING_INDICATOR_SPRING = {
|
|
|
16
16
|
};
|
|
17
17
|
export const LOADING_INDICATOR_SHAPES = [
|
|
18
18
|
'softBurst',
|
|
19
|
-
'
|
|
19
|
+
'cookie9Sided',
|
|
20
20
|
'pentagon',
|
|
21
21
|
'pill',
|
|
22
22
|
'sunny',
|
|
23
|
-
'
|
|
23
|
+
'cookie4Sided',
|
|
24
24
|
'oval'
|
|
25
25
|
];
|
|
26
26
|
export const LOADING_INDICATOR_DETERMINATE_SHAPES = ['circle', 'softBurst'];
|
|
@@ -1,12 +1,23 @@
|
|
|
1
1
|
import { createCanvasContext, updateCanvasDimensions } from "../../../core/canvas/utils.js";
|
|
2
2
|
import { onThemeChange } from "../../../core/utils/theme.js";
|
|
3
|
-
import {
|
|
3
|
+
import { shapeCircle, shapeCookie4Sided, shapeCookie9Sided, shapeOval, shapePentagon, shapePill, shapeSoftBurst, shapeSunny, } from "../../../core/shapes/index.js";
|
|
4
|
+
import { sampleProfile } from "../../../core/shapes/material.js";
|
|
5
|
+
const indicatorShapes = {
|
|
6
|
+
circle: shapeCircle,
|
|
7
|
+
oval: shapeOval,
|
|
8
|
+
pentagon: shapePentagon,
|
|
9
|
+
pill: shapePill,
|
|
10
|
+
softBurst: shapeSoftBurst,
|
|
11
|
+
sunny: shapeSunny,
|
|
12
|
+
cookie4Sided: shapeCookie4Sided,
|
|
13
|
+
cookie9Sided: shapeCookie9Sided,
|
|
14
|
+
};
|
|
4
15
|
import { LOADING_INDICATOR_CLASSES, LOADING_INDICATOR_DEFAULTS } from "../constants.js";
|
|
5
16
|
const profileCache = new Map();
|
|
6
17
|
const profileFor = (name) => {
|
|
7
18
|
let profile = profileCache.get(name);
|
|
8
19
|
if (!profile) {
|
|
9
|
-
profile =
|
|
20
|
+
profile = sampleProfile((indicatorShapes[name] ?? shapeCircle)(), LOADING_INDICATOR_DEFAULTS.SAMPLES);
|
|
10
21
|
profileCache.set(name, profile);
|
|
11
22
|
}
|
|
12
23
|
return profile;
|
|
@@ -62,11 +62,12 @@ export function updateTabPanels(component) {
|
|
|
62
62
|
}
|
|
63
63
|
}
|
|
64
64
|
}
|
|
65
|
+
const isEnabledTab = (tab) => !tab.element.disabled && tab.element.getAttribute("aria-disabled") !== "true";
|
|
65
66
|
export function syncTabStops(component) {
|
|
66
67
|
if (typeof component.getTabs !== "function")
|
|
67
68
|
return;
|
|
68
69
|
const tabs = component.getTabs();
|
|
69
|
-
const enabled = tabs.filter(
|
|
70
|
+
const enabled = tabs.filter(isEnabledTab);
|
|
70
71
|
const active = getActiveTab(component);
|
|
71
72
|
const stop = active && enabled.includes(active) ? active : enabled[0];
|
|
72
73
|
tabs.forEach((tab) => tab.element.setAttribute("tabindex", tab === stop ? "0" : "-1"));
|
|
@@ -89,7 +90,7 @@ export function setupKeyboardNavigation(component, options = {}) {
|
|
|
89
90
|
return;
|
|
90
91
|
const tabs = component
|
|
91
92
|
.getTabs()
|
|
92
|
-
.filter(
|
|
93
|
+
.filter(isEnabledTab);
|
|
93
94
|
const currentIndex = tabs.findIndex((tab) => tab.element === tabElement);
|
|
94
95
|
if (currentIndex === -1)
|
|
95
96
|
return;
|
|
@@ -18,6 +18,8 @@ export declare const straightLine: (x0: number, y0: number, x1: number, y1: numb
|
|
|
18
18
|
*/
|
|
19
19
|
export declare const circularArc: (centerX: number, centerY: number, x0: number, y0: number, x1: number, y1: number) => Cubic;
|
|
20
20
|
export declare const pointOnCurve: (c: Cubic, t: number) => Point;
|
|
21
|
+
/** Splits a cubic at `t` into two that together trace it (de Casteljau) */
|
|
22
|
+
export declare const splitCubic: (c: Cubic, t: number) => [Cubic, Cubic];
|
|
21
23
|
export declare const reverseCubic: (c: Cubic) => Cubic;
|
|
22
24
|
export declare const zeroLength: (c: Cubic) => boolean;
|
|
23
25
|
export type PointTransformer = (x: number, y: number) => Point;
|
|
@@ -41,6 +41,20 @@ export const pointOnCurve = (c, t) => {
|
|
|
41
41
|
c[1] * (u * u * u) + c[3] * (3 * t * u * u) + c[5] * (3 * t * t * u) + c[7] * (t * t * t),
|
|
42
42
|
];
|
|
43
43
|
};
|
|
44
|
+
export const splitCubic = (c, t) => {
|
|
45
|
+
const u = 1 - t;
|
|
46
|
+
const [x0, y0, x1, y1, x2, y2, x3, y3] = c;
|
|
47
|
+
const ax = u * x0 + t * x1, ay = u * y0 + t * y1;
|
|
48
|
+
const bx = u * x1 + t * x2, by = u * y1 + t * y2;
|
|
49
|
+
const cx = u * x2 + t * x3, cy = u * y2 + t * y3;
|
|
50
|
+
const dx = u * ax + t * bx, dy = u * ay + t * by;
|
|
51
|
+
const ex = u * bx + t * cx, ey = u * by + t * cy;
|
|
52
|
+
const mx = u * dx + t * ex, my = u * dy + t * ey;
|
|
53
|
+
return [
|
|
54
|
+
[x0, y0, ax, ay, dx, dy, mx, my],
|
|
55
|
+
[mx, my, ex, ey, cx, cy, x3, y3],
|
|
56
|
+
];
|
|
57
|
+
};
|
|
44
58
|
export const reverseCubic = (c) => [c[6], c[7], c[4], c[5], c[2], c[3], c[0], c[1]];
|
|
45
59
|
export const zeroLength = (c) => Math.abs(c[0] - c[6]) < DISTANCE_EPSILON && Math.abs(c[1] - c[7]) < DISTANCE_EPSILON;
|
|
46
60
|
export const transformCubic = (c, f) => {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export type { Cubic, Point, PointTransformer } from "./cubic.js";
|
|
2
|
-
export { straightLine, circularArc, pointOnCurve, cubicBounds } from "./cubic.js";
|
|
2
|
+
export { straightLine, circularArc, pointOnCurve, cubicBounds, splitCubic } from "./cubic.js";
|
|
3
3
|
export type { CornerRounding, RoundedPolygon } from "./polygon.js";
|
|
4
|
-
export { UNROUNDED, roundedPolygon, regularPolygon, circle, star, transformPolygon, rotatePolygon, scalePolygon, polygonBounds, polygonMaxRadius, normalizePolygon, } from "./polygon.js";
|
|
4
|
+
export { UNROUNDED, roundedPolygon, rectangle, regularPolygon, circle, star, transformPolygon, rotatePolygon, scalePolygon, polygonBounds, polygonMaxRadius, normalizePolygon, } from "./polygon.js";
|
|
5
5
|
export type { MaterialShapeName, RadialProfile } from "./material.js";
|
|
6
|
-
export { materialShape, radialProfile } from "./material.js";
|
|
6
|
+
export { materialShape, materialShapePath, polygonPath, radialProfile, shapeCircle, shapeSquare, shapeSlanted, shapeArch, shapeFan, shapeArrow, shapeSemiCircle, shapeOval, shapePill, shapeTriangle, shapeDiamond, shapeClamShell, shapePentagon, shapeGem, shapeSunny, shapeVerySunny, shapeCookie4Sided, shapeCookie6Sided, shapeCookie7Sided, shapeCookie9Sided, shapeCookie12Sided, shapeGhostish, shapeClover4Leaf, shapeClover8Leaf, shapeBurst, shapeSoftBurst, shapeBoom, shapeSoftBoom, shapeFlower, shapePuffy, shapePuffyDiamond, shapePixelCircle, shapePixelTriangle, shapeBun, shapeHeart, } from "./material.js";
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
export { straightLine, circularArc, pointOnCurve, cubicBounds } from "./cubic.js";
|
|
2
|
-
export { UNROUNDED, roundedPolygon, regularPolygon, circle, star, transformPolygon, rotatePolygon, scalePolygon, polygonBounds, polygonMaxRadius, normalizePolygon, } from "./polygon.js";
|
|
3
|
-
export { materialShape, radialProfile } from "./material.js";
|
|
1
|
+
export { straightLine, circularArc, pointOnCurve, cubicBounds, splitCubic } from "./cubic.js";
|
|
2
|
+
export { UNROUNDED, roundedPolygon, rectangle, regularPolygon, circle, star, transformPolygon, rotatePolygon, scalePolygon, polygonBounds, polygonMaxRadius, normalizePolygon, } from "./polygon.js";
|
|
3
|
+
export { materialShape, materialShapePath, polygonPath, radialProfile, shapeCircle, shapeSquare, shapeSlanted, shapeArch, shapeFan, shapeArrow, shapeSemiCircle, shapeOval, shapePill, shapeTriangle, shapeDiamond, shapeClamShell, shapePentagon, shapeGem, shapeSunny, shapeVerySunny, shapeCookie4Sided, shapeCookie6Sided, shapeCookie7Sided, shapeCookie9Sided, shapeCookie12Sided, shapeGhostish, shapeClover4Leaf, shapeClover8Leaf, shapeBurst, shapeSoftBurst, shapeBoom, shapeSoftBoom, shapeFlower, shapePuffy, shapePuffyDiamond, shapePixelCircle, shapePixelTriangle, shapeBun, shapeHeart, } from "./material.js";
|
|
@@ -1,8 +1,129 @@
|
|
|
1
1
|
import { RoundedPolygon, polygonMaxRadius } from "./polygon.js";
|
|
2
|
-
/**
|
|
3
|
-
export
|
|
4
|
-
/** A
|
|
2
|
+
/** Circle */
|
|
3
|
+
export declare const shapeCircle: () => RoundedPolygon;
|
|
4
|
+
/** A rounded square */
|
|
5
|
+
export declare const shapeSquare: () => RoundedPolygon;
|
|
6
|
+
/** A slanted square */
|
|
7
|
+
export declare const shapeSlanted: () => RoundedPolygon;
|
|
8
|
+
/** An arch */
|
|
9
|
+
export declare const shapeArch: () => RoundedPolygon;
|
|
10
|
+
/** A fan */
|
|
11
|
+
export declare const shapeFan: () => RoundedPolygon;
|
|
12
|
+
/** An arrow */
|
|
13
|
+
export declare const shapeArrow: () => RoundedPolygon;
|
|
14
|
+
/** A semicircle */
|
|
15
|
+
export declare const shapeSemiCircle: () => RoundedPolygon;
|
|
16
|
+
/** An oval */
|
|
17
|
+
export declare const shapeOval: () => RoundedPolygon;
|
|
18
|
+
/** A pill */
|
|
19
|
+
export declare const shapePill: () => RoundedPolygon;
|
|
20
|
+
/** A triangle */
|
|
21
|
+
export declare const shapeTriangle: () => RoundedPolygon;
|
|
22
|
+
/** A diamond */
|
|
23
|
+
export declare const shapeDiamond: () => RoundedPolygon;
|
|
24
|
+
/** A clam shell */
|
|
25
|
+
export declare const shapeClamShell: () => RoundedPolygon;
|
|
26
|
+
/** A pentagon */
|
|
27
|
+
export declare const shapePentagon: () => RoundedPolygon;
|
|
28
|
+
/** A gem */
|
|
29
|
+
export declare const shapeGem: () => RoundedPolygon;
|
|
30
|
+
/** A sunny shape: an 8-point star */
|
|
31
|
+
export declare const shapeSunny: () => RoundedPolygon;
|
|
32
|
+
/** A very sunny shape */
|
|
33
|
+
export declare const shapeVerySunny: () => RoundedPolygon;
|
|
34
|
+
/** A 4-sided cookie */
|
|
35
|
+
export declare const shapeCookie4Sided: () => RoundedPolygon;
|
|
36
|
+
/** A 6-sided cookie */
|
|
37
|
+
export declare const shapeCookie6Sided: () => RoundedPolygon;
|
|
38
|
+
/** A 7-sided cookie */
|
|
39
|
+
export declare const shapeCookie7Sided: () => RoundedPolygon;
|
|
40
|
+
/** A 9-sided cookie */
|
|
41
|
+
export declare const shapeCookie9Sided: () => RoundedPolygon;
|
|
42
|
+
/** A 12-sided cookie */
|
|
43
|
+
export declare const shapeCookie12Sided: () => RoundedPolygon;
|
|
44
|
+
/** A ghost-ish shape */
|
|
45
|
+
export declare const shapeGhostish: () => RoundedPolygon;
|
|
46
|
+
/** A 4-leaf clover */
|
|
47
|
+
export declare const shapeClover4Leaf: () => RoundedPolygon;
|
|
48
|
+
/** An 8-leaf clover */
|
|
49
|
+
export declare const shapeClover8Leaf: () => RoundedPolygon;
|
|
50
|
+
/** A burst */
|
|
51
|
+
export declare const shapeBurst: () => RoundedPolygon;
|
|
52
|
+
/** A soft burst */
|
|
53
|
+
export declare const shapeSoftBurst: () => RoundedPolygon;
|
|
54
|
+
/** A boom */
|
|
55
|
+
export declare const shapeBoom: () => RoundedPolygon;
|
|
56
|
+
/** A soft boom */
|
|
57
|
+
export declare const shapeSoftBoom: () => RoundedPolygon;
|
|
58
|
+
/** A flower */
|
|
59
|
+
export declare const shapeFlower: () => RoundedPolygon;
|
|
60
|
+
/** A puffy shape */
|
|
61
|
+
export declare const shapePuffy: () => RoundedPolygon;
|
|
62
|
+
/** A puffy diamond */
|
|
63
|
+
export declare const shapePuffyDiamond: () => RoundedPolygon;
|
|
64
|
+
/** A pixelated circle */
|
|
65
|
+
export declare const shapePixelCircle: () => RoundedPolygon;
|
|
66
|
+
/** A pixelated triangle */
|
|
67
|
+
export declare const shapePixelTriangle: () => RoundedPolygon;
|
|
68
|
+
/** A bun */
|
|
69
|
+
export declare const shapeBun: () => RoundedPolygon;
|
|
70
|
+
/** A heart */
|
|
71
|
+
export declare const shapeHeart: () => RoundedPolygon;
|
|
72
|
+
declare const byName: {
|
|
73
|
+
circle: () => RoundedPolygon;
|
|
74
|
+
square: () => RoundedPolygon;
|
|
75
|
+
slanted: () => RoundedPolygon;
|
|
76
|
+
arch: () => RoundedPolygon;
|
|
77
|
+
fan: () => RoundedPolygon;
|
|
78
|
+
arrow: () => RoundedPolygon;
|
|
79
|
+
semiCircle: () => RoundedPolygon;
|
|
80
|
+
oval: () => RoundedPolygon;
|
|
81
|
+
pill: () => RoundedPolygon;
|
|
82
|
+
triangle: () => RoundedPolygon;
|
|
83
|
+
diamond: () => RoundedPolygon;
|
|
84
|
+
clamShell: () => RoundedPolygon;
|
|
85
|
+
pentagon: () => RoundedPolygon;
|
|
86
|
+
gem: () => RoundedPolygon;
|
|
87
|
+
sunny: () => RoundedPolygon;
|
|
88
|
+
verySunny: () => RoundedPolygon;
|
|
89
|
+
cookie4Sided: () => RoundedPolygon;
|
|
90
|
+
cookie6Sided: () => RoundedPolygon;
|
|
91
|
+
cookie7Sided: () => RoundedPolygon;
|
|
92
|
+
cookie9Sided: () => RoundedPolygon;
|
|
93
|
+
cookie12Sided: () => RoundedPolygon;
|
|
94
|
+
ghostish: () => RoundedPolygon;
|
|
95
|
+
clover4Leaf: () => RoundedPolygon;
|
|
96
|
+
clover8Leaf: () => RoundedPolygon;
|
|
97
|
+
burst: () => RoundedPolygon;
|
|
98
|
+
softBurst: () => RoundedPolygon;
|
|
99
|
+
boom: () => RoundedPolygon;
|
|
100
|
+
softBoom: () => RoundedPolygon;
|
|
101
|
+
flower: () => RoundedPolygon;
|
|
102
|
+
puffy: () => RoundedPolygon;
|
|
103
|
+
puffyDiamond: () => RoundedPolygon;
|
|
104
|
+
pixelCircle: () => RoundedPolygon;
|
|
105
|
+
pixelTriangle: () => RoundedPolygon;
|
|
106
|
+
bun: () => RoundedPolygon;
|
|
107
|
+
heart: () => RoundedPolygon;
|
|
108
|
+
/** @deprecated Use 'cookie4Sided' */
|
|
109
|
+
cookie4: () => RoundedPolygon;
|
|
110
|
+
/** @deprecated Use 'cookie9Sided' */
|
|
111
|
+
cookie9: () => RoundedPolygon;
|
|
112
|
+
};
|
|
113
|
+
/**
|
|
114
|
+
* Names of the Material shapes: Compose's, camelCased. 'cookie4' and
|
|
115
|
+
* 'cookie9' are deprecated aliases of 'cookie4Sided' and 'cookie9Sided'.
|
|
116
|
+
*/
|
|
117
|
+
export type MaterialShapeName = keyof typeof byName;
|
|
118
|
+
/** A Material shape by name, normalised into the unit square. Carries every shape; import `shapeX` to carry one. */
|
|
5
119
|
export declare const materialShape: (name: MaterialShapeName) => RoundedPolygon;
|
|
120
|
+
/**
|
|
121
|
+
* A polygon's outline as an SVG path ("M … C … Z"), scaled by `size`: a
|
|
122
|
+
* normalised shape fills a `size` × `size` box. Coordinates keep three decimals.
|
|
123
|
+
*/
|
|
124
|
+
export declare const polygonPath: (polygon: RoundedPolygon, size?: number) => string;
|
|
125
|
+
/** A Material shape as an SVG path in a `size` × `size` box. Carries every shape, as materialShape does. */
|
|
126
|
+
export declare const materialShapePath: (name: MaterialShapeName, size?: number) => string;
|
|
6
127
|
/** A shape's outline as radii at evenly spaced angles around its centre */
|
|
7
128
|
export interface RadialProfile {
|
|
8
129
|
/** Radius at angle i * 2π / radii.length, clockwise from the positive x axis on a y-down canvas */
|
|
@@ -14,11 +135,20 @@ export interface RadialProfile {
|
|
|
14
135
|
maxRadius: number;
|
|
15
136
|
}
|
|
16
137
|
/**
|
|
17
|
-
*
|
|
138
|
+
* radialProfile without its star-shape check, for callers whose shapes are
|
|
139
|
+
* known to be star-shaped: the loading indicator's, which the tests check.
|
|
18
140
|
* The centroid, not the bounds centre, is the pivot: a pentagon's mass sits
|
|
19
141
|
* below the middle of its box, and turning it about the box would make it
|
|
20
|
-
* orbit.
|
|
21
|
-
*
|
|
142
|
+
* orbit.
|
|
143
|
+
* @internal
|
|
144
|
+
*/
|
|
145
|
+
export declare const sampleProfile: (polygon: RoundedPolygon, samples?: number, stepsPerCubic?: number) => RadialProfile;
|
|
146
|
+
/**
|
|
147
|
+
* Samples the outline at `samples` angles around the shape's area centroid.
|
|
148
|
+
* Throws for a shape that is not star-shaped around it. Of the Material
|
|
149
|
+
* shapes, 33 are; Puffy and PixelTriangle are not (their outlines double back
|
|
150
|
+
* as seen from the centroid, Puffy by about 0.1°): a ray from the centroid
|
|
151
|
+
* would cross the outline more than once, and the profile would be wrong.
|
|
22
152
|
*/
|
|
23
153
|
export declare const radialProfile: (polygon: RoundedPolygon, samples?: number, stepsPerCubic?: number) => RadialProfile;
|
|
24
154
|
export { polygonMaxRadius };
|
|
@@ -1,9 +1,33 @@
|
|
|
1
|
+
// src/core/shapes/material.ts
|
|
2
|
+
//
|
|
3
|
+
// The Material 3 Expressive shapes: all 35 of Compose Material 3's
|
|
4
|
+
// `MaterialShapes`, built as it builds them (vertex lists repeated around a
|
|
5
|
+
// centre, stars, rectangles, circles), normalised into the unit square.
|
|
6
|
+
//
|
|
7
|
+
// Ported from androidx (Apache License 2.0, Copyright The Android Open Source
|
|
8
|
+
// Project): compose/material3/material3/src/commonMain/kotlin/androidx/compose/
|
|
9
|
+
// material3/MaterialShapes.kt at 080d2b3e5326ba80392d93442c4a51a02dc22650, on
|
|
10
|
+
// the geometry of androidx.graphics:graphics-shapes 1.0.1, the version Compose
|
|
11
|
+
// Material 3 depends on. test/fixtures/material-shapes.json holds every shape
|
|
12
|
+
// as Compose builds it (scripts/generate-material-shapes.kt), and the tests
|
|
13
|
+
// hold this port to it.
|
|
14
|
+
//
|
|
15
|
+
// Each shape is its own export, built once on first use, so a bundle carries
|
|
16
|
+
// only the shapes it names. `materialShape(name)` looks any of them up by name,
|
|
17
|
+
// and carries them all.
|
|
1
18
|
import { pointOnCurve } from "./cubic.js";
|
|
2
|
-
import { circle, normalizePolygon, polygonBounds, polygonMaxRadius, rotatePolygon, roundedPolygon, scalePolygon, star, } from "./polygon.js";
|
|
19
|
+
import { circle, normalizePolygon, polygonBounds, polygonMaxRadius, rectangle, regularPolygon, rotatePolygon, roundedPolygon, scalePolygon, star, } from "./polygon.js";
|
|
3
20
|
const cornerRound15 = { radius: 0.15 };
|
|
21
|
+
const cornerRound20 = { radius: 0.2 };
|
|
22
|
+
const cornerRound30 = { radius: 0.3 };
|
|
4
23
|
const cornerRound50 = { radius: 0.5 };
|
|
5
|
-
const
|
|
24
|
+
const cornerRound100 = { radius: 1 };
|
|
25
|
+
const point = (x, y, radius = 0, smoothing = 0) => ({ x, y, r: { radius, smoothing } });
|
|
6
26
|
const toRadians = (degrees) => (degrees / 360) * 2 * Math.PI;
|
|
27
|
+
/**
|
|
28
|
+
* Repeats a few points around the centre, `reps` times, optionally mirroring
|
|
29
|
+
* every other repetition (MaterialShapes.doRepeat)
|
|
30
|
+
*/
|
|
7
31
|
const repeat = (points, reps, cx, cy, mirroring) => {
|
|
8
32
|
const result = [];
|
|
9
33
|
if (mirroring) {
|
|
@@ -16,7 +40,7 @@ const repeat = (points, reps, cx, cy, mirroring) => {
|
|
|
16
40
|
const i = it % 2 === 0 ? index : points.length - 1 - index;
|
|
17
41
|
if (i > 0 || it % 2 === 0) {
|
|
18
42
|
const a = toRadians(sectionAngle * it + (it % 2 === 0 ? angles[i] : sectionAngle - angles[i] + 2 * angles[0]));
|
|
19
|
-
result.push(
|
|
43
|
+
result.push({ x: Math.cos(a) * distances[i] + cx, y: Math.sin(a) * distances[i] + cy, r: points[i].r });
|
|
20
44
|
}
|
|
21
45
|
}
|
|
22
46
|
}
|
|
@@ -28,43 +52,184 @@ const repeat = (points, reps, cx, cy, mirroring) => {
|
|
|
28
52
|
const a = toRadians(Math.floor(it / np) * (360 / reps));
|
|
29
53
|
const dx = p.x - cx;
|
|
30
54
|
const dy = p.y - cy;
|
|
31
|
-
result.push(
|
|
55
|
+
result.push({ x: dx * Math.cos(a) - dy * Math.sin(a) + cx, y: dx * Math.sin(a) + dy * Math.cos(a) + cy, r: p.r });
|
|
32
56
|
}
|
|
33
57
|
return result;
|
|
34
58
|
};
|
|
35
|
-
|
|
36
|
-
|
|
59
|
+
/** MaterialShapes.customPolygon: points repeated around (0.5, 0.5) */
|
|
60
|
+
const custom = (pnr, reps, mirroring = false) => {
|
|
61
|
+
const points = repeat(pnr, reps, 0.5, 0.5, mirroring);
|
|
37
62
|
const vertices = [];
|
|
38
63
|
for (const p of points)
|
|
39
64
|
vertices.push(p.x, p.y);
|
|
40
|
-
return roundedPolygon(vertices, undefined, points.map((p) => p.r),
|
|
65
|
+
return roundedPolygon(vertices, undefined, points.map((p) => p.r), 0.5, 0.5);
|
|
41
66
|
};
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
pentagon: () => customPolygon([point(0.5, -0.009, { radius: 0.172 }), point(1.03, 0.365, { radius: 0.164 }), point(0.828, 0.97, { radius: 0.169 })], 1, true),
|
|
47
|
-
sunny: () => star(8, 1, 0.8, cornerRound15),
|
|
48
|
-
cookie4: () => customPolygon([point(1.237, 1.236, { radius: 0.258 }), point(0.5, 0.918, { radius: 0.233 })], 4),
|
|
49
|
-
cookie9: () => rotatePolygon(star(9, 1, 0.8, cornerRound50), -90),
|
|
50
|
-
softBurst: () => customPolygon([point(0.193, 0.277, { radius: 0.053 }), point(0.176, 0.055, { radius: 0.053 })], 10),
|
|
67
|
+
/** A shape built once, on first use, and normalised into the unit square */
|
|
68
|
+
const shape = (build) => {
|
|
69
|
+
let built;
|
|
70
|
+
return () => (built ?? (built = normalizePolygon(build())));
|
|
51
71
|
};
|
|
52
|
-
|
|
53
|
-
export const
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
72
|
+
/** Circle */
|
|
73
|
+
export const shapeCircle = /*#__PURE__*/ shape(() => circle(10));
|
|
74
|
+
/** A rounded square */
|
|
75
|
+
export const shapeSquare = /*#__PURE__*/ shape(() => rectangle(1, 1, cornerRound30));
|
|
76
|
+
/** A slanted square */
|
|
77
|
+
export const shapeSlanted = /*#__PURE__*/ shape(() => custom([point(0.926, 0.97, 0.189, 0.811), point(-0.021, 0.967, 0.187, 0.057)], 2));
|
|
78
|
+
/** An arch */
|
|
79
|
+
export const shapeArch = /*#__PURE__*/ shape(() => rotatePolygon(regularPolygon(4, 1, 0, 0, undefined, [cornerRound100, cornerRound100, cornerRound20, cornerRound20]), -135));
|
|
80
|
+
/** A fan */
|
|
81
|
+
export const shapeFan = /*#__PURE__*/ shape(() => custom([point(1.004, 1.0, 0.148, 0.417), point(0.0, 1.0, 0.151), point(0.0, -0.003, 0.148), point(0.978, 0.02, 0.803)], 1));
|
|
82
|
+
/** An arrow */
|
|
83
|
+
export const shapeArrow = /*#__PURE__*/ shape(() => custom([point(0.5, 0.892, 0.313), point(-0.216, 1.05, 0.207), point(0.499, -0.16, 0.215, 1.0), point(1.225, 1.06, 0.211)], 1));
|
|
84
|
+
/** A semicircle */
|
|
85
|
+
export const shapeSemiCircle = /*#__PURE__*/ shape(() => rectangle(1.6, 1, undefined, [cornerRound20, cornerRound20, cornerRound100, cornerRound100]));
|
|
86
|
+
/** An oval */
|
|
87
|
+
export const shapeOval = /*#__PURE__*/ shape(() => rotatePolygon(scalePolygon(circle(), 1, 0.64), -45));
|
|
88
|
+
/** A pill */
|
|
89
|
+
export const shapePill = /*#__PURE__*/ shape(() => custom([point(0.961, 0.039, 0.426), point(1.001, 0.428), point(1.0, 0.609, 1.0)], 2, true));
|
|
90
|
+
/** A triangle */
|
|
91
|
+
export const shapeTriangle = /*#__PURE__*/ shape(() => rotatePolygon(regularPolygon(3, 1, 0, 0, cornerRound20), -90));
|
|
92
|
+
/** A diamond */
|
|
93
|
+
export const shapeDiamond = /*#__PURE__*/ shape(() => custom([point(0.5, 1.096, 0.151, 0.524), point(0.04, 0.5, 0.159)], 2));
|
|
94
|
+
/** A clam shell */
|
|
95
|
+
export const shapeClamShell = /*#__PURE__*/ shape(() => custom([point(0.171, 0.841, 0.159), point(-0.02, 0.5, 0.14), point(0.17, 0.159, 0.159)], 2));
|
|
96
|
+
/** A pentagon */
|
|
97
|
+
export const shapePentagon = /*#__PURE__*/ shape(() => custom([point(0.5, -0.009, 0.172), point(1.03, 0.365, 0.164), point(0.828, 0.97, 0.169)], 1, true));
|
|
98
|
+
/** A gem */
|
|
99
|
+
export const shapeGem = /*#__PURE__*/ shape(() => custom([point(0.499, 1.023, 0.241, 0.778), point(-0.005, 0.792, 0.208), point(0.073, 0.258, 0.228), point(0.433, -0.0, 0.491)], 1, true));
|
|
100
|
+
/** A sunny shape: an 8-point star */
|
|
101
|
+
export const shapeSunny = /*#__PURE__*/ shape(() => star(8, 1, 0.8, cornerRound15));
|
|
102
|
+
/** A very sunny shape */
|
|
103
|
+
export const shapeVerySunny = /*#__PURE__*/ shape(() => custom([point(0.5, 1.08, 0.085), point(0.358, 0.843, 0.085)], 8));
|
|
104
|
+
/** A 4-sided cookie */
|
|
105
|
+
export const shapeCookie4Sided = /*#__PURE__*/ shape(() => custom([point(1.237, 1.236, 0.258), point(0.5, 0.918, 0.233)], 4));
|
|
106
|
+
/** A 6-sided cookie */
|
|
107
|
+
export const shapeCookie6Sided = /*#__PURE__*/ shape(() => custom([point(0.723, 0.884, 0.394), point(0.5, 1.099, 0.398)], 6));
|
|
108
|
+
/** A 7-sided cookie */
|
|
109
|
+
export const shapeCookie7Sided = /*#__PURE__*/ shape(() => rotatePolygon(star(7, 1, 0.75, cornerRound50), -90));
|
|
110
|
+
/** A 9-sided cookie */
|
|
111
|
+
export const shapeCookie9Sided = /*#__PURE__*/ shape(() => rotatePolygon(star(9, 1, 0.8, cornerRound50), -90));
|
|
112
|
+
/** A 12-sided cookie */
|
|
113
|
+
export const shapeCookie12Sided = /*#__PURE__*/ shape(() => rotatePolygon(star(12, 1, 0.8, cornerRound50), -90));
|
|
114
|
+
/** A ghost-ish shape */
|
|
115
|
+
export const shapeGhostish = /*#__PURE__*/ shape(() => custom([point(0.5, 0, 1.0), point(1, 0, 1.0), point(1, 1.14, 0.254, 0.106), point(0.575, 0.906, 0.253)], 1, true));
|
|
116
|
+
/** A 4-leaf clover */
|
|
117
|
+
export const shapeClover4Leaf = /*#__PURE__*/ shape(() => custom([point(0.5, 0.074), point(0.725, -0.099, 0.476)], 4, true));
|
|
118
|
+
/** An 8-leaf clover */
|
|
119
|
+
export const shapeClover8Leaf = /*#__PURE__*/ shape(() => custom([point(0.5, 0.036), point(0.758, -0.101, 0.209)], 8));
|
|
120
|
+
/** A burst */
|
|
121
|
+
export const shapeBurst = /*#__PURE__*/ shape(() => custom([point(0.5, -0.006, 0.006), point(0.592, 0.158, 0.006)], 12));
|
|
122
|
+
/** A soft burst */
|
|
123
|
+
export const shapeSoftBurst = /*#__PURE__*/ shape(() => custom([point(0.193, 0.277, 0.053), point(0.176, 0.055, 0.053)], 10));
|
|
124
|
+
/** A boom */
|
|
125
|
+
export const shapeBoom = /*#__PURE__*/ shape(() => custom([point(0.457, 0.296, 0.007), point(0.5, -0.051, 0.007)], 15));
|
|
126
|
+
/** A soft boom */
|
|
127
|
+
export const shapeSoftBoom = /*#__PURE__*/ shape(() => custom([point(0.733, 0.454), point(0.839, 0.437, 0.532), point(0.949, 0.449, 0.439, 1.0), point(0.998, 0.478, 0.174)], 16, true));
|
|
128
|
+
/** A flower */
|
|
129
|
+
export const shapeFlower = /*#__PURE__*/ shape(() => custom([point(0.37, 0.187), point(0.416, 0.049, 0.381), point(0.479, 0.001, 0.095)], 8, true));
|
|
130
|
+
/** A puffy shape */
|
|
131
|
+
export const shapePuffy = /*#__PURE__*/ shape(() => scalePolygon(custom([
|
|
132
|
+
point(0.5, 0.053), point(0.545, -0.04, 0.405), point(0.67, -0.035, 0.426), point(0.717, 0.066, 0.574),
|
|
133
|
+
point(0.722, 0.128), point(0.777, 0.002, 0.36), point(0.914, 0.149, 0.66), point(0.926, 0.289, 0.66),
|
|
134
|
+
point(0.881, 0.346), point(0.94, 0.344, 0.126), point(1.003, 0.437, 0.255),
|
|
135
|
+
], 2, true), 1, 0.742));
|
|
136
|
+
/** A puffy diamond */
|
|
137
|
+
export const shapePuffyDiamond = /*#__PURE__*/ shape(() => custom([point(0.87, 0.13, 0.146), point(0.818, 0.357), point(1.0, 0.332, 0.853)], 4, true));
|
|
138
|
+
/** A pixelated circle */
|
|
139
|
+
export const shapePixelCircle = /*#__PURE__*/ shape(() => custom([
|
|
140
|
+
point(0.5, 0.0), point(0.704, 0.0), point(0.704, 0.065), point(0.843, 0.065),
|
|
141
|
+
point(0.843, 0.148), point(0.926, 0.148), point(0.926, 0.296), point(1.0, 0.296),
|
|
142
|
+
], 2, true));
|
|
143
|
+
/** A pixelated triangle */
|
|
144
|
+
export const shapePixelTriangle = /*#__PURE__*/ shape(() => custom([
|
|
145
|
+
point(0.11, 0.5), point(0.113, 0.0), point(0.287, 0.0), point(0.287, 0.087), point(0.421, 0.087),
|
|
146
|
+
point(0.421, 0.17), point(0.56, 0.17), point(0.56, 0.265), point(0.674, 0.265), point(0.675, 0.344),
|
|
147
|
+
point(0.789, 0.344), point(0.789, 0.439), point(0.888, 0.439),
|
|
148
|
+
], 1, true));
|
|
149
|
+
/** A bun */
|
|
150
|
+
export const shapeBun = /*#__PURE__*/ shape(() => custom([point(0.796, 0.5), point(0.853, 0.518, 1), point(0.992, 0.631, 1), point(0.968, 1.0, 1)], 2, true));
|
|
151
|
+
/** A heart */
|
|
152
|
+
export const shapeHeart = /*#__PURE__*/ shape(() => custom([point(0.5, 0.268, 0.016), point(0.792, -0.066, 0.958), point(1.064, 0.276, 1.0), point(0.501, 0.946, 0.129)], 1, true));
|
|
153
|
+
const byName = {
|
|
154
|
+
circle: shapeCircle,
|
|
155
|
+
square: shapeSquare,
|
|
156
|
+
slanted: shapeSlanted,
|
|
157
|
+
arch: shapeArch,
|
|
158
|
+
fan: shapeFan,
|
|
159
|
+
arrow: shapeArrow,
|
|
160
|
+
semiCircle: shapeSemiCircle,
|
|
161
|
+
oval: shapeOval,
|
|
162
|
+
pill: shapePill,
|
|
163
|
+
triangle: shapeTriangle,
|
|
164
|
+
diamond: shapeDiamond,
|
|
165
|
+
clamShell: shapeClamShell,
|
|
166
|
+
pentagon: shapePentagon,
|
|
167
|
+
gem: shapeGem,
|
|
168
|
+
sunny: shapeSunny,
|
|
169
|
+
verySunny: shapeVerySunny,
|
|
170
|
+
cookie4Sided: shapeCookie4Sided,
|
|
171
|
+
cookie6Sided: shapeCookie6Sided,
|
|
172
|
+
cookie7Sided: shapeCookie7Sided,
|
|
173
|
+
cookie9Sided: shapeCookie9Sided,
|
|
174
|
+
cookie12Sided: shapeCookie12Sided,
|
|
175
|
+
ghostish: shapeGhostish,
|
|
176
|
+
clover4Leaf: shapeClover4Leaf,
|
|
177
|
+
clover8Leaf: shapeClover8Leaf,
|
|
178
|
+
burst: shapeBurst,
|
|
179
|
+
softBurst: shapeSoftBurst,
|
|
180
|
+
boom: shapeBoom,
|
|
181
|
+
softBoom: shapeSoftBoom,
|
|
182
|
+
flower: shapeFlower,
|
|
183
|
+
puffy: shapePuffy,
|
|
184
|
+
puffyDiamond: shapePuffyDiamond,
|
|
185
|
+
pixelCircle: shapePixelCircle,
|
|
186
|
+
pixelTriangle: shapePixelTriangle,
|
|
187
|
+
bun: shapeBun,
|
|
188
|
+
heart: shapeHeart,
|
|
189
|
+
/** @deprecated Use 'cookie4Sided' */
|
|
190
|
+
cookie4: shapeCookie4Sided,
|
|
191
|
+
/** @deprecated Use 'cookie9Sided' */
|
|
192
|
+
cookie9: shapeCookie9Sided,
|
|
193
|
+
};
|
|
194
|
+
/** A Material shape by name, normalised into the unit square. Carries every shape; import `shapeX` to carry one. */
|
|
195
|
+
export const materialShape = (name) => byName[name]();
|
|
196
|
+
const num = (value) => {
|
|
197
|
+
const rounded = Math.round(value * 1000) / 1000;
|
|
198
|
+
return String(Object.is(rounded, -0) ? 0 : rounded);
|
|
60
199
|
};
|
|
200
|
+
/**
|
|
201
|
+
* A polygon's outline as an SVG path ("M … C … Z"), scaled by `size`: a
|
|
202
|
+
* normalised shape fills a `size` × `size` box. Coordinates keep three decimals.
|
|
203
|
+
*/
|
|
204
|
+
export const polygonPath = (polygon, size = 1) => {
|
|
205
|
+
const cubics = polygon.cubics;
|
|
206
|
+
if (!cubics.length)
|
|
207
|
+
return '';
|
|
208
|
+
const s = (v) => num(v * size);
|
|
209
|
+
let d = `M${s(cubics[0][0])} ${s(cubics[0][1])}`;
|
|
210
|
+
for (const c of cubics)
|
|
211
|
+
d += `C${s(c[2])} ${s(c[3])} ${s(c[4])} ${s(c[5])} ${s(c[6])} ${s(c[7])}`;
|
|
212
|
+
return `${d}Z`;
|
|
213
|
+
};
|
|
214
|
+
/** A Material shape as an SVG path in a `size` × `size` box. Carries every shape, as materialShape does. */
|
|
215
|
+
export const materialShapePath = (name, size = 1) => polygonPath(materialShape(name), size);
|
|
61
216
|
const TWO_PI = Math.PI * 2;
|
|
62
|
-
|
|
217
|
+
/**
|
|
218
|
+
* radialProfile without its star-shape check, for callers whose shapes are
|
|
219
|
+
* known to be star-shaped: the loading indicator's, which the tests check.
|
|
220
|
+
* The centroid, not the bounds centre, is the pivot: a pentagon's mass sits
|
|
221
|
+
* below the middle of its box, and turning it about the box would make it
|
|
222
|
+
* orbit.
|
|
223
|
+
* @internal
|
|
224
|
+
*/
|
|
225
|
+
export const sampleProfile = (polygon, samples = 360, stepsPerCubic = 24) => {
|
|
226
|
+
// The outline as a dense polygon
|
|
63
227
|
const points = [];
|
|
64
228
|
for (const cubic of polygon.cubics) {
|
|
65
229
|
for (let s = 0; s < stepsPerCubic; s++)
|
|
66
230
|
points.push(pointOnCurve(cubic, s / stepsPerCubic));
|
|
67
231
|
}
|
|
232
|
+
// Its area centroid (shoelace formula)
|
|
68
233
|
let area = 0;
|
|
69
234
|
let centerX = 0;
|
|
70
235
|
let centerY = 0;
|
|
@@ -85,6 +250,7 @@ export const radialProfile = (polygon, samples = 360, stepsPerCubic = 24) => {
|
|
|
85
250
|
centerX = (bounds[0] + bounds[2]) / 2;
|
|
86
251
|
centerY = (bounds[1] + bounds[3]) / 2;
|
|
87
252
|
}
|
|
253
|
+
// The outline as (angle, radius) pairs, sorted by angle
|
|
88
254
|
const outline = [];
|
|
89
255
|
for (const [x, y] of points) {
|
|
90
256
|
const dx = x - centerX;
|
|
@@ -116,4 +282,39 @@ export const radialProfile = (polygon, samples = 360, stepsPerCubic = 24) => {
|
|
|
116
282
|
}
|
|
117
283
|
return { radii, centerX, centerY, maxRadius };
|
|
118
284
|
};
|
|
285
|
+
/**
|
|
286
|
+
* Samples the outline at `samples` angles around the shape's area centroid.
|
|
287
|
+
* Throws for a shape that is not star-shaped around it. Of the Material
|
|
288
|
+
* shapes, 33 are; Puffy and PixelTriangle are not (their outlines double back
|
|
289
|
+
* as seen from the centroid, Puffy by about 0.1°): a ray from the centroid
|
|
290
|
+
* would cross the outline more than once, and the profile would be wrong.
|
|
291
|
+
*/
|
|
292
|
+
export const radialProfile = (polygon, samples = 360, stepsPerCubic = 24) => {
|
|
293
|
+
const profile = sampleProfile(polygon, samples, stepsPerCubic);
|
|
294
|
+
const { centerX, centerY } = profile;
|
|
295
|
+
const points = [];
|
|
296
|
+
for (const cubic of polygon.cubics) {
|
|
297
|
+
for (let s = 0; s < stepsPerCubic; s++)
|
|
298
|
+
points.push(pointOnCurve(cubic, s / stepsPerCubic));
|
|
299
|
+
}
|
|
300
|
+
// Star-shaped around the centroid: walking the outline, the angle seen from
|
|
301
|
+
// the centroid only ever turns one way. A shape that doubles back (a heart's
|
|
302
|
+
// notch, an arch's inner curve) would be sampled wrong: refuse it.
|
|
303
|
+
let turn = 0;
|
|
304
|
+
for (let i = 0; i < points.length; i++) {
|
|
305
|
+
const [x0, y0] = points[i];
|
|
306
|
+
const [x1, y1] = points[(i + 1) % points.length];
|
|
307
|
+
let step = Math.atan2(y1 - centerY, x1 - centerX) - Math.atan2(y0 - centerY, x0 - centerX);
|
|
308
|
+
if (step > Math.PI)
|
|
309
|
+
step -= TWO_PI;
|
|
310
|
+
else if (step < -Math.PI)
|
|
311
|
+
step += TWO_PI;
|
|
312
|
+
if (turn === 0)
|
|
313
|
+
turn = Math.sign(step);
|
|
314
|
+
else if (step * turn < -1e-9) {
|
|
315
|
+
throw new Error("radialProfile: the shape is not star-shaped around its centroid, so it has no radial profile");
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
return profile;
|
|
319
|
+
};
|
|
119
320
|
export { polygonMaxRadius };
|
|
@@ -23,6 +23,8 @@ export declare const regularPolygon: (numVertices: number, radius?: number, cent
|
|
|
23
23
|
export declare const circle: (numVertices?: number, radius?: number, centerX?: number, centerY?: number) => RoundedPolygon;
|
|
24
24
|
/** A star with `numVerticesPerRadius` outer and inner vertices, outer first at angle 0 */
|
|
25
25
|
export declare const star: (numVerticesPerRadius: number, radius?: number, innerRadius?: number, rounding?: CornerRounding, innerRounding?: CornerRounding, perVertexRounding?: CornerRounding[], centerX?: number, centerY?: number) => RoundedPolygon;
|
|
26
|
+
/** A rectangle centred on (centerX, centerY), its first vertex at the bottom right (graphics-shapes rectangle) */
|
|
27
|
+
export declare const rectangle: (width?: number, height?: number, rounding?: CornerRounding, perVertexRounding?: CornerRounding[], centerX?: number, centerY?: number) => RoundedPolygon;
|
|
26
28
|
export declare const transformPolygon: (polygon: RoundedPolygon, f: PointTransformer) => RoundedPolygon;
|
|
27
29
|
/** Rotation about the origin, in degrees, positive clockwise on a y-down canvas */
|
|
28
30
|
export declare const rotatePolygon: (polygon: RoundedPolygon, degrees: number) => RoundedPolygon;
|