@zumer/orbit 1.5.0 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +233 -143
- package/dist/custom-elements.json +250 -0
- package/dist/orbit.css +2820 -1233
- package/dist/orbit.d.ts +107 -0
- package/dist/orbit.js +368 -205
- package/dist/orbit.min.css +1 -1
- package/dist/orbit.min.js +42 -26
- package/dist/orbit.mjs +368 -205
- package/dist/react.d.ts +25 -0
- package/dist/style.d.ts +3 -0
- package/dist/vscode.html-custom-data.json +166 -0
- package/package.json +23 -4
package/dist/orbit.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/*
|
|
2
2
|
* orbit
|
|
3
|
-
* v.
|
|
3
|
+
* v.2.0.0
|
|
4
4
|
* Author Juan Martin Muda - Zumerlab
|
|
5
5
|
* License MIT
|
|
6
6
|
*/
|
|
@@ -18,6 +18,56 @@
|
|
|
18
18
|
}
|
|
19
19
|
return ((_a = element.classList) == null ? void 0 : _a.contains("orbit")) ? null : void 0;
|
|
20
20
|
}
|
|
21
|
+
var WARNED = Symbol.for("zumer.orbit.warned.v1");
|
|
22
|
+
function warnOnce(element, key, message) {
|
|
23
|
+
const seen = element[WARNED] || (element[WARNED] = /* @__PURE__ */ new Set());
|
|
24
|
+
if (seen.has(key)) return;
|
|
25
|
+
seen.add(key);
|
|
26
|
+
console.warn(`[orbit] ${message}`);
|
|
27
|
+
}
|
|
28
|
+
function composedClosest(element, selector) {
|
|
29
|
+
var _a, _b;
|
|
30
|
+
for (let current = element; current; current = current.parentElement || ((_a = current.getRootNode) == null ? void 0 : _a.call(current).host)) {
|
|
31
|
+
if ((_b = current.matches) == null ? void 0 : _b.call(current, selector)) return current;
|
|
32
|
+
}
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
var KINDS = ".satellite, .vector, .side, o-arc, o-progress";
|
|
36
|
+
function describe(element) {
|
|
37
|
+
const classes = [...element.classList].slice(0, 3).map((name) => "." + name).join("");
|
|
38
|
+
return `<${element.localName}${classes ? " " + classes : ""}>`;
|
|
39
|
+
}
|
|
40
|
+
function diagnose(ring) {
|
|
41
|
+
const spot = ring.parentElement;
|
|
42
|
+
const universe = composedClosest(spot.parentElement, ".bigbang, .satellite");
|
|
43
|
+
if (!universe) {
|
|
44
|
+
warnOnce(spot, "universe", 'A .gravity-spot is not inside a .bigbang, so it measures the viewport. Wrap it: <div class="bigbang"><div class="gravity-spot">\u2026</div></div>.');
|
|
45
|
+
} else if (universe.getClientRects().length && (!universe.clientWidth || !universe.clientHeight)) {
|
|
46
|
+
warnOnce(universe, "empty", `${describe(universe)} has no size (${universe.clientWidth}\xD7${universe.clientHeight}px), so its system is invisible. Give it a width, or place it in a parent with a width.`);
|
|
47
|
+
}
|
|
48
|
+
const visual = ring.querySelector(":scope > o-arc, :scope > o-progress");
|
|
49
|
+
if (visual) {
|
|
50
|
+
const style = ring.ownerDocument.defaultView.getComputedStyle(ring);
|
|
51
|
+
const x = style.getPropertyValue("--o-ellipse-x").trim(), y = style.getPropertyValue("--o-ellipse-y").trim();
|
|
52
|
+
if (x && Number(x) !== 1 || y && Number(y) !== 1) {
|
|
53
|
+
warnOnce(ring, "ellipse-visual", `${describe(visual)} is on ${describe(ring)}, which is stretched into an ellipse (--o-ellipse-x: ${x || 1}, --o-ellipse-y: ${y || 1}). Arcs and progress are drawn on circles only, so it is hidden. Put it on a round ring.`);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
if (!composedClosest(ring, ".dev-orbit")) return;
|
|
57
|
+
for (const child of ring.children) {
|
|
58
|
+
if (orbitNumber(child) !== void 0) {
|
|
59
|
+
warnOnce(child, "nested-ring", `${describe(child)} is inside another ring. Rings must be direct children of .gravity-spot; to nest a system, put a .gravity-spot directly inside a .satellite.`);
|
|
60
|
+
} else if (!child.matches(KINDS)) {
|
|
61
|
+
warnOnce(child, "ring-child", `${describe(child)} is not placed by its ring. A ring holds only .satellite, .vector, .side, <o-arc> and <o-progress>; wrap content as <div class="satellite"><div class="capsule">\u2026</div></div>.`);
|
|
62
|
+
} else if (child.classList.contains("satellite")) {
|
|
63
|
+
for (const inner of child.children) {
|
|
64
|
+
if (!inner.matches(".capsule, .gravity-spot, .bigbang")) {
|
|
65
|
+
warnOnce(inner, "satellite-child", `${describe(inner)} sits directly in a .satellite. Put content in a .capsule so it stays upright and centered.`);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
21
71
|
function setLayoutProperty(element, name, value) {
|
|
22
72
|
const text = String(value);
|
|
23
73
|
if (element.style.getPropertyValue(name) !== text) element.style.setProperty(name, text);
|
|
@@ -62,7 +112,8 @@
|
|
|
62
112
|
else if (child.localName === "o-progress") setLayoutProperty(child, "--o-layout-index", 0);
|
|
63
113
|
}
|
|
64
114
|
const count = Math.max(counts.arc, counts.satellite, counts.vector, 1);
|
|
65
|
-
const
|
|
115
|
+
const points = counts.satellite || counts.vector;
|
|
116
|
+
const divisor = counts.side ? String(counts.side) : points ? `max(1, ${count} - var(--o-fit-range, 0))` : String(count);
|
|
66
117
|
setLayoutProperty(ring, "--o-layout-angle", `calc(var(--o-range, 360deg) / ${divisor})`);
|
|
67
118
|
}
|
|
68
119
|
function isScope(root) {
|
|
@@ -88,10 +139,19 @@
|
|
|
88
139
|
});
|
|
89
140
|
return doc[REGISTRY];
|
|
90
141
|
}
|
|
142
|
+
function cancel(state) {
|
|
143
|
+
if (state.frame < 0) state.view.clearTimeout(-state.frame);
|
|
144
|
+
else if (state.frame) state.view.cancelAnimationFrame(state.frame);
|
|
145
|
+
state.frame = 0;
|
|
146
|
+
}
|
|
147
|
+
function settle(state) {
|
|
148
|
+
for (const resolve of state.waiters.splice(0)) resolve();
|
|
149
|
+
}
|
|
91
150
|
function dispose(state) {
|
|
92
151
|
if (state.disposed) return;
|
|
93
152
|
state.disposed = true;
|
|
94
|
-
|
|
153
|
+
cancel(state);
|
|
154
|
+
settle(state);
|
|
95
155
|
state.observer.disconnect();
|
|
96
156
|
state.resize.disconnect();
|
|
97
157
|
for (const remove of state.listeners) remove();
|
|
@@ -146,17 +206,20 @@
|
|
|
146
206
|
all: true,
|
|
147
207
|
frame: 0,
|
|
148
208
|
disposed: false,
|
|
149
|
-
listeners: []
|
|
209
|
+
listeners: [],
|
|
210
|
+
waiters: []
|
|
150
211
|
};
|
|
151
212
|
const listen = (target, event, listener, options) => {
|
|
152
213
|
target == null ? void 0 : target.addEventListener(event, listener, options);
|
|
153
214
|
state.listeners.push(() => target == null ? void 0 : target.removeEventListener(event, listener, options));
|
|
154
215
|
};
|
|
155
216
|
const queue = () => {
|
|
156
|
-
if (
|
|
217
|
+
if (state.disposed || state.frame) return;
|
|
218
|
+
const run = () => {
|
|
157
219
|
state.frame = 0;
|
|
158
220
|
state.flush();
|
|
159
|
-
}
|
|
221
|
+
};
|
|
222
|
+
state.frame = doc.visibilityState === "hidden" ? -view.setTimeout(run, 0) : view.requestAnimationFrame(run);
|
|
160
223
|
};
|
|
161
224
|
state.queue = queue;
|
|
162
225
|
state.flush = () => flush(state);
|
|
@@ -196,6 +259,12 @@
|
|
|
196
259
|
invalidateDescendants(state);
|
|
197
260
|
queue();
|
|
198
261
|
};
|
|
262
|
+
listen(doc, "visibilitychange", () => {
|
|
263
|
+
if (doc.visibilityState === "hidden" && state.frame > 0) {
|
|
264
|
+
cancel(state);
|
|
265
|
+
queue();
|
|
266
|
+
}
|
|
267
|
+
});
|
|
199
268
|
if (scope === doc) {
|
|
200
269
|
listen(view, "resize", invalidateAll);
|
|
201
270
|
listen(doc.fonts, "loadingdone", invalidateAll);
|
|
@@ -255,13 +324,13 @@
|
|
|
255
324
|
state.scan = false;
|
|
256
325
|
}
|
|
257
326
|
function flush(state) {
|
|
258
|
-
var _a;
|
|
259
327
|
if (state.disposed) return;
|
|
260
328
|
if (state.scope.host && !state.scope.host.isConnected) {
|
|
261
329
|
dispose(state);
|
|
262
330
|
return;
|
|
263
331
|
}
|
|
264
332
|
state.observer.disconnect();
|
|
333
|
+
const drawn = [];
|
|
265
334
|
try {
|
|
266
335
|
if (state.scan) discover(state);
|
|
267
336
|
const dirty = state.all ? /* @__PURE__ */ new Set([...state.rings, ...state.visuals]) : state.dirty;
|
|
@@ -270,15 +339,21 @@
|
|
|
270
339
|
if (!target.isConnected) continue;
|
|
271
340
|
if (state.rings.has(target)) {
|
|
272
341
|
layoutRing(target);
|
|
342
|
+
diagnose(target);
|
|
273
343
|
for (const visual of target.querySelectorAll(VISUALS)) visuals.add(visual);
|
|
274
344
|
} else if (state.visuals.has(target)) visuals.add(target);
|
|
275
345
|
}
|
|
276
|
-
for (const visual of state.visuals) if (visuals.has(visual)
|
|
346
|
+
for (const visual of state.visuals) if (visuals.has(visual) && visual.update) {
|
|
347
|
+
visual.update();
|
|
348
|
+
drawn.push(visual);
|
|
349
|
+
}
|
|
277
350
|
state.dirty = /* @__PURE__ */ new Set();
|
|
278
351
|
state.all = false;
|
|
279
352
|
} finally {
|
|
280
353
|
if (!state.disposed) state.observer.observe(state.scope, OBSERVE);
|
|
281
354
|
}
|
|
355
|
+
for (const visual of drawn) visual.dispatchEvent(new state.view.Event("o-rendered", { bubbles: true, composed: true }));
|
|
356
|
+
if (!state.frame) settle(state);
|
|
282
357
|
}
|
|
283
358
|
function requestLayout(element = globalThis.document) {
|
|
284
359
|
var _a;
|
|
@@ -299,11 +374,22 @@
|
|
|
299
374
|
if (current !== state && (!current.scope.host || !composedContains(root, current.scope.host))) continue;
|
|
300
375
|
current.scan = true;
|
|
301
376
|
current.all = true;
|
|
302
|
-
|
|
303
|
-
current.frame = 0;
|
|
377
|
+
cancel(current);
|
|
304
378
|
current.flush();
|
|
305
379
|
}
|
|
306
380
|
}
|
|
381
|
+
function layoutReady(root = globalThis.document) {
|
|
382
|
+
const scope = scopeFor(root);
|
|
383
|
+
const state = scope && ensureState(scope);
|
|
384
|
+
if (!state) return Promise.resolve();
|
|
385
|
+
pruneDisconnected(state.registry);
|
|
386
|
+
const pending = [...state.registry.states].filter((current) => current === state || current.scope.host && composedContains(root, current.scope.host));
|
|
387
|
+
return Promise.all(pending.map((current) => new Promise((resolve) => {
|
|
388
|
+
current.waiters.push(resolve);
|
|
389
|
+
current.queue();
|
|
390
|
+
}))).then(() => {
|
|
391
|
+
});
|
|
392
|
+
}
|
|
307
393
|
|
|
308
394
|
// src/js/orbit-base.js
|
|
309
395
|
var OrbitBase = class extends (globalThis.HTMLElement || class {
|
|
@@ -312,8 +398,32 @@
|
|
|
312
398
|
super();
|
|
313
399
|
}
|
|
314
400
|
connectedCallback() {
|
|
401
|
+
for (const name of ["value", "max"]) {
|
|
402
|
+
if (Object.prototype.hasOwnProperty.call(this, name)) {
|
|
403
|
+
const pending = this[name];
|
|
404
|
+
delete this[name];
|
|
405
|
+
this[name] = pending;
|
|
406
|
+
}
|
|
407
|
+
}
|
|
315
408
|
requestLayout(this);
|
|
316
409
|
}
|
|
410
|
+
/** value and max mirror their attributes, so property bindings redraw too. */
|
|
411
|
+
get value() {
|
|
412
|
+
const raw = this.getAttribute("value");
|
|
413
|
+
return raw === null || raw.trim() === "" ? null : Number(raw);
|
|
414
|
+
}
|
|
415
|
+
set value(next) {
|
|
416
|
+
if (next === null || next === void 0 || next === "") this.removeAttribute("value");
|
|
417
|
+
else this.setAttribute("value", String(next));
|
|
418
|
+
}
|
|
419
|
+
get max() {
|
|
420
|
+
const raw = this.getAttribute("max");
|
|
421
|
+
return raw === null ? 100 : Number(raw);
|
|
422
|
+
}
|
|
423
|
+
set max(next) {
|
|
424
|
+
if (next === null || next === void 0 || next === "") this.removeAttribute("max");
|
|
425
|
+
else this.setAttribute("max", String(next));
|
|
426
|
+
}
|
|
317
427
|
disconnectedCallback() {
|
|
318
428
|
if (this._textFrame) this.ownerDocument.defaultView.cancelAnimationFrame(this._textFrame);
|
|
319
429
|
this._textFrame = 0;
|
|
@@ -324,6 +434,28 @@
|
|
|
324
434
|
readAngle(style, name, fallback = 0) {
|
|
325
435
|
return cssNumber(this, style.getPropertyValue(name), fallback, "angle");
|
|
326
436
|
}
|
|
437
|
+
/** The value attribute as a number. Invalid values draw nothing, and say why. */
|
|
438
|
+
readValue() {
|
|
439
|
+
const raw = this.getAttribute("value");
|
|
440
|
+
if (raw === null || raw.trim() === "") return 0;
|
|
441
|
+
const value = Number(raw);
|
|
442
|
+
if (!Number.isFinite(value)) {
|
|
443
|
+
warnOnce(this, "value:" + raw, `<${this.localName} value="${raw}"> is not a number, so nothing is drawn.`);
|
|
444
|
+
return 0;
|
|
445
|
+
}
|
|
446
|
+
return value;
|
|
447
|
+
}
|
|
448
|
+
/** The max attribute (default 100). It must be a positive number. */
|
|
449
|
+
readMax() {
|
|
450
|
+
const raw = this.getAttribute("max");
|
|
451
|
+
if (raw === null) return 100;
|
|
452
|
+
const max = Number(raw);
|
|
453
|
+
if (!(max > 0)) {
|
|
454
|
+
warnOnce(this, "max:" + raw, `<${this.localName} max="${raw}"> must be a positive number, so nothing is drawn.`);
|
|
455
|
+
return 0;
|
|
456
|
+
}
|
|
457
|
+
return max;
|
|
458
|
+
}
|
|
327
459
|
getCommonAttributes(element) {
|
|
328
460
|
var _a;
|
|
329
461
|
const style = element.ownerDocument.defaultView.getComputedStyle(element);
|
|
@@ -351,7 +483,7 @@
|
|
|
351
483
|
arcHeight,
|
|
352
484
|
realRadius,
|
|
353
485
|
arcAngle: 0,
|
|
354
|
-
//
|
|
486
|
+
// set by each component
|
|
355
487
|
shape,
|
|
356
488
|
arcHeightPercentage,
|
|
357
489
|
orbitNumber: orbitNumber2,
|
|
@@ -364,22 +496,6 @@
|
|
|
364
496
|
if (!Number.isFinite(value) || !Number.isFinite(maxValue) || maxValue <= 0) return 0;
|
|
365
497
|
return Math.min(1, Math.max(0, value / maxValue)) * Math.max(0, Math.min(360, maxAngle));
|
|
366
498
|
}
|
|
367
|
-
getControlPoint(x, y, x1, y1, direction = "clockwise") {
|
|
368
|
-
const xm = (x + x1) / 2;
|
|
369
|
-
const ym = (y + y1) / 2;
|
|
370
|
-
const dx = x1 - x;
|
|
371
|
-
const dy = y1 - y;
|
|
372
|
-
if (direction === "clockwise") {
|
|
373
|
-
return {
|
|
374
|
-
xc: xm + dy * 0.4,
|
|
375
|
-
yc: ym - dx * 0.4
|
|
376
|
-
};
|
|
377
|
-
}
|
|
378
|
-
return {
|
|
379
|
-
xc: xm - dy * 0.4,
|
|
380
|
-
yc: ym + dx * 0.4
|
|
381
|
-
};
|
|
382
|
-
}
|
|
383
499
|
arcPoint(radius, angle, radiusAdjustment = 0, angleOffsetDegrees = 0) {
|
|
384
500
|
const adjustedRadius = radius + radiusAdjustment;
|
|
385
501
|
const adjustedAngle = angle + angleOffsetDegrees * Math.PI / 180;
|
|
@@ -390,7 +506,7 @@
|
|
|
390
506
|
}
|
|
391
507
|
calculateCommonArcParameters(arcAngle, radius, arcHeightPercentage, orbitNumber2, shape, strokeWidth, arcHeight, gap = 0) {
|
|
392
508
|
const offset = Math.PI / 2;
|
|
393
|
-
const fangle = Math.max(0, Math.min(
|
|
509
|
+
const fangle = Math.max(0, Math.min(360, arcAngle)) * Math.PI / 180;
|
|
394
510
|
const bigRadius = radius + arcHeightPercentage;
|
|
395
511
|
const smallRadius = Math.max(1e-3, radius - arcHeightPercentage);
|
|
396
512
|
const bigGap = Math.min(fangle * 0.49, (gap + strokeWidth * 1.25) / orbitNumber2 / bigRadius);
|
|
@@ -403,19 +519,11 @@
|
|
|
403
519
|
const upperArcEnd = this.arcPoint(bigRadius, upperAngleEnd);
|
|
404
520
|
const innerArcStart = this.arcPoint(smallRadius, innerAngleStart);
|
|
405
521
|
const innerArcEnd = this.arcPoint(smallRadius, innerAngleEnd);
|
|
406
|
-
const upperSweep = upperAngleEnd - upperAngleStart;
|
|
407
|
-
const innerSweep = innerAngleEnd - innerAngleStart;
|
|
408
|
-
const largeArcFlagUpper = upperSweep > Math.PI ? 1 : 0;
|
|
409
|
-
const largeArcFlagInner = innerSweep > Math.PI ? 1 : 0;
|
|
410
522
|
return {
|
|
411
523
|
upperArcStart,
|
|
412
524
|
upperArcEnd,
|
|
413
525
|
innerArcStart,
|
|
414
526
|
innerArcEnd,
|
|
415
|
-
largeArcFlag: largeArcFlagUpper,
|
|
416
|
-
// back-compat alias; prefer the two below
|
|
417
|
-
largeArcFlagUpper,
|
|
418
|
-
largeArcFlagInner,
|
|
419
527
|
bigRadius,
|
|
420
528
|
smallRadius,
|
|
421
529
|
radius,
|
|
@@ -425,143 +533,153 @@
|
|
|
425
533
|
innerAngleEnd
|
|
426
534
|
};
|
|
427
535
|
}
|
|
428
|
-
generatePathData(shape, params
|
|
429
|
-
let d = "";
|
|
536
|
+
generatePathData(shape, params) {
|
|
430
537
|
switch (shape) {
|
|
431
538
|
case "rounded":
|
|
432
|
-
|
|
433
|
-
break;
|
|
539
|
+
return this.generateRoundedPath(params);
|
|
434
540
|
case "circle":
|
|
435
541
|
case "circle-a":
|
|
436
542
|
case "bullet":
|
|
437
|
-
|
|
438
|
-
break;
|
|
543
|
+
return this.generateCirclePath(params, shape);
|
|
439
544
|
case "circle-b":
|
|
440
|
-
|
|
441
|
-
break;
|
|
545
|
+
return this.generateCircleBPath(params);
|
|
442
546
|
case "arrow":
|
|
443
|
-
|
|
444
|
-
break;
|
|
547
|
+
return this.generateArrowPath(params);
|
|
445
548
|
case "backslash":
|
|
446
549
|
case "slash":
|
|
447
|
-
|
|
448
|
-
break;
|
|
550
|
+
return this.generateSlashPath(params, shape);
|
|
449
551
|
case "zigzag":
|
|
450
|
-
|
|
451
|
-
break;
|
|
552
|
+
return this.generateZigzagPath(params);
|
|
452
553
|
default:
|
|
453
|
-
|
|
554
|
+
return this.generateDefaultPath(params);
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
/** "x,y" of a point at radius and angle (radians), in viewBox units. */
|
|
558
|
+
pt(radius, angle) {
|
|
559
|
+
const point = this.arcPoint(radius, angle);
|
|
560
|
+
return `${point.x},${point.y}`;
|
|
561
|
+
}
|
|
562
|
+
/**
|
|
563
|
+
* SVG arc commands from the current point (at `from`) to `to`, along one
|
|
564
|
+
* circle. Every arc in Orbit goes through here. Splitting into pieces of at
|
|
565
|
+
* most 90° means no large-arc flag to get wrong and no arc whose endpoints
|
|
566
|
+
* coincide, so a full 360° circle is just another arc.
|
|
567
|
+
*/
|
|
568
|
+
arcTo(radius, from, to) {
|
|
569
|
+
const steps = Math.max(1, Math.ceil(Math.abs(to - from) / (Math.PI / 2) - 1e-9));
|
|
570
|
+
const sweep = to >= from ? 1 : 0;
|
|
571
|
+
let d = "";
|
|
572
|
+
for (let i = 1; i <= steps; i++) {
|
|
573
|
+
d += ` A ${radius},${radius} 0 0 ${sweep} ${this.pt(radius, from + (to - from) * i / steps)}`;
|
|
454
574
|
}
|
|
455
575
|
return d;
|
|
456
576
|
}
|
|
457
|
-
|
|
577
|
+
/** An open arc along one radius: strokes, curved text and touch areas. */
|
|
578
|
+
linePath(radius, from, to) {
|
|
579
|
+
return `M ${this.pt(radius, from)}${this.arcTo(radius, from, to)}`;
|
|
580
|
+
}
|
|
581
|
+
/**
|
|
582
|
+
* Shape endings are sized from the band itself (h = half its thickness, in
|
|
583
|
+
* viewBox units), so they look the same on every ring and at every size.
|
|
584
|
+
* Angles are in radians, as in calculateCommonArcParameters().
|
|
585
|
+
*/
|
|
586
|
+
ending(params, factor) {
|
|
458
587
|
const { bigRadius, smallRadius } = params;
|
|
459
|
-
const
|
|
460
|
-
const
|
|
461
|
-
const
|
|
462
|
-
const
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
const
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
const
|
|
471
|
-
const
|
|
472
|
-
const
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
d += `Q ${Q.xc},${Q.yc} ${upperPointEnd.x},${upperPointEnd.y} L ${innerPointEnd.x},${innerPointEnd.y}`;
|
|
478
|
-
d += `Q ${Q1.xc},${Q1.yc} ${newInnerEnd.x},${newInnerEnd.y}`;
|
|
479
|
-
d += `A ${smallRadius},${smallRadius} 0 ${flagI} 0 ${newInnerStart.x},${newInnerStart.y}`;
|
|
480
|
-
d += `Q ${Q2.xc},${Q2.yc} ${innerPointStart.x},${innerPointStart.y} L ${upperPointStart.x},${upperPointStart.y}`;
|
|
481
|
-
d += ` Q ${Q3.xc},${Q3.yc} ${newUpperStart.x},${newUpperStart.y}`;
|
|
482
|
-
d += ` Z`;
|
|
588
|
+
const h = (bigRadius - smallRadius) / 2;
|
|
589
|
+
const middle = (bigRadius + smallRadius) / 2;
|
|
590
|
+
const span = Math.min(params.upperAngleEnd - params.upperAngleStart, params.innerAngleEnd - params.innerAngleStart);
|
|
591
|
+
const length = Math.max(0, Math.min(h * factor, span * smallRadius * 0.49));
|
|
592
|
+
return { h, middle, length, angle: length / middle };
|
|
593
|
+
}
|
|
594
|
+
generateDefaultPath(params) {
|
|
595
|
+
const { bigRadius: R, smallRadius: r, upperAngleStart: uS, upperAngleEnd: uE, innerAngleStart: iS, innerAngleEnd: iE } = params;
|
|
596
|
+
return `M ${this.pt(R, uS)}${this.arcTo(R, uS, uE)} L ${this.pt(r, iE)}${this.arcTo(r, iE, iS)} Z`;
|
|
597
|
+
}
|
|
598
|
+
generateRoundedPath(params) {
|
|
599
|
+
const { bigRadius: R, smallRadius: r, upperAngleStart: uS, upperAngleEnd: uE, innerAngleStart: iS, innerAngleEnd: iE } = params;
|
|
600
|
+
const { length: c } = this.ending(params, 0.5);
|
|
601
|
+
const corner = (radius, angle) => ` A ${c},${c} 0 0 1 ${this.pt(radius, angle)}`;
|
|
602
|
+
let d = `M ${this.pt(R, uS + c / R)}${this.arcTo(R, uS + c / R, uE - c / R)}`;
|
|
603
|
+
d += `${corner(R - c, uE)} L ${this.pt(r + c, iE)}${corner(r, iE - c / r)}`;
|
|
604
|
+
d += this.arcTo(r, iE - c / r, iS + c / r);
|
|
605
|
+
d += `${corner(r + c, iS)} L ${this.pt(R - c, uS)}${corner(R, uS + c / R)} Z`;
|
|
483
606
|
return d;
|
|
484
607
|
}
|
|
485
|
-
// Dentro de la clase OrbitCommon en orbit-common.js
|
|
486
608
|
generateCirclePath(params, shape) {
|
|
487
|
-
const {
|
|
488
|
-
|
|
489
|
-
d
|
|
490
|
-
d +=
|
|
491
|
-
d += ` A 1,1 0 0 ${shape === "circle" || shape === "circle-a" ? 1 : 0} ${upperArcStart.x},${upperArcStart.y} `;
|
|
492
|
-
d += ` Z`;
|
|
609
|
+
const { bigRadius: R, smallRadius: r, upperAngleStart: uS, upperAngleEnd: uE, innerAngleStart: iS, innerAngleEnd: iE } = params;
|
|
610
|
+
const startSweep = shape === "bullet" ? 0 : 1;
|
|
611
|
+
let d = `M ${this.pt(R, uS)}${this.arcTo(R, uS, uE)} A 1,1 0 0 1 ${this.pt(r, iE)}`;
|
|
612
|
+
d += `${this.arcTo(r, iE, iS)} A 1,1 0 0 ${startSweep} ${this.pt(R, uS)} Z`;
|
|
493
613
|
return d;
|
|
494
614
|
}
|
|
495
|
-
generateCircleBPath(params
|
|
496
|
-
const {
|
|
497
|
-
const
|
|
498
|
-
const
|
|
499
|
-
const
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
const newUpperStart = this.arcPoint(bigRadius, upperAngleStart, 0, segment / orbitNumber2);
|
|
503
|
-
const newUpperEnd = this.arcPoint(bigRadius, upperAngleEnd, 0, -segment / orbitNumber2);
|
|
504
|
-
const newInnerStart = this.arcPoint(smallRadius, innerAngleStart, 0, segment / orbitNumber2);
|
|
505
|
-
const newInnerEnd = this.arcPoint(smallRadius, innerAngleEnd, 0, -segment / orbitNumber2);
|
|
506
|
-
let d = `M ${newUpperStart.x},${newUpperStart.y} A ${bigRadius},${bigRadius} 0 ${flagU} 1 ${newUpperEnd.x},${newUpperEnd.y}`;
|
|
507
|
-
d += ` A 1,1 0 0 1 ${newInnerEnd.x},${newInnerEnd.y} `;
|
|
508
|
-
d += ` A ${smallRadius},${smallRadius} 0 ${flagI} 0 ${newInnerStart.x},${newInnerStart.y}`;
|
|
509
|
-
d += ` A 1,1 0 0 1 ${newUpperStart.x},${newUpperStart.y} `;
|
|
510
|
-
d += ` Z`;
|
|
615
|
+
generateCircleBPath(params) {
|
|
616
|
+
const { bigRadius: R, smallRadius: r } = params;
|
|
617
|
+
const { h, angle } = this.ending(params, 1);
|
|
618
|
+
const a0 = params.upperAngleStart + angle;
|
|
619
|
+
const a1 = params.upperAngleEnd - angle;
|
|
620
|
+
let d = `M ${this.pt(R, a0)}${this.arcTo(R, a0, a1)} A ${h},${h} 0 0 1 ${this.pt(r, a1)}`;
|
|
621
|
+
d += `${this.arcTo(r, a1, a0)} A ${h},${h} 0 0 1 ${this.pt(R, a0)} Z`;
|
|
511
622
|
return d;
|
|
512
623
|
}
|
|
513
|
-
generateArrowPath(params
|
|
514
|
-
const {
|
|
515
|
-
const
|
|
516
|
-
const
|
|
517
|
-
|
|
518
|
-
d
|
|
519
|
-
d += `L ${
|
|
520
|
-
d +=
|
|
521
|
-
d += `L ${middleStart.x} ${middleStart.y}`;
|
|
522
|
-
d += `Z`;
|
|
624
|
+
generateArrowPath(params) {
|
|
625
|
+
const { bigRadius: R, smallRadius: r } = params;
|
|
626
|
+
const { middle, angle } = this.ending(params, 1.6);
|
|
627
|
+
const a0 = params.upperAngleStart;
|
|
628
|
+
const a1 = params.upperAngleEnd;
|
|
629
|
+
let d = `M ${this.pt(R, a0)}${this.arcTo(R, a0, a1 - angle)}`;
|
|
630
|
+
d += ` L ${this.pt(middle, a1)} L ${this.pt(r, a1 - angle)}`;
|
|
631
|
+
d += `${this.arcTo(r, a1 - angle, a0)} L ${this.pt(middle, a0 + angle)} Z`;
|
|
523
632
|
return d;
|
|
524
633
|
}
|
|
525
|
-
generateSlashPath(params, shape
|
|
526
|
-
const {
|
|
527
|
-
const
|
|
528
|
-
const
|
|
529
|
-
const
|
|
530
|
-
const
|
|
531
|
-
|
|
532
|
-
d += `L ${newInnerEnd.x} ${newInnerEnd.y}`;
|
|
533
|
-
d += `A ${smallRadius},${smallRadius} 0 ${largeArcFlagInner} 0 ${newInnerStart.x}, ${newInnerStart.y}`;
|
|
534
|
-
d += `Z`;
|
|
535
|
-
return d;
|
|
634
|
+
generateSlashPath(params, shape) {
|
|
635
|
+
const { bigRadius: R, smallRadius: r } = params;
|
|
636
|
+
const { angle } = this.ending(params, 1);
|
|
637
|
+
const a0 = params.upperAngleStart;
|
|
638
|
+
const a1 = params.upperAngleEnd;
|
|
639
|
+
const [outer0, outer1, inner0, inner1] = shape === "backslash" ? [a0, a1 - angle, a0 + angle, a1] : [a0 + angle, a1, a0, a1 - angle];
|
|
640
|
+
return `M ${this.pt(R, outer0)}${this.arcTo(R, outer0, outer1)} L ${this.pt(r, inner1)}${this.arcTo(r, inner1, inner0)} Z`;
|
|
536
641
|
}
|
|
537
|
-
generateZigzagPath(params
|
|
538
|
-
const {
|
|
539
|
-
const
|
|
540
|
-
const
|
|
541
|
-
const
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
const e4 = this.arcPoint(radius, params.innerAngleEnd, -h2, 3);
|
|
546
|
-
let d = `M ${upperArcStart.x},${upperArcStart.y} A ${bigRadius},${bigRadius} 0 ${largeArcFlagUpper} 1 ${upperArcEnd.x},${upperArcEnd.y}`;
|
|
547
|
-
d += `L ${e2.x} ${e2.y}`;
|
|
548
|
-
d += `L ${e3.x} ${e3.y}`;
|
|
549
|
-
d += `L ${e4.x} ${e4.y}`;
|
|
550
|
-
d += `L ${innerArcEnd.x} ${innerArcEnd.y}`;
|
|
551
|
-
d += `A ${smallRadius},${smallRadius} 0 ${largeArcFlagInner} 0 ${innerArcStart.x}, ${innerArcStart.y}`;
|
|
552
|
-
d += `L ${s2.x} ${s2.y}`;
|
|
553
|
-
d += `L ${s3.x} ${s3.y}`;
|
|
554
|
-
d += `L ${s4.x} ${s4.y}`;
|
|
555
|
-
d += `Z`;
|
|
642
|
+
generateZigzagPath(params) {
|
|
643
|
+
const { bigRadius: R, smallRadius: r } = params;
|
|
644
|
+
const { middle, angle } = this.ending(params, 1.6);
|
|
645
|
+
const a0 = params.upperAngleStart;
|
|
646
|
+
const a1 = params.upperAngleEnd;
|
|
647
|
+
let d = `M ${this.pt(R, a0 + angle)}${this.arcTo(R, a0 + angle, a1)}`;
|
|
648
|
+
d += ` L ${this.pt(middle, a1 - angle)} L ${this.pt(r, a1)}`;
|
|
649
|
+
d += `${this.arcTo(r, a1, a0 + angle)} L ${this.pt(middle, a0)} Z`;
|
|
556
650
|
return d;
|
|
557
651
|
}
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
652
|
+
/**
|
|
653
|
+
* The touch area of an interactive arc or progress: an invisible stroke
|
|
654
|
+
* along the middle of the band, from `from` to `to` degrees (0 = start of
|
|
655
|
+
* the element). It is as wide as the band plus `hit-width` px on each side,
|
|
656
|
+
* and catches the pointer whatever the fill, so transparent bands work.
|
|
657
|
+
*/
|
|
658
|
+
updateHitArea(hit, attrs, from, to) {
|
|
659
|
+
const extra = Math.max(0, Number(this.getAttribute("hit-width")) || 0);
|
|
660
|
+
this.fitCanvas(attrs, this.hasAttribute("interactive") ? extra : 0);
|
|
661
|
+
if (!this.hasAttribute("interactive") || !(to > from) || !(attrs.orbitRadius > 0)) {
|
|
662
|
+
hit.removeAttribute("d");
|
|
663
|
+
return;
|
|
664
|
+
}
|
|
665
|
+
const radians = (degrees) => (degrees - 90) * Math.PI / 180;
|
|
666
|
+
hit.setAttribute("d", this.linePath(attrs.realRadius, radians(from), radians(Math.min(to, from + 360))));
|
|
667
|
+
hit.style.strokeWidth = `${attrs.arcHeight + attrs.strokeWidth + 2 * extra}px`;
|
|
668
|
+
}
|
|
669
|
+
/**
|
|
670
|
+
* Grow the SVG canvas past the ring's box so it covers the whole band, its
|
|
671
|
+
* stroke and touch area. Some engines (WebKit) neither paint reliably nor
|
|
672
|
+
* hit-test SVG content that overflows its box.
|
|
673
|
+
*/
|
|
674
|
+
fitCanvas(attrs, extra) {
|
|
675
|
+
const svg = this.shadowRoot.querySelector("svg");
|
|
676
|
+
const scale = attrs.orbitRadius / 50;
|
|
677
|
+
if (!svg || !(scale > 0)) return;
|
|
678
|
+
const outer = (attrs.realRadius + attrs.arcHeightPercentage) * scale + attrs.strokeWidth + extra;
|
|
679
|
+
const pad = Math.max(0, Math.ceil(outer - attrs.orbitRadius));
|
|
680
|
+
const units = pad / scale;
|
|
681
|
+
svg.setAttribute("viewBox", `${-units} ${-units} ${100 + 2 * units} ${100 + 2 * units}`);
|
|
682
|
+
svg.style.inset = `${-pad}px`;
|
|
565
683
|
}
|
|
566
684
|
};
|
|
567
685
|
|
|
@@ -580,17 +698,11 @@
|
|
|
580
698
|
--o-back-stroke: none;
|
|
581
699
|
--o-back-stroke-width: 1;
|
|
582
700
|
}
|
|
583
|
-
:host(:hover) {
|
|
584
|
-
--o-fill: var(--o-gray-light);
|
|
585
|
-
--o-stroke: var(--o-fill);
|
|
586
|
-
--o-stroke-width: 1;
|
|
587
|
-
--o-back-fill: transparent;
|
|
588
|
-
--o-back-stroke: none;
|
|
589
|
-
--o-back-stroke-width: 1;
|
|
590
|
-
}
|
|
591
701
|
svg {
|
|
592
|
-
|
|
593
|
-
|
|
702
|
+
position: absolute;
|
|
703
|
+
inset: 0;
|
|
704
|
+
width: auto;
|
|
705
|
+
height: auto;
|
|
594
706
|
overflow: visible;
|
|
595
707
|
pointer-events: none;
|
|
596
708
|
}
|
|
@@ -602,6 +714,14 @@
|
|
|
602
714
|
:host([interactive]) svg * {
|
|
603
715
|
pointer-events: visiblePainted;
|
|
604
716
|
}
|
|
717
|
+
/* The touch area covers the whole track, whatever its colors. */
|
|
718
|
+
.hit {
|
|
719
|
+
fill: none;
|
|
720
|
+
stroke: transparent;
|
|
721
|
+
}
|
|
722
|
+
:host([interactive]) .hit {
|
|
723
|
+
pointer-events: stroke;
|
|
724
|
+
}
|
|
605
725
|
:host([interactive]) {
|
|
606
726
|
cursor: pointer;
|
|
607
727
|
}
|
|
@@ -617,11 +737,17 @@
|
|
|
617
737
|
stroke: var(--o-back-stroke);
|
|
618
738
|
stroke-width: var(--o-back-stroke-width);
|
|
619
739
|
}
|
|
620
|
-
/* variant="stroke": the thin, legible gauge
|
|
740
|
+
/* variant="stroke": the thin, legible gauge.
|
|
621
741
|
The default band is a FILLED donut wedge, so everyone building a
|
|
622
742
|
thin gauge tripped on --o-fill/--o-back-fill. With this variant the
|
|
623
743
|
paths are open arcs and the data color lives where you expect it:
|
|
624
744
|
--o-stroke for the bar, --o-back-stroke for the track. */
|
|
745
|
+
/* The stroke variant draws a 2px bar over a gray 1px track unless you
|
|
746
|
+
set your own values on the element. */
|
|
747
|
+
:host([variant="stroke"]) {
|
|
748
|
+
--o-stroke-width: 2;
|
|
749
|
+
--o-back-stroke: var(--o-gray-light);
|
|
750
|
+
}
|
|
625
751
|
:host([variant="stroke"]) .progress-bar {
|
|
626
752
|
fill: none;
|
|
627
753
|
stroke: var(--o-stroke);
|
|
@@ -636,6 +762,7 @@
|
|
|
636
762
|
}
|
|
637
763
|
</style>
|
|
638
764
|
<svg viewBox="0 0 100 100">
|
|
765
|
+
<path class="hit" vector-effect="non-scaling-stroke"></path>
|
|
639
766
|
<path class="progress-bg" shape-rendering="geometricPrecision" vector-effect="non-scaling-stroke"></path>
|
|
640
767
|
<path class="progress-bar" shape-rendering="geometricPrecision" vector-effect="non-scaling-stroke"></path>
|
|
641
768
|
</svg>
|
|
@@ -648,6 +775,7 @@
|
|
|
648
775
|
const dBar = isStroke ? this.calculateStrokeArc(attrs, false) : this.calculateArcParameters(attrs, false);
|
|
649
776
|
this.shadowRoot.querySelector(".progress-bg").setAttribute("d", dBg);
|
|
650
777
|
this.shadowRoot.querySelector(".progress-bar").setAttribute("d", dBar);
|
|
778
|
+
this.updateHitArea(this.shadowRoot.querySelector(".hit"), attrs, 0, attrs.range);
|
|
651
779
|
}
|
|
652
780
|
/**
|
|
653
781
|
* variant="stroke": open arc along the orbit radius (no closed band),
|
|
@@ -655,24 +783,17 @@
|
|
|
655
783
|
*/
|
|
656
784
|
calculateStrokeArc(attrs, full) {
|
|
657
785
|
const { realRadius } = attrs;
|
|
658
|
-
const arcAngle = Math.max(0, Math.min(this.getProgressAngle(attrs, full),
|
|
786
|
+
const arcAngle = Math.max(0, Math.min(this.getProgressAngle(attrs, full), 360));
|
|
659
787
|
if (!(arcAngle > 0) || !(attrs.orbitRadius > 0)) return "";
|
|
660
|
-
const
|
|
661
|
-
|
|
662
|
-
const x0 = 50 + realRadius * Math.cos(a0);
|
|
663
|
-
const y0 = 50 + realRadius * Math.sin(a0);
|
|
664
|
-
const x1 = 50 + realRadius * Math.cos(a1);
|
|
665
|
-
const y1 = 50 + realRadius * Math.sin(a1);
|
|
666
|
-
const largeArcFlag = arcAngle > 180 ? 1 : 0;
|
|
667
|
-
return `M ${x0},${y0} A ${realRadius},${realRadius} 0 ${largeArcFlag} 1 ${x1},${y1}`;
|
|
788
|
+
const start = -Math.PI / 2;
|
|
789
|
+
return this.linePath(realRadius, start, start + arcAngle * Math.PI / 180);
|
|
668
790
|
}
|
|
669
791
|
getAttributes() {
|
|
670
792
|
const common = super.getCommonAttributes(this);
|
|
671
793
|
const range = Math.max(0, Math.min(360, this.readAngle(common.style, "--o-range", 360)));
|
|
672
794
|
const rawValue = common.style.getPropertyValue("--o-progress").trim();
|
|
673
|
-
const progress = rawValue ? this.readNumber(common.style, "--o-progress", 0) :
|
|
674
|
-
const
|
|
675
|
-
const maxValue = rawMax === null ? 100 : Number(rawMax);
|
|
795
|
+
const progress = rawValue ? this.readNumber(common.style, "--o-progress", 0) : this.readValue();
|
|
796
|
+
const maxValue = this.readMax();
|
|
676
797
|
return {
|
|
677
798
|
...common,
|
|
678
799
|
range,
|
|
@@ -697,7 +818,7 @@
|
|
|
697
818
|
strokeWidth,
|
|
698
819
|
arcHeight
|
|
699
820
|
);
|
|
700
|
-
return super.generatePathData(shape, params
|
|
821
|
+
return super.generatePathData(shape, params);
|
|
701
822
|
}
|
|
702
823
|
};
|
|
703
824
|
|
|
@@ -711,15 +832,11 @@
|
|
|
711
832
|
--o-stroke-width: 1;
|
|
712
833
|
--o-color: currentcolor;
|
|
713
834
|
}
|
|
714
|
-
:host(:hover) {
|
|
715
|
-
--o-fill: var(--o-gray-light);
|
|
716
|
-
--o-stroke: var(--o-fill);
|
|
717
|
-
--o-stroke-width: 1;
|
|
718
|
-
--o-color: currentcolor;
|
|
719
|
-
}
|
|
720
835
|
svg {
|
|
721
|
-
|
|
722
|
-
|
|
836
|
+
position: absolute;
|
|
837
|
+
inset: 0;
|
|
838
|
+
width: auto;
|
|
839
|
+
height: auto;
|
|
723
840
|
overflow: visible;
|
|
724
841
|
pointer-events: none;
|
|
725
842
|
}
|
|
@@ -733,6 +850,14 @@
|
|
|
733
850
|
:host([interactive]) svg * {
|
|
734
851
|
pointer-events: visiblePainted;
|
|
735
852
|
}
|
|
853
|
+
/* The touch area catches the pointer even where the band is transparent. */
|
|
854
|
+
#orbitHit {
|
|
855
|
+
fill: none;
|
|
856
|
+
stroke: transparent;
|
|
857
|
+
}
|
|
858
|
+
:host([interactive]) #orbitHit {
|
|
859
|
+
pointer-events: stroke;
|
|
860
|
+
}
|
|
736
861
|
:host([interactive]) {
|
|
737
862
|
cursor: pointer;
|
|
738
863
|
}
|
|
@@ -745,18 +870,21 @@
|
|
|
745
870
|
text {
|
|
746
871
|
fill: var(--o-color);
|
|
747
872
|
}
|
|
748
|
-
|
|
749
|
-
|
|
873
|
+
/* Guides for text: never painted and never under the pointer. */
|
|
874
|
+
#orbitPath, #measure {
|
|
875
|
+
fill: none;
|
|
750
876
|
stroke: none;
|
|
751
|
-
|
|
877
|
+
pointer-events: none;
|
|
752
878
|
}
|
|
753
879
|
</style>
|
|
754
880
|
<svg viewBox="0 0 100 100">
|
|
881
|
+
<path id="orbitHit" vector-effect="non-scaling-stroke"></path>
|
|
755
882
|
<path id="orbitShape" shape-rendering="geometricPrecision" vector-effect="non-scaling-stroke"></path>
|
|
756
883
|
<path id="orbitPath" shape-rendering="geometricPrecision" vector-effect="non-scaling-stroke"></path>
|
|
757
884
|
<text>
|
|
758
885
|
<textPath href="#orbitPath" alignment-baseline="middle"></textPath>
|
|
759
886
|
</text>
|
|
887
|
+
<text id="measure" visibility="hidden" aria-hidden="true"></text>
|
|
760
888
|
</svg>
|
|
761
889
|
`;
|
|
762
890
|
var OrbitArc = class extends OrbitBase {
|
|
@@ -767,9 +895,10 @@
|
|
|
767
895
|
}
|
|
768
896
|
update() {
|
|
769
897
|
const attrs = this.getAttributes();
|
|
770
|
-
const {
|
|
771
|
-
if (
|
|
772
|
-
|
|
898
|
+
const { fitRange, stacked } = attrs;
|
|
899
|
+
if (stacked) {
|
|
900
|
+
const anchor = attrs.auto && this.hasAttribute("text-anchor") ? { middle: -0.5, end: -1 }[attrs.textAnchor] || 0 : 0;
|
|
901
|
+
setLayoutProperty(this, "--o-arc-start", `${attrs.stackOffset + anchor * attrs.arcAngle}deg`);
|
|
773
902
|
setLayoutProperty(this, "--o_stack", attrs.stackOffset + attrs.arcAngle);
|
|
774
903
|
} else {
|
|
775
904
|
this.style.removeProperty("--o-arc-start");
|
|
@@ -780,6 +909,8 @@
|
|
|
780
909
|
const textPath = this.shadowRoot.querySelector("textPath");
|
|
781
910
|
orbitShape.setAttribute("d", this.calculateArcParameters(attrs).dShape);
|
|
782
911
|
orbitPath.setAttribute("d", this.calculateTextArcParameters(attrs).dPath);
|
|
912
|
+
this.updateHitArea(this.shadowRoot.querySelector("#orbitHit"), attrs, 0, attrs.arcAngle);
|
|
913
|
+
const textAnchor = attrs.textAnchor;
|
|
783
914
|
if (textAnchor === "start") {
|
|
784
915
|
textPath.setAttribute("startOffset", "0%");
|
|
785
916
|
textPath.setAttribute("text-anchor", "start");
|
|
@@ -795,9 +926,9 @@
|
|
|
795
926
|
} else {
|
|
796
927
|
textPath.parentElement.removeAttribute("textLength");
|
|
797
928
|
}
|
|
798
|
-
textPath.parentElement.style.fontSize =
|
|
929
|
+
textPath.parentElement.style.fontSize = attrs.svgFontSize;
|
|
799
930
|
textPath.textContent = this.textContent;
|
|
800
|
-
this.warnIfTextOverflows(orbitPath, textPath, fitRange);
|
|
931
|
+
if (!attrs.auto) this.warnIfTextOverflows(orbitPath, textPath, fitRange);
|
|
801
932
|
}
|
|
802
933
|
/**
|
|
803
934
|
* Curved text has an explicit angular budget: glyphs past the end of the
|
|
@@ -818,7 +949,7 @@
|
|
|
818
949
|
if (pathLen > 0 && textLen > pathLen && this._truncWarned !== raw) {
|
|
819
950
|
this._truncWarned = raw;
|
|
820
951
|
console.warn(
|
|
821
|
-
`[orbit] <o-arc> text "${raw.length > 34 ? raw.slice(0, 34) + "\u2026" : raw}" overflows its arc (${Math.round(textLen)} > ${Math.round(pathLen)} units) and will clip. Shorten the text, widen --o-range,
|
|
952
|
+
`[orbit] <o-arc> text "${raw.length > 34 ? raw.slice(0, 34) + "\u2026" : raw}" overflows its arc (${Math.round(textLen)} > ${Math.round(pathLen)} units) and will clip. Shorten the text, widen --o-range, add range="auto" to fit the arc to the text, or fit-range to squeeze it.`
|
|
822
953
|
);
|
|
823
954
|
}
|
|
824
955
|
} catch (e) {
|
|
@@ -835,23 +966,43 @@
|
|
|
835
966
|
const textAnchor = this.getAttribute("text-anchor") || "middle";
|
|
836
967
|
const fontSize = style.fontSize || "16px";
|
|
837
968
|
const gap = Math.max(0, this.readNumber(style, "--o-gap", 1));
|
|
838
|
-
const
|
|
839
|
-
const
|
|
840
|
-
const
|
|
841
|
-
|
|
969
|
+
const svgFontSize = `calc(${fontSize} * (100 / (${length}) * (12 / var(--o-orbit-number)))`;
|
|
970
|
+
const auto = this.getAttribute("range") === "auto";
|
|
971
|
+
const stacked = auto || this.hasAttribute("value");
|
|
972
|
+
let arcAngle;
|
|
973
|
+
if (auto) arcAngle = this.measureTextAngle(svgFontSize, common.realRadius, gap);
|
|
974
|
+
else if (this.hasAttribute("value")) arcAngle = super.getProgressAngle(range, this.readValue(), this.readMax());
|
|
975
|
+
else arcAngle = Math.max(0, Math.min(360, this.readAngle(style, "--o-angle", 0)));
|
|
976
|
+
this._autoAngle = auto ? arcAngle : 0;
|
|
842
977
|
let stackOffset = 0;
|
|
843
|
-
if (
|
|
978
|
+
if (stacked) {
|
|
844
979
|
for (let prev = this.previousElementSibling; prev; prev = prev.previousElementSibling) {
|
|
845
980
|
if (prev.localName !== "o-arc") continue;
|
|
846
981
|
const previousStyle = this.ownerDocument.defaultView.getComputedStyle(prev);
|
|
847
|
-
if (prev.
|
|
982
|
+
if (prev.getAttribute("range") === "auto") stackOffset += prev._autoAngle || 0;
|
|
983
|
+
else if (prev.hasAttribute("value")) {
|
|
848
984
|
const prevMax = prev.hasAttribute("max") ? Number(prev.getAttribute("max")) : 100;
|
|
849
985
|
const prevRange = cssNumber(prev, previousStyle.getPropertyValue("--o-range"), range, "angle");
|
|
850
986
|
stackOffset += super.getProgressAngle(prevRange, Number(prev.getAttribute("value")), prevMax);
|
|
851
987
|
} else stackOffset += cssNumber(prev, previousStyle.getPropertyValue("--o-angle"), 0, "angle");
|
|
852
988
|
}
|
|
853
989
|
}
|
|
854
|
-
return { ...common, gap, arcAngle, stackOffset, flip, fitRange, length, fontSize, textAnchor };
|
|
990
|
+
return { ...common, gap, arcAngle, stackOffset, flip, fitRange, length, fontSize, svgFontSize, textAnchor, auto, stacked };
|
|
991
|
+
}
|
|
992
|
+
/** range="auto": the angle that fits this arc's text, with half an em of room at each end. */
|
|
993
|
+
measureTextAngle(svgFontSize, radius, gap) {
|
|
994
|
+
const text = (this.textContent || "").trim();
|
|
995
|
+
if (!text || !(radius > 0)) return 0;
|
|
996
|
+
const measure = this.shadowRoot.querySelector("#measure");
|
|
997
|
+
measure.style.fontSize = svgFontSize;
|
|
998
|
+
measure.textContent = text;
|
|
999
|
+
let length = 0;
|
|
1000
|
+
try {
|
|
1001
|
+
length = measure.getComputedTextLength() + parseFloat(this.ownerDocument.defaultView.getComputedStyle(measure).fontSize);
|
|
1002
|
+
} catch (e) {
|
|
1003
|
+
}
|
|
1004
|
+
if (!(length > 0)) return 0;
|
|
1005
|
+
return Math.min(360, length / radius * 180 / Math.PI + gap);
|
|
855
1006
|
}
|
|
856
1007
|
calculateArcParameters(attrs) {
|
|
857
1008
|
const { arcAngle, realRadius, arcHeightPercentage, orbitNumber: orbitNumber2, shape, strokeWidth, arcHeight, gap } = attrs;
|
|
@@ -866,32 +1017,38 @@
|
|
|
866
1017
|
arcHeight,
|
|
867
1018
|
gap
|
|
868
1019
|
);
|
|
869
|
-
const dShape = super.generatePathData(shape, params
|
|
1020
|
+
const dShape = super.generatePathData(shape, params);
|
|
870
1021
|
return { dShape };
|
|
871
1022
|
}
|
|
872
1023
|
calculateTextArcParameters(attrs) {
|
|
873
1024
|
const { arcAngle, realRadius, gap, flip } = attrs;
|
|
874
1025
|
if (!(arcAngle > 0)) return { dPath: "" };
|
|
875
1026
|
const adjustedGap = Math.min(gap * 0.5, arcAngle * 0.49);
|
|
876
|
-
const
|
|
877
|
-
const
|
|
878
|
-
|
|
879
|
-
let coordY1 = 50 + realRadius * Math.sin((-90 + adjustedGap) * (Math.PI / 180));
|
|
880
|
-
let coordX2 = 50 + realRadius * Math.cos((arcAngle - 90 - adjustedGap) * Math.PI / 180);
|
|
881
|
-
let coordY2 = 50 + realRadius * Math.sin((arcAngle - 90 - adjustedGap) * Math.PI / 180);
|
|
882
|
-
const [startX, startY, endX, endY] = flip ? [coordX2, coordY2, coordX1, coordY1] : [coordX1, coordY1, coordX2, coordY2];
|
|
883
|
-
const dPath = `M ${startX},${startY} A ${realRadius},${realRadius} 0 ${largeArcFlag} ${sweepFlag} ${endX},${endY}`;
|
|
884
|
-
return { dPath };
|
|
1027
|
+
const start = (adjustedGap - 90) * Math.PI / 180;
|
|
1028
|
+
const end = (arcAngle - adjustedGap - 90) * Math.PI / 180;
|
|
1029
|
+
return { dPath: flip ? this.linePath(realRadius, end, start) : this.linePath(realRadius, start, end) };
|
|
885
1030
|
}
|
|
886
|
-
|
|
1031
|
+
resolveAngle(cssExpression) {
|
|
887
1032
|
return cssNumber(this, cssExpression, 0, "angle");
|
|
888
1033
|
}
|
|
1034
|
+
/** @deprecated Use resolveAngle(). */
|
|
1035
|
+
calcularExpresionCSS(cssExpression) {
|
|
1036
|
+
return this.resolveAngle(cssExpression);
|
|
1037
|
+
}
|
|
889
1038
|
};
|
|
890
1039
|
|
|
891
1040
|
// src/js/orbit-resize.js
|
|
892
1041
|
var resizing = /* @__PURE__ */ new WeakMap();
|
|
1042
|
+
var FIXED = /^[+-]?(?:\d+\.?\d*|\.\d+)px$/;
|
|
893
1043
|
var Orbit = {
|
|
894
1044
|
refresh: refreshLayout,
|
|
1045
|
+
/** A promise that resolves when pending layouts are drawn. */
|
|
1046
|
+
ready: layoutReady,
|
|
1047
|
+
/**
|
|
1048
|
+
* Scale systems that use a fixed px --o-force with their container's width
|
|
1049
|
+
* (500px wide = ratio 1). Systems on the default --o-force already follow
|
|
1050
|
+
* their .bigbang, so they are left alone.
|
|
1051
|
+
*/
|
|
895
1052
|
resize(parentElementSelector) {
|
|
896
1053
|
var _a, _b;
|
|
897
1054
|
const parent = typeof parentElementSelector === "string" ? (_a = globalThis.document) == null ? void 0 : _a.querySelector(parentElementSelector) : parentElementSelector;
|
|
@@ -904,10 +1061,16 @@
|
|
|
904
1061
|
const view = parent.ownerDocument.defaultView;
|
|
905
1062
|
const applyRatio = (width) => {
|
|
906
1063
|
if (!(width > 0)) return;
|
|
1064
|
+
let fixed = 0;
|
|
907
1065
|
for (const element of parent.querySelectorAll(".gravity-spot")) {
|
|
1066
|
+
if (!FIXED.test(view.getComputedStyle(element).getPropertyValue("--o-force").trim())) continue;
|
|
1067
|
+
fixed++;
|
|
908
1068
|
const ratio = String(width / 500);
|
|
909
1069
|
if (element.style.getPropertyValue("--o-force-ratio") !== ratio) element.style.setProperty("--o-force-ratio", ratio);
|
|
910
1070
|
}
|
|
1071
|
+
if (!fixed) {
|
|
1072
|
+
warnOnce(parent, "resize", "Orbit.resize() has nothing to do here: every .bigbang already scales with its container. It only scales systems that set a fixed px --o-force.");
|
|
1073
|
+
}
|
|
911
1074
|
refreshLayout(parent);
|
|
912
1075
|
};
|
|
913
1076
|
const observer = new view.ResizeObserver((entries) => {
|