@zumer/orbit 1.4.11 → 1.5.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 +57 -7
- package/dist/orbit.css +448 -600
- package/dist/orbit.js +535 -157
- package/dist/orbit.min.css +1 -1
- package/dist/orbit.min.js +39 -4
- package/dist/orbit.mjs +941 -0
- package/package.json +13 -4
package/dist/orbit.mjs
ADDED
|
@@ -0,0 +1,941 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* orbit
|
|
3
|
+
* v.1.5.0
|
|
4
|
+
* Author Juan Martin Muda - Zumerlab
|
|
5
|
+
* License MIT
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
// src/js/orbit-layout.js
|
|
9
|
+
var STATE = Symbol.for("zumer.orbit.layout.state.v1");
|
|
10
|
+
var REGISTRY = Symbol.for("zumer.orbit.layout.registry.v1");
|
|
11
|
+
var PROBE = Symbol.for("zumer.orbit.layout.probe.v1");
|
|
12
|
+
var VISUALS = "o-arc, o-progress";
|
|
13
|
+
var OBSERVE = { subtree: true, childList: true, characterData: true, attributes: true, attributeOldValue: true };
|
|
14
|
+
function orbitNumber(element) {
|
|
15
|
+
var _a;
|
|
16
|
+
for (const token of element.classList || []) {
|
|
17
|
+
if (/^orbit-\d+$/.test(token)) return Number(token.slice(6));
|
|
18
|
+
}
|
|
19
|
+
return ((_a = element.classList) == null ? void 0 : _a.contains("orbit")) ? null : void 0;
|
|
20
|
+
}
|
|
21
|
+
function setLayoutProperty(element, name, value) {
|
|
22
|
+
const text = String(value);
|
|
23
|
+
if (element.style.getPropertyValue(name) !== text) element.style.setProperty(name, text);
|
|
24
|
+
}
|
|
25
|
+
function cssNumber(element, value, fallback = 0, type = "number") {
|
|
26
|
+
const raw = String(value || "").trim();
|
|
27
|
+
if (!raw) return fallback;
|
|
28
|
+
if (type === "number" && /^[+-]?(?:\d+\.?\d*|\.\d+)$/.test(raw)) return Number(raw);
|
|
29
|
+
const angle = raw.match(/^([+-]?(?:\d+\.?\d*|\.\d+))(deg|rad|grad|turn)?$/);
|
|
30
|
+
if (type === "angle" && angle) {
|
|
31
|
+
return Number(angle[1]) * ({ deg: 1, rad: 180 / Math.PI, grad: 0.9, turn: 360 }[angle[2]] || 1);
|
|
32
|
+
}
|
|
33
|
+
const doc = element.ownerDocument;
|
|
34
|
+
if (!(doc == null ? void 0 : doc.documentElement)) return fallback;
|
|
35
|
+
let probe = doc[PROBE];
|
|
36
|
+
if (!probe) {
|
|
37
|
+
probe = doc.createElement("span");
|
|
38
|
+
probe.setAttribute("data-orbit-measure", "");
|
|
39
|
+
probe.setAttribute("aria-hidden", "true");
|
|
40
|
+
probe.style.cssText = "all:initial!important;position:fixed!important;visibility:hidden!important;pointer-events:none!important;width:0!important;height:0!important;overflow:hidden!important;";
|
|
41
|
+
doc.documentElement.appendChild(probe);
|
|
42
|
+
Object.defineProperty(doc, PROBE, { value: probe, configurable: true });
|
|
43
|
+
}
|
|
44
|
+
const property = type === "angle" ? "rotate" : "scale";
|
|
45
|
+
probe.style.removeProperty(property);
|
|
46
|
+
probe.style.setProperty(property, raw, "important");
|
|
47
|
+
if (!probe.style.getPropertyValue(property)) return fallback;
|
|
48
|
+
const computed = doc.defaultView.getComputedStyle(probe).getPropertyValue(property);
|
|
49
|
+
const resolved = parseFloat(computed);
|
|
50
|
+
return Number.isFinite(resolved) ? resolved : fallback;
|
|
51
|
+
}
|
|
52
|
+
function group(element) {
|
|
53
|
+
if (element.localName === "o-arc") return "arc";
|
|
54
|
+
for (const name of ["satellite", "vector", "side"]) if (element.classList.contains(name)) return name;
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
function layoutRing(ring) {
|
|
58
|
+
const counts = { arc: 0, satellite: 0, vector: 0, side: 0 };
|
|
59
|
+
for (const child of ring.children) {
|
|
60
|
+
const kind = group(child);
|
|
61
|
+
if (kind) setLayoutProperty(child, "--o-layout-index", counts[kind]++ - (kind === "side" ? 1 : 0));
|
|
62
|
+
else if (child.localName === "o-progress") setLayoutProperty(child, "--o-layout-index", 0);
|
|
63
|
+
}
|
|
64
|
+
const count = Math.max(counts.arc, counts.satellite, counts.vector, 1);
|
|
65
|
+
const divisor = counts.side ? String(counts.side) : `max(1, ${count} - var(--o-fit-range, 0))`;
|
|
66
|
+
setLayoutProperty(ring, "--o-layout-angle", `calc(var(--o-range, 360deg) / ${divisor})`);
|
|
67
|
+
}
|
|
68
|
+
function isScope(root) {
|
|
69
|
+
return (root == null ? void 0 : root.nodeType) === 9 || (root == null ? void 0 : root.nodeType) === 11 && !!root.host;
|
|
70
|
+
}
|
|
71
|
+
function scopeFor(element) {
|
|
72
|
+
var _a;
|
|
73
|
+
if (isScope(element)) return element;
|
|
74
|
+
const root = (_a = element == null ? void 0 : element.getRootNode) == null ? void 0 : _a.call(element);
|
|
75
|
+
return isScope(root) ? root : null;
|
|
76
|
+
}
|
|
77
|
+
function composedContains(ancestor, node) {
|
|
78
|
+
var _a, _b;
|
|
79
|
+
for (let current = node; current; current = (_a = current.getRootNode) == null ? void 0 : _a.call(current).host) {
|
|
80
|
+
if (ancestor === current || ((_b = ancestor.contains) == null ? void 0 : _b.call(ancestor, current))) return true;
|
|
81
|
+
}
|
|
82
|
+
return false;
|
|
83
|
+
}
|
|
84
|
+
function registryFor(doc) {
|
|
85
|
+
if (!doc[REGISTRY]) Object.defineProperty(doc, REGISTRY, {
|
|
86
|
+
value: { states: /* @__PURE__ */ new Set() },
|
|
87
|
+
configurable: true
|
|
88
|
+
});
|
|
89
|
+
return doc[REGISTRY];
|
|
90
|
+
}
|
|
91
|
+
function dispose(state) {
|
|
92
|
+
if (state.disposed) return;
|
|
93
|
+
state.disposed = true;
|
|
94
|
+
state.view.cancelAnimationFrame(state.frame);
|
|
95
|
+
state.observer.disconnect();
|
|
96
|
+
state.resize.disconnect();
|
|
97
|
+
for (const remove of state.listeners) remove();
|
|
98
|
+
state.rings.clear();
|
|
99
|
+
state.visuals.clear();
|
|
100
|
+
state.dirty.clear();
|
|
101
|
+
state.registry.states.delete(state);
|
|
102
|
+
if (state.scope[STATE] === state) delete state.scope[STATE];
|
|
103
|
+
}
|
|
104
|
+
function pruneDisconnected(registry) {
|
|
105
|
+
for (const state of registry.states) {
|
|
106
|
+
if (state.scope.host && !state.scope.host.isConnected) dispose(state);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
function invalidateDescendants(state, target = state.scope) {
|
|
110
|
+
for (const other of state.registry.states) {
|
|
111
|
+
if (other !== state && other.scope.host && composedContains(target, other.scope.host)) {
|
|
112
|
+
other.all = true;
|
|
113
|
+
other.queue();
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
function hasLayoutStructure(node) {
|
|
118
|
+
if (node.nodeType !== 1 || node.hasAttribute("data-orbit-measure")) return false;
|
|
119
|
+
if (orbitNumber(node) !== void 0 || node.matches(".gravity-spot, o-arc, o-progress")) return true;
|
|
120
|
+
return !!node.querySelector(".gravity-spot, .orbit, [data-orbit-ring], o-arc, o-progress");
|
|
121
|
+
}
|
|
122
|
+
function hasStylesheet(node) {
|
|
123
|
+
return node.nodeType === 1 && (node.matches('style, link[rel~="stylesheet"]') || !!node.querySelector('style, link[rel~="stylesheet"]'));
|
|
124
|
+
}
|
|
125
|
+
function needsDiscovery(record, state) {
|
|
126
|
+
if (record.type === "childList") {
|
|
127
|
+
return [...record.addedNodes, ...record.removedNodes].some(hasLayoutStructure);
|
|
128
|
+
}
|
|
129
|
+
if (record.attributeName !== "class") return false;
|
|
130
|
+
const element = record.target;
|
|
131
|
+
return state.rings.has(element) || element.classList.contains("gravity-spot") || orbitNumber(element) !== void 0 || (record.oldValue || "").split(/\s+/).some((token) => token === "gravity-spot" || token === "orbit" || /^orbit-\d+$/.test(token));
|
|
132
|
+
}
|
|
133
|
+
function createState(scope) {
|
|
134
|
+
const doc = scope.nodeType === 9 ? scope : scope.ownerDocument;
|
|
135
|
+
const view = doc.defaultView;
|
|
136
|
+
const registry = registryFor(doc);
|
|
137
|
+
const state = {
|
|
138
|
+
scope,
|
|
139
|
+
doc,
|
|
140
|
+
view,
|
|
141
|
+
registry,
|
|
142
|
+
rings: /* @__PURE__ */ new Set(),
|
|
143
|
+
visuals: /* @__PURE__ */ new Set(),
|
|
144
|
+
dirty: /* @__PURE__ */ new Set(),
|
|
145
|
+
scan: true,
|
|
146
|
+
all: true,
|
|
147
|
+
frame: 0,
|
|
148
|
+
disposed: false,
|
|
149
|
+
listeners: []
|
|
150
|
+
};
|
|
151
|
+
const listen = (target, event, listener, options) => {
|
|
152
|
+
target == null ? void 0 : target.addEventListener(event, listener, options);
|
|
153
|
+
state.listeners.push(() => target == null ? void 0 : target.removeEventListener(event, listener, options));
|
|
154
|
+
};
|
|
155
|
+
const queue = () => {
|
|
156
|
+
if (!state.disposed && !state.frame) state.frame = view.requestAnimationFrame(() => {
|
|
157
|
+
state.frame = 0;
|
|
158
|
+
state.flush();
|
|
159
|
+
});
|
|
160
|
+
};
|
|
161
|
+
state.queue = queue;
|
|
162
|
+
state.flush = () => flush(state);
|
|
163
|
+
state.invalidate = (target = scope, includeDescendants = true) => {
|
|
164
|
+
var _a, _b;
|
|
165
|
+
if (includeDescendants && (target === scope || target === doc.documentElement || target === doc.head)) state.all = true;
|
|
166
|
+
else {
|
|
167
|
+
for (const ring of state.rings) if (ring === target || ring.contains(target) || includeDescendants && ((_a = target.contains) == null ? void 0 : _a.call(target, ring))) state.dirty.add(ring);
|
|
168
|
+
for (const visual of state.visuals) if (visual === target || visual.contains(target) || includeDescendants && ((_b = target.contains) == null ? void 0 : _b.call(target, visual))) state.dirty.add(visual);
|
|
169
|
+
}
|
|
170
|
+
if (state.all || state.dirty.size || state.scan) queue();
|
|
171
|
+
};
|
|
172
|
+
state.observer = new view.MutationObserver((records) => {
|
|
173
|
+
var _a, _b;
|
|
174
|
+
if (records.some((record) => record.type === "childList")) pruneDisconnected(registry);
|
|
175
|
+
if (state.disposed) return;
|
|
176
|
+
for (const record of records) {
|
|
177
|
+
const target = record.target.nodeType === 3 ? record.target.parentElement : record.target;
|
|
178
|
+
if (!target || ((_a = target.hasAttribute) == null ? void 0 : _a.call(target, "data-orbit-measure"))) continue;
|
|
179
|
+
const structural = needsDiscovery(record, state);
|
|
180
|
+
if (structural) state.scan = true;
|
|
181
|
+
const stylesheetChange = record.type === "childList" && [...record.addedNodes, ...record.removedNodes].some(hasStylesheet);
|
|
182
|
+
if (stylesheetChange || ((_b = target.closest) == null ? void 0 : _b.call(target, "head, style")) || target.localName === "link") {
|
|
183
|
+
state.all = true;
|
|
184
|
+
invalidateDescendants(state);
|
|
185
|
+
} else if (record.type === "attributes") invalidateDescendants(state, target);
|
|
186
|
+
state.invalidate(target, record.type === "attributes" || structural);
|
|
187
|
+
}
|
|
188
|
+
});
|
|
189
|
+
state.observer.observe(scope, OBSERVE);
|
|
190
|
+
state.resize = new view.ResizeObserver((entries) => {
|
|
191
|
+
for (const entry of entries) state.dirty.add(entry.target);
|
|
192
|
+
queue();
|
|
193
|
+
});
|
|
194
|
+
const invalidateAll = () => {
|
|
195
|
+
state.all = true;
|
|
196
|
+
invalidateDescendants(state);
|
|
197
|
+
queue();
|
|
198
|
+
};
|
|
199
|
+
if (scope === doc) {
|
|
200
|
+
listen(view, "resize", invalidateAll);
|
|
201
|
+
listen(doc.fonts, "loadingdone", invalidateAll);
|
|
202
|
+
}
|
|
203
|
+
listen(scope, "load", (event) => {
|
|
204
|
+
if (event.target.localName === "link") invalidateAll();
|
|
205
|
+
}, true);
|
|
206
|
+
for (const event of ["pointerover", "pointerout", "focusin", "focusout"]) {
|
|
207
|
+
listen(scope, event, (e) => {
|
|
208
|
+
state.invalidate(e.target);
|
|
209
|
+
invalidateDescendants(state, e.target);
|
|
210
|
+
}, true);
|
|
211
|
+
}
|
|
212
|
+
Object.defineProperty(scope, STATE, { value: state, configurable: true });
|
|
213
|
+
registry.states.add(state);
|
|
214
|
+
queue();
|
|
215
|
+
return state;
|
|
216
|
+
}
|
|
217
|
+
function ensureState(scope) {
|
|
218
|
+
const doc = scope.nodeType === 9 ? scope : scope.ownerDocument;
|
|
219
|
+
if (!(doc == null ? void 0 : doc.defaultView) || !doc.documentElement || scope.host && !scope.host.isConnected) return null;
|
|
220
|
+
if (scope[STATE] && scope[STATE].doc !== doc) dispose(scope[STATE]);
|
|
221
|
+
if (scope.host) {
|
|
222
|
+
const parent = scopeFor(scope.host);
|
|
223
|
+
if (parent) ensureState(parent);
|
|
224
|
+
}
|
|
225
|
+
return scope[STATE] || createState(scope);
|
|
226
|
+
}
|
|
227
|
+
function discover(state) {
|
|
228
|
+
const nextRings = /* @__PURE__ */ new Set();
|
|
229
|
+
const automaticCounts = /* @__PURE__ */ new Map();
|
|
230
|
+
for (const ring of state.scope.querySelectorAll(".gravity-spot > *")) {
|
|
231
|
+
const number = orbitNumber(ring);
|
|
232
|
+
if (number === void 0) continue;
|
|
233
|
+
let automatic = automaticCounts.get(ring.parentElement) || 0;
|
|
234
|
+
if (ring.classList.contains("orbit")) automaticCounts.set(ring.parentElement, ++automatic);
|
|
235
|
+
const resolved = number === null ? automatic : number;
|
|
236
|
+
ring.setAttribute("data-orbit-ring", "");
|
|
237
|
+
setLayoutProperty(ring, "--o-layout-number", resolved === 0 ? 1e-5 : resolved);
|
|
238
|
+
nextRings.add(ring);
|
|
239
|
+
state.dirty.add(ring);
|
|
240
|
+
if (!state.rings.has(ring)) state.resize.observe(ring);
|
|
241
|
+
}
|
|
242
|
+
for (const old of state.rings) if (!nextRings.has(old)) {
|
|
243
|
+
state.resize.unobserve(old);
|
|
244
|
+
old.removeAttribute("data-orbit-ring");
|
|
245
|
+
for (const name of ["--o-layout-number", "--o-layout-angle"]) old.style.removeProperty(name);
|
|
246
|
+
}
|
|
247
|
+
state.rings = nextRings;
|
|
248
|
+
const nextVisuals = new Set(state.scope.querySelectorAll(VISUALS));
|
|
249
|
+
for (const old of state.visuals) if (!nextVisuals.has(old)) state.resize.unobserve(old);
|
|
250
|
+
for (const visual of nextVisuals) if (!state.visuals.has(visual)) {
|
|
251
|
+
state.resize.observe(visual);
|
|
252
|
+
state.dirty.add(visual);
|
|
253
|
+
}
|
|
254
|
+
state.visuals = nextVisuals;
|
|
255
|
+
state.scan = false;
|
|
256
|
+
}
|
|
257
|
+
function flush(state) {
|
|
258
|
+
var _a;
|
|
259
|
+
if (state.disposed) return;
|
|
260
|
+
if (state.scope.host && !state.scope.host.isConnected) {
|
|
261
|
+
dispose(state);
|
|
262
|
+
return;
|
|
263
|
+
}
|
|
264
|
+
state.observer.disconnect();
|
|
265
|
+
try {
|
|
266
|
+
if (state.scan) discover(state);
|
|
267
|
+
const dirty = state.all ? /* @__PURE__ */ new Set([...state.rings, ...state.visuals]) : state.dirty;
|
|
268
|
+
const visuals = /* @__PURE__ */ new Set();
|
|
269
|
+
for (const target of dirty) {
|
|
270
|
+
if (!target.isConnected) continue;
|
|
271
|
+
if (state.rings.has(target)) {
|
|
272
|
+
layoutRing(target);
|
|
273
|
+
for (const visual of target.querySelectorAll(VISUALS)) visuals.add(visual);
|
|
274
|
+
} else if (state.visuals.has(target)) visuals.add(target);
|
|
275
|
+
}
|
|
276
|
+
for (const visual of state.visuals) if (visuals.has(visual)) (_a = visual.update) == null ? void 0 : _a.call(visual);
|
|
277
|
+
state.dirty = /* @__PURE__ */ new Set();
|
|
278
|
+
state.all = false;
|
|
279
|
+
} finally {
|
|
280
|
+
if (!state.disposed) state.observer.observe(state.scope, OBSERVE);
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
function requestLayout(element = globalThis.document) {
|
|
284
|
+
var _a;
|
|
285
|
+
const scope = scopeFor(element);
|
|
286
|
+
if (!scope) return;
|
|
287
|
+
const state = ensureState(scope);
|
|
288
|
+
if (!state) return;
|
|
289
|
+
if (((_a = element == null ? void 0 : element.matches) == null ? void 0 : _a.call(element, VISUALS)) && !state.visuals.has(element)) state.scan = true;
|
|
290
|
+
state.invalidate(element || scope);
|
|
291
|
+
}
|
|
292
|
+
function refreshLayout(root = globalThis.document) {
|
|
293
|
+
const scope = scopeFor(root);
|
|
294
|
+
if (!scope) return;
|
|
295
|
+
const state = ensureState(scope);
|
|
296
|
+
if (!state) return;
|
|
297
|
+
pruneDisconnected(state.registry);
|
|
298
|
+
for (const current of state.registry.states) {
|
|
299
|
+
if (current !== state && (!current.scope.host || !composedContains(root, current.scope.host))) continue;
|
|
300
|
+
current.scan = true;
|
|
301
|
+
current.all = true;
|
|
302
|
+
current.view.cancelAnimationFrame(current.frame);
|
|
303
|
+
current.frame = 0;
|
|
304
|
+
current.flush();
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
// src/js/orbit-base.js
|
|
309
|
+
var OrbitBase = class extends (globalThis.HTMLElement || class {
|
|
310
|
+
}) {
|
|
311
|
+
constructor() {
|
|
312
|
+
super();
|
|
313
|
+
}
|
|
314
|
+
connectedCallback() {
|
|
315
|
+
requestLayout(this);
|
|
316
|
+
}
|
|
317
|
+
disconnectedCallback() {
|
|
318
|
+
if (this._textFrame) this.ownerDocument.defaultView.cancelAnimationFrame(this._textFrame);
|
|
319
|
+
this._textFrame = 0;
|
|
320
|
+
}
|
|
321
|
+
readNumber(style, name, fallback = 0) {
|
|
322
|
+
return cssNumber(this, style.getPropertyValue(name), fallback);
|
|
323
|
+
}
|
|
324
|
+
readAngle(style, name, fallback = 0) {
|
|
325
|
+
return cssNumber(this, style.getPropertyValue(name), fallback, "angle");
|
|
326
|
+
}
|
|
327
|
+
getCommonAttributes(element) {
|
|
328
|
+
var _a;
|
|
329
|
+
const style = element.ownerDocument.defaultView.getComputedStyle(element);
|
|
330
|
+
const measuredRadius = parseFloat(style.getPropertyValue("r"));
|
|
331
|
+
const orbitRadius = Math.max(0, Number.isFinite(measuredRadius) ? measuredRadius : (((_a = element.parentElement) == null ? void 0 : _a.clientWidth) || 0) / 2);
|
|
332
|
+
const orbitNumber2 = Math.max(1e-5, this.readNumber(style, "--o-orbit-number", 1));
|
|
333
|
+
const size = Math.max(0, this.readNumber(style, "--o-size-ratio", 1));
|
|
334
|
+
const strokeWidth = Math.max(0, this.readNumber(style, "--o-stroke-width", 1));
|
|
335
|
+
const shape = element.getAttribute("shape") || "none";
|
|
336
|
+
const arcHeight = Math.max(0, orbitRadius / orbitNumber2 * size - strokeWidth + 0.3);
|
|
337
|
+
const arcHeightPercentage = orbitRadius > 0 ? Math.min(49.999, arcHeight * 25 / orbitRadius) : 0;
|
|
338
|
+
let innerOuter = 0;
|
|
339
|
+
if (element.classList.contains("outer-orbit")) {
|
|
340
|
+
innerOuter = arcHeightPercentage;
|
|
341
|
+
} else if (element.classList.contains("quarter-outer-orbit")) {
|
|
342
|
+
innerOuter = arcHeightPercentage * -0.5;
|
|
343
|
+
} else if (element.classList.contains("inner-orbit")) {
|
|
344
|
+
innerOuter = arcHeightPercentage * -1;
|
|
345
|
+
} else if (element.classList.contains("quarter-inner-orbit")) {
|
|
346
|
+
innerOuter = arcHeightPercentage * 0.5;
|
|
347
|
+
}
|
|
348
|
+
const realRadius = 50 + innerOuter;
|
|
349
|
+
return {
|
|
350
|
+
orbitRadius,
|
|
351
|
+
arcHeight,
|
|
352
|
+
realRadius,
|
|
353
|
+
arcAngle: 0,
|
|
354
|
+
// Se sobrescribe en cada componente
|
|
355
|
+
shape,
|
|
356
|
+
arcHeightPercentage,
|
|
357
|
+
orbitNumber: orbitNumber2,
|
|
358
|
+
size,
|
|
359
|
+
strokeWidth,
|
|
360
|
+
style
|
|
361
|
+
};
|
|
362
|
+
}
|
|
363
|
+
getProgressAngle(maxAngle, value, maxValue = 100) {
|
|
364
|
+
if (!Number.isFinite(value) || !Number.isFinite(maxValue) || maxValue <= 0) return 0;
|
|
365
|
+
return Math.min(1, Math.max(0, value / maxValue)) * Math.max(0, Math.min(360, maxAngle));
|
|
366
|
+
}
|
|
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
|
+
arcPoint(radius, angle, radiusAdjustment = 0, angleOffsetDegrees = 0) {
|
|
384
|
+
const adjustedRadius = radius + radiusAdjustment;
|
|
385
|
+
const adjustedAngle = angle + angleOffsetDegrees * Math.PI / 180;
|
|
386
|
+
return {
|
|
387
|
+
x: 50 + adjustedRadius * Math.cos(adjustedAngle),
|
|
388
|
+
y: 50 + adjustedRadius * Math.sin(adjustedAngle)
|
|
389
|
+
};
|
|
390
|
+
}
|
|
391
|
+
calculateCommonArcParameters(arcAngle, radius, arcHeightPercentage, orbitNumber2, shape, strokeWidth, arcHeight, gap = 0) {
|
|
392
|
+
const offset = Math.PI / 2;
|
|
393
|
+
const fangle = Math.max(0, Math.min(359.999999, arcAngle)) * Math.PI / 180;
|
|
394
|
+
const bigRadius = radius + arcHeightPercentage;
|
|
395
|
+
const smallRadius = Math.max(1e-3, radius - arcHeightPercentage);
|
|
396
|
+
const bigGap = Math.min(fangle * 0.49, (gap + strokeWidth * 1.25) / orbitNumber2 / bigRadius);
|
|
397
|
+
const smallGap = Math.min(fangle * 0.49, (gap + strokeWidth * 1.25) / orbitNumber2 / smallRadius);
|
|
398
|
+
const upperAngleStart = bigGap - offset;
|
|
399
|
+
const upperAngleEnd = fangle - bigGap - offset;
|
|
400
|
+
const innerAngleStart = smallGap - offset;
|
|
401
|
+
const innerAngleEnd = fangle - smallGap - offset;
|
|
402
|
+
const upperArcStart = this.arcPoint(bigRadius, upperAngleStart);
|
|
403
|
+
const upperArcEnd = this.arcPoint(bigRadius, upperAngleEnd);
|
|
404
|
+
const innerArcStart = this.arcPoint(smallRadius, innerAngleStart);
|
|
405
|
+
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
|
+
return {
|
|
411
|
+
upperArcStart,
|
|
412
|
+
upperArcEnd,
|
|
413
|
+
innerArcStart,
|
|
414
|
+
innerArcEnd,
|
|
415
|
+
largeArcFlag: largeArcFlagUpper,
|
|
416
|
+
// back-compat alias; prefer the two below
|
|
417
|
+
largeArcFlagUpper,
|
|
418
|
+
largeArcFlagInner,
|
|
419
|
+
bigRadius,
|
|
420
|
+
smallRadius,
|
|
421
|
+
radius,
|
|
422
|
+
upperAngleStart,
|
|
423
|
+
upperAngleEnd,
|
|
424
|
+
innerAngleStart,
|
|
425
|
+
innerAngleEnd
|
|
426
|
+
};
|
|
427
|
+
}
|
|
428
|
+
generatePathData(shape, params, arcHeight, orbitNumber2) {
|
|
429
|
+
let d = "";
|
|
430
|
+
switch (shape) {
|
|
431
|
+
case "rounded":
|
|
432
|
+
d = this.generateRoundedPath(params, arcHeight, orbitNumber2);
|
|
433
|
+
break;
|
|
434
|
+
case "circle":
|
|
435
|
+
case "circle-a":
|
|
436
|
+
case "bullet":
|
|
437
|
+
d = this.generateCirclePath(params, shape);
|
|
438
|
+
break;
|
|
439
|
+
case "circle-b":
|
|
440
|
+
d = this.generateCircleBPath(params, arcHeight, orbitNumber2);
|
|
441
|
+
break;
|
|
442
|
+
case "arrow":
|
|
443
|
+
d = this.generateArrowPath(params, orbitNumber2);
|
|
444
|
+
break;
|
|
445
|
+
case "backslash":
|
|
446
|
+
case "slash":
|
|
447
|
+
d = this.generateSlashPath(params, shape, orbitNumber2);
|
|
448
|
+
break;
|
|
449
|
+
case "zigzag":
|
|
450
|
+
d = this.generateZigzagPath(params, arcHeight, orbitNumber2);
|
|
451
|
+
break;
|
|
452
|
+
default:
|
|
453
|
+
d = this.generateDefaultPath(params);
|
|
454
|
+
}
|
|
455
|
+
return d;
|
|
456
|
+
}
|
|
457
|
+
generateRoundedPath(params, arcHeight, orbitNumber2) {
|
|
458
|
+
const { bigRadius, smallRadius } = params;
|
|
459
|
+
const available = Math.min(params.upperAngleEnd - params.upperAngleStart, params.innerAngleEnd - params.innerAngleStart);
|
|
460
|
+
const curve = Math.min(arcHeight < 5 ? 2.5 : arcHeight < 10 ? 5 : 10, available * 180 / Math.PI * orbitNumber2 * 0.49);
|
|
461
|
+
const capRad = curve / orbitNumber2 * Math.PI / 180;
|
|
462
|
+
const flagU = params.upperAngleEnd - params.upperAngleStart - 2 * capRad > Math.PI ? 1 : 0;
|
|
463
|
+
const flagI = params.innerAngleEnd - params.innerAngleStart - 2 * capRad > Math.PI ? 1 : 0;
|
|
464
|
+
const newUpperStart = this.arcPoint(bigRadius, params.upperAngleStart, 0, curve / orbitNumber2);
|
|
465
|
+
const newUpperEnd = this.arcPoint(bigRadius, params.upperAngleEnd, 0, -curve / orbitNumber2);
|
|
466
|
+
const newInnerStart = this.arcPoint(smallRadius, params.innerAngleStart, 0, curve / orbitNumber2);
|
|
467
|
+
const newInnerEnd = this.arcPoint(smallRadius, params.innerAngleEnd, 0, -curve / orbitNumber2);
|
|
468
|
+
const upperPointStart = this.arcPoint(bigRadius, params.upperAngleStart, -(curve / 2) / orbitNumber2, 0);
|
|
469
|
+
const upperPointEnd = this.arcPoint(bigRadius, params.upperAngleEnd, -(curve / 2) / orbitNumber2, 0);
|
|
470
|
+
const innerPointStart = this.arcPoint(smallRadius, params.innerAngleStart, curve / 2 / orbitNumber2, 0);
|
|
471
|
+
const innerPointEnd = this.arcPoint(smallRadius, params.innerAngleEnd, curve / 2 / orbitNumber2, 0);
|
|
472
|
+
const Q = this.getControlPoint(newUpperEnd.x, newUpperEnd.y, upperPointEnd.x, upperPointEnd.y);
|
|
473
|
+
const Q1 = this.getControlPoint(innerPointEnd.x, innerPointEnd.y, newInnerEnd.x, newInnerEnd.y);
|
|
474
|
+
const Q2 = this.getControlPoint(newInnerStart.x, newInnerStart.y, innerPointStart.x, innerPointStart.y);
|
|
475
|
+
const Q3 = this.getControlPoint(upperPointStart.x, upperPointStart.y, newUpperStart.x, newUpperStart.y);
|
|
476
|
+
let d = `M ${newUpperStart.x},${newUpperStart.y} A ${bigRadius},${bigRadius} 0 ${flagU} 1 ${newUpperEnd.x},${newUpperEnd.y}`;
|
|
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`;
|
|
483
|
+
return d;
|
|
484
|
+
}
|
|
485
|
+
// Dentro de la clase OrbitCommon en orbit-common.js
|
|
486
|
+
generateCirclePath(params, shape) {
|
|
487
|
+
const { upperArcStart, upperArcEnd, innerArcStart, innerArcEnd, bigRadius, smallRadius, largeArcFlagUpper, largeArcFlagInner } = params;
|
|
488
|
+
let d = `M ${upperArcStart.x},${upperArcStart.y} A ${bigRadius},${bigRadius} 0 ${largeArcFlagUpper} 1 ${upperArcEnd.x},${upperArcEnd.y}`;
|
|
489
|
+
d += ` A 1,1 0 0 1 ${innerArcEnd.x},${innerArcEnd.y} `;
|
|
490
|
+
d += ` A ${smallRadius},${smallRadius} 0 ${largeArcFlagInner} 0 ${innerArcStart.x},${innerArcStart.y}`;
|
|
491
|
+
d += ` A 1,1 0 0 ${shape === "circle" || shape === "circle-a" ? 1 : 0} ${upperArcStart.x},${upperArcStart.y} `;
|
|
492
|
+
d += ` Z`;
|
|
493
|
+
return d;
|
|
494
|
+
}
|
|
495
|
+
generateCircleBPath(params, arcHeight, orbitNumber2) {
|
|
496
|
+
const { upperAngleStart, upperAngleEnd, innerAngleStart, innerAngleEnd, bigRadius, smallRadius } = params;
|
|
497
|
+
const available = Math.min(upperAngleEnd - upperAngleStart, innerAngleEnd - innerAngleStart);
|
|
498
|
+
const segment = Math.min(arcHeight * 1.36, available * 180 / Math.PI * orbitNumber2 * 0.49);
|
|
499
|
+
const capRad = segment / orbitNumber2 * Math.PI / 180;
|
|
500
|
+
const flagU = upperAngleEnd - upperAngleStart - 2 * capRad > Math.PI ? 1 : 0;
|
|
501
|
+
const flagI = innerAngleEnd - innerAngleStart - 2 * capRad > Math.PI ? 1 : 0;
|
|
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`;
|
|
511
|
+
return d;
|
|
512
|
+
}
|
|
513
|
+
generateArrowPath(params, orbitNumber2) {
|
|
514
|
+
const { upperArcStart, upperArcEnd, innerArcStart, innerArcEnd, bigRadius, smallRadius, largeArcFlagUpper, largeArcFlagInner, radius } = params;
|
|
515
|
+
const middleEnd = this.arcPoint(radius, params.upperAngleEnd, 0, 24 / orbitNumber2 / 2);
|
|
516
|
+
const middleStart = this.arcPoint(radius, params.upperAngleStart, 0, 24 / orbitNumber2 / 2);
|
|
517
|
+
let d = `M ${upperArcStart.x},${upperArcStart.y} A ${bigRadius},${bigRadius} 0 ${largeArcFlagUpper} 1 ${upperArcEnd.x},${upperArcEnd.y}`;
|
|
518
|
+
d += `L ${middleEnd.x} ${middleEnd.y}`;
|
|
519
|
+
d += `L ${innerArcEnd.x} ${innerArcEnd.y}`;
|
|
520
|
+
d += `A ${smallRadius},${smallRadius} 0 ${largeArcFlagInner} 0 ${innerArcStart.x}, ${innerArcStart.y}`;
|
|
521
|
+
d += `L ${middleStart.x} ${middleStart.y}`;
|
|
522
|
+
d += `Z`;
|
|
523
|
+
return d;
|
|
524
|
+
}
|
|
525
|
+
generateSlashPath(params, shape, orbitNumber2) {
|
|
526
|
+
const { upperAngleStart, upperAngleEnd, innerAngleStart, innerAngleEnd, bigRadius, smallRadius, largeArcFlagUpper, largeArcFlagInner } = params;
|
|
527
|
+
const newUpperStart = this.arcPoint(bigRadius, upperAngleStart, 0, shape === "backslash" ? 0 : 24 / orbitNumber2 / 2);
|
|
528
|
+
const newUpperEnd = this.arcPoint(bigRadius, upperAngleEnd, 0, shape === "backslash" ? 0 : 24 / orbitNumber2 / 2);
|
|
529
|
+
const newInnerStart = this.arcPoint(smallRadius, innerAngleStart, 0, shape === "backslash" ? 24 / orbitNumber2 / 2 : 0);
|
|
530
|
+
const newInnerEnd = this.arcPoint(smallRadius, innerAngleEnd, 0, shape === "backslash" ? 24 / orbitNumber2 / 2 : 0);
|
|
531
|
+
let d = `M ${newUpperStart.x},${newUpperStart.y} A ${bigRadius},${bigRadius} 0 ${largeArcFlagUpper} 1 ${newUpperEnd.x},${newUpperEnd.y}`;
|
|
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;
|
|
536
|
+
}
|
|
537
|
+
generateZigzagPath(params, arcHeight, orbitNumber2) {
|
|
538
|
+
const { upperArcStart, upperArcEnd, innerArcStart, innerArcEnd, bigRadius, smallRadius, largeArcFlagUpper, largeArcFlagInner, radius } = params;
|
|
539
|
+
const h2 = arcHeight / orbitNumber2 / 2;
|
|
540
|
+
const s2 = this.arcPoint(radius, params.upperAngleStart, -h2, 3);
|
|
541
|
+
const s3 = this.arcPoint(radius, params.upperAngleStart, 0, 0);
|
|
542
|
+
const s4 = this.arcPoint(radius, params.upperAngleStart, h2, 3);
|
|
543
|
+
const e2 = this.arcPoint(radius, params.innerAngleEnd, h2, 3);
|
|
544
|
+
const e3 = this.arcPoint(radius, params.innerAngleEnd, 0, 0);
|
|
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`;
|
|
556
|
+
return d;
|
|
557
|
+
}
|
|
558
|
+
generateDefaultPath(params) {
|
|
559
|
+
const { upperArcStart, upperArcEnd, innerArcStart, innerArcEnd, bigRadius, smallRadius, largeArcFlagUpper, largeArcFlagInner } = params;
|
|
560
|
+
let d = `M ${upperArcStart.x},${upperArcStart.y} A ${bigRadius},${bigRadius} 0 ${largeArcFlagUpper} 1 ${upperArcEnd.x},${upperArcEnd.y}`;
|
|
561
|
+
d += `L ${innerArcEnd.x} ${innerArcEnd.y}`;
|
|
562
|
+
d += `A ${smallRadius},${smallRadius} 0 ${largeArcFlagInner} 0 ${innerArcStart.x}, ${innerArcStart.y}`;
|
|
563
|
+
d += `Z`;
|
|
564
|
+
return d;
|
|
565
|
+
}
|
|
566
|
+
};
|
|
567
|
+
|
|
568
|
+
// src/js/orbit-progress.js
|
|
569
|
+
var OrbitProgress = class extends OrbitBase {
|
|
570
|
+
constructor() {
|
|
571
|
+
super();
|
|
572
|
+
this.attachShadow({ mode: "open" });
|
|
573
|
+
this.shadowRoot.innerHTML = `
|
|
574
|
+
<style>
|
|
575
|
+
:host {
|
|
576
|
+
--o-fill: var(--o-gray-light);
|
|
577
|
+
--o-stroke: var(--o-fill);
|
|
578
|
+
--o-stroke-width: 1;
|
|
579
|
+
--o-back-fill: transparent;
|
|
580
|
+
--o-back-stroke: none;
|
|
581
|
+
--o-back-stroke-width: 1;
|
|
582
|
+
}
|
|
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
|
+
svg {
|
|
592
|
+
width: 100%;
|
|
593
|
+
height: 100%;
|
|
594
|
+
overflow: visible;
|
|
595
|
+
pointer-events: none;
|
|
596
|
+
}
|
|
597
|
+
/* Display element by default: never steals clicks (see orbit-arc).
|
|
598
|
+
Interaction is opt-in via the "interactive" attribute. */
|
|
599
|
+
svg * {
|
|
600
|
+
pointer-events: none;
|
|
601
|
+
}
|
|
602
|
+
:host([interactive]) svg * {
|
|
603
|
+
pointer-events: visiblePainted;
|
|
604
|
+
}
|
|
605
|
+
:host([interactive]) {
|
|
606
|
+
cursor: pointer;
|
|
607
|
+
}
|
|
608
|
+
.progress-bar {
|
|
609
|
+
fill: var(--o-fill);
|
|
610
|
+
stroke: var(--o-stroke);
|
|
611
|
+
stroke-width: var(--o-stroke-width);
|
|
612
|
+
transition: fill 0.25s, stroke 0.25s;
|
|
613
|
+
stroke-linejoin: round;
|
|
614
|
+
}
|
|
615
|
+
.progress-bg {
|
|
616
|
+
fill: var(--o-back-fill);
|
|
617
|
+
stroke: var(--o-back-stroke);
|
|
618
|
+
stroke-width: var(--o-back-stroke-width);
|
|
619
|
+
}
|
|
620
|
+
/* variant="stroke": the thin, legible gauge (registro "medidor").
|
|
621
|
+
The default band is a FILLED donut wedge, so everyone building a
|
|
622
|
+
thin gauge tripped on --o-fill/--o-back-fill. With this variant the
|
|
623
|
+
paths are open arcs and the data color lives where you expect it:
|
|
624
|
+
--o-stroke for the bar, --o-back-stroke for the track. */
|
|
625
|
+
:host([variant="stroke"]) .progress-bar {
|
|
626
|
+
fill: none;
|
|
627
|
+
stroke: var(--o-stroke);
|
|
628
|
+
stroke-width: var(--o-stroke-width, 2);
|
|
629
|
+
stroke-linecap: round;
|
|
630
|
+
}
|
|
631
|
+
:host([variant="stroke"]) .progress-bg {
|
|
632
|
+
fill: none;
|
|
633
|
+
stroke: var(--o-back-stroke, var(--o-gray-light));
|
|
634
|
+
stroke-width: var(--o-back-stroke-width, 1);
|
|
635
|
+
stroke-linecap: round;
|
|
636
|
+
}
|
|
637
|
+
</style>
|
|
638
|
+
<svg viewBox="0 0 100 100">
|
|
639
|
+
<path class="progress-bg" shape-rendering="geometricPrecision" vector-effect="non-scaling-stroke"></path>
|
|
640
|
+
<path class="progress-bar" shape-rendering="geometricPrecision" vector-effect="non-scaling-stroke"></path>
|
|
641
|
+
</svg>
|
|
642
|
+
`;
|
|
643
|
+
}
|
|
644
|
+
update() {
|
|
645
|
+
const attrs = this.getAttributes();
|
|
646
|
+
const isStroke = this.getAttribute("variant") === "stroke";
|
|
647
|
+
const dBg = isStroke ? this.calculateStrokeArc(attrs, true) : this.calculateArcParameters(attrs, true);
|
|
648
|
+
const dBar = isStroke ? this.calculateStrokeArc(attrs, false) : this.calculateArcParameters(attrs, false);
|
|
649
|
+
this.shadowRoot.querySelector(".progress-bg").setAttribute("d", dBg);
|
|
650
|
+
this.shadowRoot.querySelector(".progress-bar").setAttribute("d", dBar);
|
|
651
|
+
}
|
|
652
|
+
/**
|
|
653
|
+
* variant="stroke": open arc along the orbit radius (no closed band),
|
|
654
|
+
* stroked by CSS. Same progress math as the band variant.
|
|
655
|
+
*/
|
|
656
|
+
calculateStrokeArc(attrs, full) {
|
|
657
|
+
const { realRadius } = attrs;
|
|
658
|
+
const arcAngle = Math.max(0, Math.min(this.getProgressAngle(attrs, full), 359.999999));
|
|
659
|
+
if (!(arcAngle > 0) || !(attrs.orbitRadius > 0)) return "";
|
|
660
|
+
const a0 = -90 * (Math.PI / 180);
|
|
661
|
+
const a1 = (-90 + arcAngle) * (Math.PI / 180);
|
|
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}`;
|
|
668
|
+
}
|
|
669
|
+
getAttributes() {
|
|
670
|
+
const common = super.getCommonAttributes(this);
|
|
671
|
+
const range = Math.max(0, Math.min(360, this.readAngle(common.style, "--o-range", 360)));
|
|
672
|
+
const rawValue = common.style.getPropertyValue("--o-progress").trim();
|
|
673
|
+
const progress = rawValue ? this.readNumber(common.style, "--o-progress", 0) : Number(this.getAttribute("value") || 0);
|
|
674
|
+
const rawMax = this.getAttribute("max");
|
|
675
|
+
const maxValue = rawMax === null ? 100 : Number(rawMax);
|
|
676
|
+
return {
|
|
677
|
+
...common,
|
|
678
|
+
range,
|
|
679
|
+
progress,
|
|
680
|
+
maxValue
|
|
681
|
+
};
|
|
682
|
+
}
|
|
683
|
+
getProgressAngle(attrs, full) {
|
|
684
|
+
const { range, progress, maxValue } = attrs;
|
|
685
|
+
return full ? range : super.getProgressAngle(range, progress, maxValue);
|
|
686
|
+
}
|
|
687
|
+
calculateArcParameters(attrs, full) {
|
|
688
|
+
const { shape, realRadius, arcHeightPercentage, orbitNumber: orbitNumber2, strokeWidth, arcHeight } = attrs;
|
|
689
|
+
const arcAngle = this.getProgressAngle(attrs, full);
|
|
690
|
+
if (!(arcAngle > 0) || !(attrs.orbitRadius > 0)) return "";
|
|
691
|
+
const params = super.calculateCommonArcParameters(
|
|
692
|
+
arcAngle,
|
|
693
|
+
realRadius,
|
|
694
|
+
arcHeightPercentage,
|
|
695
|
+
orbitNumber2,
|
|
696
|
+
shape,
|
|
697
|
+
strokeWidth,
|
|
698
|
+
arcHeight
|
|
699
|
+
);
|
|
700
|
+
return super.generatePathData(shape, params, arcHeight, orbitNumber2);
|
|
701
|
+
}
|
|
702
|
+
};
|
|
703
|
+
|
|
704
|
+
// src/js/orbit-arc.js
|
|
705
|
+
var template = typeof document === "undefined" ? null : document.createElement("template");
|
|
706
|
+
if (template) template.innerHTML = `
|
|
707
|
+
<style>
|
|
708
|
+
:host {
|
|
709
|
+
--o-fill: var(--o-gray-light);
|
|
710
|
+
--o-stroke: var(--o-fill);
|
|
711
|
+
--o-stroke-width: 1;
|
|
712
|
+
--o-color: currentcolor;
|
|
713
|
+
}
|
|
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
|
+
svg {
|
|
721
|
+
width: 100%;
|
|
722
|
+
height: 100%;
|
|
723
|
+
overflow: visible;
|
|
724
|
+
pointer-events: none;
|
|
725
|
+
}
|
|
726
|
+
/* Arcs are display elements by default: they must NOT steal clicks from
|
|
727
|
+
satellites/controls nearby (a painted arc used to capture clicks even
|
|
728
|
+
at opacity 0, and pointer-events:none on the host could not pierce the
|
|
729
|
+
shadow). Interaction is opt-in via the "interactive" attribute. */
|
|
730
|
+
svg * {
|
|
731
|
+
pointer-events: none;
|
|
732
|
+
}
|
|
733
|
+
:host([interactive]) svg * {
|
|
734
|
+
pointer-events: visiblePainted;
|
|
735
|
+
}
|
|
736
|
+
:host([interactive]) {
|
|
737
|
+
cursor: pointer;
|
|
738
|
+
}
|
|
739
|
+
#orbitShape {
|
|
740
|
+
fill: var(--o-fill);
|
|
741
|
+
stroke: var(--o-stroke);
|
|
742
|
+
stroke-width: var(--o-stroke-width);
|
|
743
|
+
transition: fill 0.25s, stroke 0.25s;
|
|
744
|
+
}
|
|
745
|
+
text {
|
|
746
|
+
fill: var(--o-color);
|
|
747
|
+
}
|
|
748
|
+
#orbitPath {
|
|
749
|
+
fill: transparent;
|
|
750
|
+
stroke: none;
|
|
751
|
+
stroke-width: 0;
|
|
752
|
+
}
|
|
753
|
+
</style>
|
|
754
|
+
<svg viewBox="0 0 100 100">
|
|
755
|
+
<path id="orbitShape" shape-rendering="geometricPrecision" vector-effect="non-scaling-stroke"></path>
|
|
756
|
+
<path id="orbitPath" shape-rendering="geometricPrecision" vector-effect="non-scaling-stroke"></path>
|
|
757
|
+
<text>
|
|
758
|
+
<textPath href="#orbitPath" alignment-baseline="middle"></textPath>
|
|
759
|
+
</text>
|
|
760
|
+
</svg>
|
|
761
|
+
`;
|
|
762
|
+
var OrbitArc = class extends OrbitBase {
|
|
763
|
+
constructor() {
|
|
764
|
+
super();
|
|
765
|
+
this.attachShadow({ mode: "open" });
|
|
766
|
+
this.shadowRoot.appendChild(template.content.cloneNode(true));
|
|
767
|
+
}
|
|
768
|
+
update() {
|
|
769
|
+
const attrs = this.getAttributes();
|
|
770
|
+
const { length, fontSize, textAnchor, fitRange } = attrs;
|
|
771
|
+
if (this.hasAttribute("value")) {
|
|
772
|
+
setLayoutProperty(this, "--o-arc-start", `${attrs.stackOffset}deg`);
|
|
773
|
+
setLayoutProperty(this, "--o_stack", attrs.stackOffset + attrs.arcAngle);
|
|
774
|
+
} else {
|
|
775
|
+
this.style.removeProperty("--o-arc-start");
|
|
776
|
+
this.style.removeProperty("--o_stack");
|
|
777
|
+
}
|
|
778
|
+
const orbitPath = this.shadowRoot.querySelector("#orbitPath");
|
|
779
|
+
const orbitShape = this.shadowRoot.querySelector("#orbitShape");
|
|
780
|
+
const textPath = this.shadowRoot.querySelector("textPath");
|
|
781
|
+
orbitShape.setAttribute("d", this.calculateArcParameters(attrs).dShape);
|
|
782
|
+
orbitPath.setAttribute("d", this.calculateTextArcParameters(attrs).dPath);
|
|
783
|
+
if (textAnchor === "start") {
|
|
784
|
+
textPath.setAttribute("startOffset", "0%");
|
|
785
|
+
textPath.setAttribute("text-anchor", "start");
|
|
786
|
+
} else if (textAnchor === "middle") {
|
|
787
|
+
textPath.setAttribute("startOffset", "50%");
|
|
788
|
+
textPath.setAttribute("text-anchor", "middle");
|
|
789
|
+
} else if (textAnchor === "end") {
|
|
790
|
+
textPath.setAttribute("startOffset", "100%");
|
|
791
|
+
textPath.setAttribute("text-anchor", "end");
|
|
792
|
+
}
|
|
793
|
+
if (fitRange) {
|
|
794
|
+
textPath.parentElement.setAttribute("textLength", orbitPath.getTotalLength());
|
|
795
|
+
} else {
|
|
796
|
+
textPath.parentElement.removeAttribute("textLength");
|
|
797
|
+
}
|
|
798
|
+
textPath.parentElement.style.fontSize = `calc(${fontSize} * (100 / (${length}) * (12 / var(--o-orbit-number)))`;
|
|
799
|
+
textPath.textContent = this.textContent;
|
|
800
|
+
this.warnIfTextOverflows(orbitPath, textPath, fitRange);
|
|
801
|
+
}
|
|
802
|
+
/**
|
|
803
|
+
* Curved text has an explicit angular budget: glyphs past the end of the
|
|
804
|
+
* arc path are clipped SILENTLY by SVG. Measure and warn so nobody loses
|
|
805
|
+
* time to invisible truncation (~20 chars in 96° at default sizes).
|
|
806
|
+
* fit-range squeezes the text to the path via textLength, so it never clips.
|
|
807
|
+
*/
|
|
808
|
+
warnIfTextOverflows(orbitPath, textPath, fitRange) {
|
|
809
|
+
const raw = (this.textContent || "").trim();
|
|
810
|
+
if (!raw || fitRange || !this.isConnected) return;
|
|
811
|
+
if (this._textFrame) this.ownerDocument.defaultView.cancelAnimationFrame(this._textFrame);
|
|
812
|
+
this._textFrame = this.ownerDocument.defaultView.requestAnimationFrame(() => {
|
|
813
|
+
this._textFrame = 0;
|
|
814
|
+
if (!this.isConnected) return;
|
|
815
|
+
try {
|
|
816
|
+
const pathLen = orbitPath.getTotalLength();
|
|
817
|
+
const textLen = textPath.parentElement.getComputedTextLength();
|
|
818
|
+
if (pathLen > 0 && textLen > pathLen && this._truncWarned !== raw) {
|
|
819
|
+
this._truncWarned = raw;
|
|
820
|
+
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, or use fit-range to squeeze it.`
|
|
822
|
+
);
|
|
823
|
+
}
|
|
824
|
+
} catch (e) {
|
|
825
|
+
}
|
|
826
|
+
});
|
|
827
|
+
}
|
|
828
|
+
getAttributes() {
|
|
829
|
+
const common = super.getCommonAttributes(this);
|
|
830
|
+
const { style } = common;
|
|
831
|
+
const range = Math.max(0, Math.min(360, this.readAngle(style, "--o-range", 360)));
|
|
832
|
+
const flip = this.hasAttribute("flip") || this.classList.contains("flip");
|
|
833
|
+
const fitRange = this.hasAttribute("fit-range") || this.classList.contains("fit-range");
|
|
834
|
+
const length = common.orbitRadius * 24 / common.orbitNumber || 100;
|
|
835
|
+
const textAnchor = this.getAttribute("text-anchor") || "middle";
|
|
836
|
+
const fontSize = style.fontSize || "16px";
|
|
837
|
+
const gap = Math.max(0, this.readNumber(style, "--o-gap", 1));
|
|
838
|
+
const value = Number(this.getAttribute("value"));
|
|
839
|
+
const rawMax = this.getAttribute("max");
|
|
840
|
+
const max = rawMax === null ? 100 : Number(rawMax);
|
|
841
|
+
const arcAngle = this.hasAttribute("value") ? super.getProgressAngle(range, value, max) : Math.max(0, Math.min(360, this.readAngle(style, "--o-angle", 0)));
|
|
842
|
+
let stackOffset = 0;
|
|
843
|
+
if (this.hasAttribute("value")) {
|
|
844
|
+
for (let prev = this.previousElementSibling; prev; prev = prev.previousElementSibling) {
|
|
845
|
+
if (prev.localName !== "o-arc") continue;
|
|
846
|
+
const previousStyle = this.ownerDocument.defaultView.getComputedStyle(prev);
|
|
847
|
+
if (prev.hasAttribute("value")) {
|
|
848
|
+
const prevMax = prev.hasAttribute("max") ? Number(prev.getAttribute("max")) : 100;
|
|
849
|
+
const prevRange = cssNumber(prev, previousStyle.getPropertyValue("--o-range"), range, "angle");
|
|
850
|
+
stackOffset += super.getProgressAngle(prevRange, Number(prev.getAttribute("value")), prevMax);
|
|
851
|
+
} else stackOffset += cssNumber(prev, previousStyle.getPropertyValue("--o-angle"), 0, "angle");
|
|
852
|
+
}
|
|
853
|
+
}
|
|
854
|
+
return { ...common, gap, arcAngle, stackOffset, flip, fitRange, length, fontSize, textAnchor };
|
|
855
|
+
}
|
|
856
|
+
calculateArcParameters(attrs) {
|
|
857
|
+
const { arcAngle, realRadius, arcHeightPercentage, orbitNumber: orbitNumber2, shape, strokeWidth, arcHeight, gap } = attrs;
|
|
858
|
+
if (!(arcAngle > 0) || !(attrs.orbitRadius > 0)) return { dShape: "" };
|
|
859
|
+
const params = super.calculateCommonArcParameters(
|
|
860
|
+
arcAngle,
|
|
861
|
+
realRadius,
|
|
862
|
+
arcHeightPercentage,
|
|
863
|
+
orbitNumber2,
|
|
864
|
+
shape,
|
|
865
|
+
strokeWidth,
|
|
866
|
+
arcHeight,
|
|
867
|
+
gap
|
|
868
|
+
);
|
|
869
|
+
const dShape = super.generatePathData(shape, params, arcHeight, orbitNumber2);
|
|
870
|
+
return { dShape };
|
|
871
|
+
}
|
|
872
|
+
calculateTextArcParameters(attrs) {
|
|
873
|
+
const { arcAngle, realRadius, gap, flip } = attrs;
|
|
874
|
+
if (!(arcAngle > 0)) return { dPath: "" };
|
|
875
|
+
const adjustedGap = Math.min(gap * 0.5, arcAngle * 0.49);
|
|
876
|
+
const sweepFlag = flip ? 0 : 1;
|
|
877
|
+
const largeArcFlag = arcAngle <= 180 ? 0 : 1;
|
|
878
|
+
let coordX1 = 50 + realRadius * Math.cos((-90 + adjustedGap) * (Math.PI / 180));
|
|
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 };
|
|
885
|
+
}
|
|
886
|
+
calcularExpresionCSS(cssExpression) {
|
|
887
|
+
return cssNumber(this, cssExpression, 0, "angle");
|
|
888
|
+
}
|
|
889
|
+
};
|
|
890
|
+
|
|
891
|
+
// src/js/orbit-resize.js
|
|
892
|
+
var resizing = /* @__PURE__ */ new WeakMap();
|
|
893
|
+
var Orbit = {
|
|
894
|
+
refresh: refreshLayout,
|
|
895
|
+
resize(parentElementSelector) {
|
|
896
|
+
var _a, _b;
|
|
897
|
+
const parent = typeof parentElementSelector === "string" ? (_a = globalThis.document) == null ? void 0 : _a.querySelector(parentElementSelector) : parentElementSelector;
|
|
898
|
+
if (!(parent == null ? void 0 : parent.ownerDocument)) {
|
|
899
|
+
console.error("Orbit.resize: element not found:", parentElementSelector);
|
|
900
|
+
return () => {
|
|
901
|
+
};
|
|
902
|
+
}
|
|
903
|
+
(_b = resizing.get(parent)) == null ? void 0 : _b();
|
|
904
|
+
const view = parent.ownerDocument.defaultView;
|
|
905
|
+
const applyRatio = (width) => {
|
|
906
|
+
if (!(width > 0)) return;
|
|
907
|
+
for (const element of parent.querySelectorAll(".gravity-spot")) {
|
|
908
|
+
const ratio = String(width / 500);
|
|
909
|
+
if (element.style.getPropertyValue("--o-force-ratio") !== ratio) element.style.setProperty("--o-force-ratio", ratio);
|
|
910
|
+
}
|
|
911
|
+
refreshLayout(parent);
|
|
912
|
+
};
|
|
913
|
+
const observer = new view.ResizeObserver((entries) => {
|
|
914
|
+
for (const entry of entries) applyRatio(entry.contentRect.width);
|
|
915
|
+
});
|
|
916
|
+
observer.observe(parent);
|
|
917
|
+
applyRatio(parent.clientWidth || parent.getBoundingClientRect().width);
|
|
918
|
+
const stop = () => {
|
|
919
|
+
observer.disconnect();
|
|
920
|
+
if (resizing.get(parent) === stop) resizing.delete(parent);
|
|
921
|
+
};
|
|
922
|
+
resizing.set(parent, stop);
|
|
923
|
+
return stop;
|
|
924
|
+
}
|
|
925
|
+
};
|
|
926
|
+
|
|
927
|
+
// src/orbit.js
|
|
928
|
+
function registerOrbit() {
|
|
929
|
+
if (typeof customElements === "undefined" || typeof document === "undefined") return;
|
|
930
|
+
if (!customElements.get("o-progress")) customElements.define("o-progress", OrbitProgress);
|
|
931
|
+
if (!customElements.get("o-arc")) customElements.define("o-arc", OrbitArc);
|
|
932
|
+
requestLayout(document);
|
|
933
|
+
window.Orbit = Orbit;
|
|
934
|
+
}
|
|
935
|
+
registerOrbit();
|
|
936
|
+
export {
|
|
937
|
+
Orbit,
|
|
938
|
+
OrbitArc,
|
|
939
|
+
OrbitProgress,
|
|
940
|
+
registerOrbit
|
|
941
|
+
};
|