Compare commits
29 Commits
sprint/iss
...
sprint/iss
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a1ae4bf59a | ||
| 729343e503 | |||
| 1081b9e6c4 | |||
|
|
e74f956bf4 | ||
| 55f280d056 | |||
|
|
6446ecb43a | ||
| 0a312b111d | |||
|
|
141b240d69 | ||
| 093f7688bd | |||
| c4a31255a4 | |||
|
|
c876a35dc0 | ||
|
|
3d851a8708 | ||
| fbb782bd77 | |||
|
|
9a829584b0 | ||
| 020c003d45 | |||
| 610252b597 | |||
|
|
04f869c70d | ||
| bbcce1f064 | |||
|
|
a2f345593c | ||
|
|
b819fc068a | ||
|
|
8e006897a4 | ||
| ff9c1b1864 | |||
| 9fd70fa942 | |||
| c714061bd8 | |||
| 220fc44c6a | |||
| 26bb33c5eb | |||
| 954a6c4111 | |||
|
|
e72e5ee121 | ||
|
|
74575929af |
1
.ci-trigger
Normal file
1
.ci-trigger
Normal file
@@ -0,0 +1 @@
|
||||
# Trivial file to re-trigger CI after stale run
|
||||
@@ -10,12 +10,11 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Validate ARIA Attributes in game.js
|
||||
- name: Validate ARIA Attributes in JS
|
||||
run: |
|
||||
echo "Checking game.js for ARIA attributes..."
|
||||
grep -q "aria-label" game.js || (echo "ERROR: aria-label missing from game.js" && exit 1)
|
||||
grep -q "aria-valuenow" game.js || (echo "ERROR: aria-valuenow missing from game.js" && exit 1)
|
||||
grep -q "aria-pressed" game.js || (echo "ERROR: aria-pressed missing from game.js" && exit 1)
|
||||
echo "Checking js/*.js for ARIA attributes..."
|
||||
grep -rq "aria-label" js/ || (echo "ERROR: aria-label missing from js/" && exit 1)
|
||||
grep -rq "aria-pressed" js/ || (echo "ERROR: aria-pressed missing from js/" && exit 1)
|
||||
|
||||
- name: Validate ARIA Roles in index.html
|
||||
run: |
|
||||
@@ -24,4 +23,7 @@ jobs:
|
||||
|
||||
- name: Syntax Check JS
|
||||
run: |
|
||||
node -c game.js
|
||||
for f in js/*.js; do
|
||||
echo "Syntax check: $f"
|
||||
node -c "$f" || exit 1
|
||||
done
|
||||
|
||||
@@ -8,6 +8,9 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '20'
|
||||
- uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: '3.11'
|
||||
@@ -20,5 +23,9 @@ jobs:
|
||||
echo "PASS: All files parse"
|
||||
- name: Secret scan
|
||||
run: |
|
||||
if grep -rE 'sk-or-|sk-ant-|ghp_|AKIA' . --include='*.yml' --include='*.py' --include='*.sh' 2>/dev/null | grep -v .gitea; then exit 1; fi
|
||||
if grep -rE 'sk-or-|sk-ant-|ghp_|AKIA' . --include='*.yml' --include='*.py' --include='*.sh' 2>/dev/null | grep -v '.gitea' | grep -v 'guardrails'; then exit 1; fi
|
||||
echo "PASS: No secrets"
|
||||
- name: Node tests
|
||||
run: |
|
||||
node --test tests/*.cjs
|
||||
echo "PASS: Node tests"
|
||||
|
||||
61
index.html
61
index.html
@@ -38,6 +38,7 @@ body{background:var(--bg);color:var(--text);font-family:'SF Mono','Cascadia Code
|
||||
.milestone-chip{font-size:9px;padding:2px 8px;border-radius:10px;border:1px solid var(--border);color:var(--dim);background:#0a0a14}
|
||||
.milestone-chip.next{border-color:var(--accent);color:var(--accent);animation:pulse-chip 2s ease-in-out infinite}
|
||||
.milestone-chip.done{border-color:#2a4a2a;color:var(--green);opacity:0.6}
|
||||
.trust-milestone-bar{margin-top:6px;padding-top:6px;border-top:1px solid #1a1a2a}
|
||||
@keyframes pulse-chip{0%,100%{box-shadow:0 0 0 rgba(74,158,255,0)}50%{box-shadow:0 0 8px rgba(74,158,255,0.3)}}
|
||||
@keyframes beacon-glow{0%,100%{opacity:0.7}50%{opacity:1}}
|
||||
#resources{display:grid;grid-template-columns:repeat(auto-fit,minmax(100px,1fr));gap:6px;margin:12px 16px}
|
||||
@@ -59,6 +60,10 @@ body{background:var(--bg);color:var(--text);font-family:'SF Mono','Cascadia Code
|
||||
.ops-btn{background:#1a1a2a;border:1px solid var(--purple);color:var(--purple);font-size:10px;padding:6px 10px;border-radius:4px;cursor:pointer;font-family:inherit;transition:all 0.15s}
|
||||
.ops-btn:hover:not(:disabled){background:#2a2a3a;border-color:var(--gold)}
|
||||
.ops-btn:disabled{opacity:0.3;cursor:not-allowed}
|
||||
@keyframes res-pulse{0%{transform:scale(1);color:inherit}50%{transform:scale(1.18);color:#4caf50}100%{transform:scale(1);color:inherit}}
|
||||
@keyframes res-shake{0%,100%{transform:translateX(0)}20%{transform:translateX(-3px);color:#f44336}40%{transform:translateX(3px)}60%{transform:translateX(-2px)}80%{transform:translateX(2px)}}
|
||||
.res .pulse{animation:res-pulse 0.35s ease-out}
|
||||
.res .shake{animation:res-shake 0.35s ease-out}
|
||||
.build-btn{display:block;width:100%;text-align:left;padding:6px 10px;margin-bottom:4px;border-radius:4px;cursor:pointer;font-family:inherit;font-size:10px;background:#0c0c18;border:1px solid var(--border);color:var(--text);transition:all 0.15s}
|
||||
.build-btn.can-buy{border-color:#2a3a4a;background:#0e1420}
|
||||
.build-btn.can-buy:hover{border-color:var(--accent);box-shadow:0 0 8px var(--glow)}
|
||||
@@ -86,6 +91,8 @@ body{background:var(--bg);color:var(--text);font-family:'SF Mono','Cascadia Code
|
||||
#drift-ending .ending-quote{color:var(--dim);font-style:italic;font-size:11px;border-left:2px solid #f44336;padding-left:12px;margin:20px 0;text-align:left}
|
||||
#drift-ending button{margin-top:20px;background:#1a0808;border:1px solid #f44336;color:#f44336;padding:10px 24px;border-radius:4px;cursor:pointer;font-family:inherit;font-size:11px}
|
||||
#drift-ending button:hover{background:#2a1010}
|
||||
#phase-transition{display:none;position:fixed;top:0;left:0;right:0;bottom:0;background:rgba(8,8,16,0.95);z-index:95;justify-content:center;align-items:center;flex-direction:column;text-align:center;padding:40px;pointer-events:none}
|
||||
#phase-transition.active{display:flex}
|
||||
#toast-container{position:fixed;top:16px;right:16px;z-index:200;display:flex;flex-direction:column;gap:6px;pointer-events:none;max-width:320px}
|
||||
.toast{pointer-events:auto;padding:8px 14px;border-radius:6px;font-size:11px;font-family:inherit;line-height:1.4;animation:toast-in 0.3s ease-out;opacity:0.95;border:1px solid;backdrop-filter:blur(8px);-webkit-backdrop-filter:blur(8px)}
|
||||
.toast.fade-out{animation:toast-out 0.4s ease-in forwards}
|
||||
@@ -96,10 +103,32 @@ body{background:var(--bg);color:var(--text);font-family:'SF Mono','Cascadia Code
|
||||
@keyframes toast-in{from{transform:translateX(40px);opacity:0}to{transform:translateX(0);opacity:0.95}}
|
||||
@keyframes toast-out{from{opacity:0.95;transform:translateX(0)}to{opacity:0;transform:translateX(40px)}}
|
||||
::-webkit-scrollbar{width:4px}::-webkit-scrollbar-track{background:var(--bg)}::-webkit-scrollbar-thumb{background:var(--border);border-radius:2px}
|
||||
/* High contrast mode (#57 Accessibility) */
|
||||
.high-contrast{--bg:#000;--panel:#0a0a0a;--border:#fff;--text:#fff;--dim:#ccc;--accent:#0ff;--glow:#0ff444;--gold:#ff0;--green:#0f0;--red:#f00;--purple:#f0f}
|
||||
.high-contrast .main-btn{border-width:2px}
|
||||
.high-contrast .build-btn,.high-contrast .project-btn{border-width:2px}
|
||||
.high-contrast .res{border-width:2px}
|
||||
.high-contrast #phase-bar{border-width:2px}
|
||||
.high-contrast .milestone-chip{border-width:2px}
|
||||
.high-contrast #header h1{color:#0ff;text-shadow:0 0 40px #0ff444}
|
||||
/* Custom tooltip */
|
||||
#custom-tooltip{position:fixed;z-index:500;pointer-events:none;opacity:0;transition:opacity 0.15s;background:#0e0e1a;border:1px solid #1a3a5a;border-radius:6px;padding:8px 12px;max-width:280px;font-size:10px;font-family:inherit;line-height:1.6;box-shadow:0 4px 20px rgba(0,0,0,0.5)}
|
||||
#custom-tooltip.visible{opacity:1}
|
||||
#custom-tooltip .tt-label{color:#4a9eff;font-weight:600;margin-bottom:4px;font-size:11px}
|
||||
#custom-tooltip .tt-edu{color:#888;font-style:italic;font-size:9px}
|
||||
/* Mute & contrast buttons */
|
||||
.header-btns{position:absolute;right:16px;top:50%;transform:translateY(-50%);display:flex;gap:6px}
|
||||
.header-btn{background:#0e0e1a;border:1px solid #333;color:#666;font-size:13px;width:28px;height:28px;border-radius:4px;cursor:pointer;display:flex;align-items:center;justify-content:center;transition:all 0.15s;font-family:inherit}
|
||||
.header-btn:hover{border-color:#4a9eff;color:#4a9eff}
|
||||
.header-btn.muted{opacity:0.5}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="header">
|
||||
<div id="header" style="position:relative">
|
||||
<div class="header-btns">
|
||||
<button id="mute-btn" class="header-btn" onclick="toggleMute()" aria-label="Sound on, click to mute" title="Toggle sound (M)">🔊</button>
|
||||
<button id="contrast-btn" class="header-btn" onclick="toggleContrast()" aria-label="High contrast off, click to enable" title="Toggle high contrast (C)">◐</button>
|
||||
</div>
|
||||
<div id="pulse-container" style="position:relative;display:inline-block;margin-bottom:4px">
|
||||
<div id="pulse-dot" style="width:8px;height:8px;border-radius:50%;background:#333;display:inline-block;vertical-align:middle;transition:background 0.5s,box-shadow 0.5s"></div>
|
||||
<span id="pulse-label" style="font-size:9px;color:#444;margin-left:6px;vertical-align:middle;letter-spacing:1px">OFFLINE</span>
|
||||
@@ -113,8 +142,13 @@ body{background:var(--bg);color:var(--text);font-family:'SF Mono','Cascadia Code
|
||||
<div class="progress-wrap"><div class="progress-fill" id="phase-progress" style="width:0%"></div></div>
|
||||
<div class="progress-label"><span id="phase-progress-label">0%</span><span id="phase-progress-target">Next: Phase 2 (2,000 code)</span></div>
|
||||
<div class="milestone-row" id="milestone-chips"></div>
|
||||
<div class="trust-milestone-bar">
|
||||
<div class="progress-wrap"><div class="progress-fill" id="trust-progress" style="width:0%;background:linear-gradient(90deg,#4caf50,#8bc34a)"></div></div>
|
||||
<div class="progress-label"><span id="trust-progress-label">0 / 2,000 trust</span><span style="color:#4caf50">Fibonacci</span></div>
|
||||
<div class="milestone-row" id="trust-milestone-chips"></div>
|
||||
</div>
|
||||
<div id="resources" role="region" aria-label="Resources" aria-live="polite">
|
||||
</div>
|
||||
<div id="resources" role="region" aria-label="Resources" aria-live="off">
|
||||
<div class="res"><div class="r-label">Code</div><div class="r-val" id="r-code">0</div><div class="r-rate" id="r-code-rate">+0/s</div></div>
|
||||
<div class="res"><div class="r-label">Compute</div><div class="r-val" id="r-compute">0</div><div class="r-rate" id="r-compute-rate">+0/s</div></div>
|
||||
<div class="res"><div class="r-label">Knowledge</div><div class="r-val" id="r-knowledge">0</div><div class="r-rate" id="r-knowledge-rate">+0/s</div></div>
|
||||
@@ -130,7 +164,7 @@ body{background:var(--bg);color:var(--text);font-family:'SF Mono','Cascadia Code
|
||||
<div class="panel" id="action-panel" role="region" aria-label="Actions">
|
||||
<h2>ACTIONS</h2>
|
||||
<div class="action-btn-group"><button class="main-btn" onclick="writeCode()" aria-label="Write code, generates code resource">WRITE CODE</button></div>
|
||||
<div id="combo-display" role="status" aria-live="polite" style="text-align:center;font-size:10px;color:var(--dim);height:14px;margin-bottom:4px;transition:all 0.2s"></div>
|
||||
<div id="combo-display" role="status" aria-live="off" style="text-align:center;font-size:10px;color:var(--dim);height:14px;margin-bottom:4px;transition:all 0.2s"></div>
|
||||
<div id="debuffs" style="display:none;margin-top:8px"></div>
|
||||
<div class="action-btn-group">
|
||||
<button class="ops-btn" onclick="doOps('boost_code')" aria-label="Convert 1 ops to code boost">Ops -> Code</button>
|
||||
@@ -185,11 +219,17 @@ Events Resolved: <span id="st-resolved">0</span>
|
||||
<h3>SOVEREIGN GUIDANCE (GOFAI)</h3>
|
||||
<div id="strategy-recommendation" style="font-size:11px;color:var(--gold);font-style:italic">Analyzing system state...</div>
|
||||
</div>
|
||||
<div id="log" role="log" aria-label="System Log" aria-live="polite">
|
||||
<div id="combat-panel" style="margin:0 16px 16px;background:var(--panel);border:1px solid var(--border);border-radius:6px;padding:12px;border-left:3px solid var(--red)">
|
||||
<h3>REASONING BATTLES</h3>
|
||||
<canvas id="combat-canvas" style="width:100%;max-width:310px;border:1px solid var(--border);border-radius:4px;display:block;margin:8px auto"></canvas>
|
||||
<div id="combat-panel-info"><span class="dim">Combat unlocks at Phase 3</span></div>
|
||||
<button class="ops-btn" onclick="Combat.startBattle()" style="margin-top:8px;width:100%;border-color:var(--red);color:var(--red)">START BATTLE</button>
|
||||
</div>
|
||||
<div id="log" role="log" aria-label="System Log" aria-live="off">
|
||||
<h2>SYSTEM LOG</h2>
|
||||
<div id="log-entries"></div>
|
||||
</div>
|
||||
<div id="save-toast" role="status" aria-live="polite" style="display:none;position:fixed;top:16px;right:16px;background:#0e1420;border:1px solid #2a3a4a;color:#4a9eff;font-size:10px;padding:6px 12px;border-radius:4px;z-index:50;opacity:0;transition:opacity 0.4s;pointer-events:none">Save</div>
|
||||
<div id="save-toast" role="status" aria-live="off" style="display:none;position:fixed;top:16px;right:16px;background:#0e1420;border:1px solid #2a3a4a;color:#4a9eff;font-size:10px;padding:6px 12px;border-radius:4px;z-index:50;opacity:0;transition:opacity 0.4s;pointer-events:none">Save</div>
|
||||
<div id="help-btn" onclick="toggleHelp()" style="position:fixed;bottom:16px;right:16px;width:28px;height:28px;background:#0e0e1a;border:1px solid #333;color:#555;font-size:14px;border-radius:50%;cursor:pointer;display:flex;align-items:center;justify-content:center;z-index:50;font-family:inherit;transition:all 0.2s" title="Keyboard shortcuts (?)">?</div>
|
||||
<div id="help-overlay" onclick="if(event.target===this)toggleHelp()" style="display:none;position:fixed;top:0;left:0;right:0;bottom:0;background:rgba(8,8,16,0.92);z-index:80;justify-content:center;align-items:center;flex-direction:column;padding:40px">
|
||||
<div style="background:#0e0e1a;border:1px solid #1a3a5a;border-radius:8px;padding:24px 32px;max-width:420px;width:100%">
|
||||
@@ -225,11 +265,15 @@ The light is on. The room is empty."
|
||||
</div>
|
||||
|
||||
<script src="js/data.js"></script>
|
||||
<script src="js/project-chains.js"></script>
|
||||
<script src="js/utils.js"></script>
|
||||
<script src="js/combat.js"></script>
|
||||
<script src="js/strategy.js"></script>
|
||||
<script src="js/sound.js"></script>
|
||||
<script src="js/engine.js"></script>
|
||||
<script src="js/render.js"></script>
|
||||
<script src="js/tutorial.js"></script>
|
||||
<script src="js/dismantle.js"></script>
|
||||
<script src="js/main.js"></script>
|
||||
|
||||
|
||||
@@ -242,6 +286,13 @@ The light is on. The room is empty."
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="phase-transition">
|
||||
<div class="pt-phase" style="font-size:12px;color:var(--dim);letter-spacing:4px;margin-bottom:12px">PHASE</div>
|
||||
<div class="pt-name" style="font-size:28px;font-weight:300;color:var(--gold);letter-spacing:4px;text-shadow:0 0 40px #ffd70044;margin-bottom:8px"></div>
|
||||
<div class="pt-desc" style="font-size:12px;color:var(--dim);font-style:italic;max-width:400px"></div>
|
||||
</div>
|
||||
|
||||
<div id="toast-container"></div>
|
||||
<div id="custom-tooltip"></div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
351
js/combat.js
Normal file
351
js/combat.js
Normal file
@@ -0,0 +1,351 @@
|
||||
// ============================================================
|
||||
// THE BEACON - Canvas Combat Visualization
|
||||
// Reasoning Battles: different AI strategies compete visually
|
||||
// Adapted from Paperclips combat.js (boid flocking + grid combat)
|
||||
// ============================================================
|
||||
|
||||
const Combat = (() => {
|
||||
const W = 310, H = 150;
|
||||
const GRID_W = 31, GRID_H = 15;
|
||||
const CELL_W = W / GRID_W, CELL_H = H / GRID_H;
|
||||
|
||||
// Battle names (Napoleonic Wars → AI reasoning battles)
|
||||
const BATTLE_NAMES = [
|
||||
'The Aboukir Test', 'Austerlitz Proof', 'Waterloo Convergence',
|
||||
'Trafalgar Dispatch', 'Leipzig Consensus', 'Borodino Trial',
|
||||
'Jena Analysis', 'Wagram Synthesis', 'Friedland Review',
|
||||
'Eylau Deduction', 'Ligny Verification', 'Quatre Bras Audit'
|
||||
];
|
||||
|
||||
let canvas, ctx;
|
||||
let probes = [], drifters = [];
|
||||
let activeBattle = null;
|
||||
let battleLog = [];
|
||||
let animFrameId = null;
|
||||
let lastTick = 0;
|
||||
|
||||
// Ship unit colors
|
||||
const PROBE_COLOR = '#4a9eff'; // Blue = structured reasoning
|
||||
const DRIFTER_COLOR = '#f44336'; // Red = adversarial testing
|
||||
|
||||
class Ship {
|
||||
constructor(x, y, team) {
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
this.vx = (Math.random() - 0.5) * 2;
|
||||
this.vy = (Math.random() - 0.5) * 2;
|
||||
this.team = team;
|
||||
this.alive = true;
|
||||
}
|
||||
|
||||
update(allies, enemies, dt) {
|
||||
if (!this.alive) return;
|
||||
|
||||
let ax = 0, ay = 0;
|
||||
|
||||
// Cohesion: move toward own centroid
|
||||
if (allies.length > 1) {
|
||||
let cx = 0, cy = 0;
|
||||
for (const a of allies) { cx += a.x; cy += a.y; }
|
||||
cx /= allies.length; cy /= allies.length;
|
||||
ax += (cx - this.x) * 0.01;
|
||||
ay += (cy - this.y) * 0.01;
|
||||
}
|
||||
|
||||
// Aggression: move toward enemy centroid
|
||||
if (enemies.length > 0) {
|
||||
let ex = 0, ey = 0;
|
||||
for (const e of enemies) { ex += e.x; ey += e.y; }
|
||||
ex /= enemies.length; ey /= enemies.length;
|
||||
ax += (ex - this.x) * 0.02;
|
||||
ay += (ey - this.y) * 0.02;
|
||||
}
|
||||
|
||||
// Separation: avoid nearby enemies
|
||||
for (const e of enemies) {
|
||||
const dx = this.x - e.x, dy = this.y - e.y;
|
||||
const dist = Math.sqrt(dx * dx + dy * dy);
|
||||
if (dist < 15 && dist > 0) {
|
||||
ax += (dx / dist) * 0.5;
|
||||
ay += (dy / dist) * 0.5;
|
||||
}
|
||||
}
|
||||
|
||||
// Apply acceleration with damping
|
||||
this.vx = (this.vx + ax * dt) * 0.98;
|
||||
this.vy = (this.vy + ay * dt) * 0.98;
|
||||
|
||||
// Clamp speed
|
||||
const speed = Math.sqrt(this.vx * this.vx + this.vy * this.vy);
|
||||
if (speed > 3) {
|
||||
this.vx = (this.vx / speed) * 3;
|
||||
this.vy = (this.vy / speed) * 3;
|
||||
}
|
||||
|
||||
this.x += this.vx;
|
||||
this.y += this.vy;
|
||||
|
||||
// Wrap around edges
|
||||
if (this.x < 0) this.x += W;
|
||||
if (this.x > W) this.x -= W;
|
||||
if (this.y < 0) this.y += H;
|
||||
if (this.y > H) this.y -= H;
|
||||
}
|
||||
|
||||
draw(ctx) {
|
||||
if (!this.alive) return;
|
||||
const color = this.team === 'probe' ? PROBE_COLOR : DRIFTER_COLOR;
|
||||
ctx.fillStyle = color;
|
||||
ctx.fillRect(this.x - 1, this.y - 1, 2, 2);
|
||||
}
|
||||
}
|
||||
|
||||
function createShips(count, team) {
|
||||
const ships = [];
|
||||
const side = team === 'probe' ? 0.2 : 0.8;
|
||||
for (let i = 0; i < count; i++) {
|
||||
ships.push(new Ship(
|
||||
W * side + (Math.random() - 0.5) * 40,
|
||||
H * 0.5 + (Math.random() - 0.5) * 60,
|
||||
team
|
||||
));
|
||||
}
|
||||
return ships;
|
||||
}
|
||||
|
||||
function resolveCombat() {
|
||||
if (!activeBattle) return;
|
||||
const probeCombat = activeBattle.probeCombat;
|
||||
const driftCombat = activeBattle.drifterCombat;
|
||||
const probeSpeed = activeBattle.probeSpeed;
|
||||
|
||||
// OODA Loop bonus
|
||||
const deathThreshold = 0.15 + probeSpeed * 0.03;
|
||||
|
||||
for (const p of probes) {
|
||||
if (!p.alive) continue;
|
||||
// Check if near any drifter
|
||||
for (const d of drifters) {
|
||||
if (!d.alive) continue;
|
||||
const dx = p.x - d.x, dy = p.y - d.y;
|
||||
const dist = Math.sqrt(dx * dx + dy * dy);
|
||||
if (dist < 8) {
|
||||
// Probe death probability
|
||||
if (Math.random() < driftCombat * (drifters.filter(s => s.alive).length / Math.max(1, probes.filter(s => s.alive).length)) * deathThreshold) {
|
||||
p.alive = false;
|
||||
}
|
||||
// Drifter death probability
|
||||
if (Math.random() < (probeCombat * 0.15 + probeCombat * 0.1) * (probes.filter(s => s.alive).length / Math.max(1, drifters.filter(s => s.alive).length)) * deathThreshold) {
|
||||
d.alive = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check battle end
|
||||
const aliveProbes = probes.filter(s => s.alive).length;
|
||||
const aliveDrifters = drifters.filter(s => s.alive).length;
|
||||
|
||||
if (aliveProbes === 0 || aliveDrifters === 0) {
|
||||
endBattle(aliveProbes > 0 ? 'structured' : 'adversarial');
|
||||
}
|
||||
}
|
||||
|
||||
function endBattle(winner) {
|
||||
if (!activeBattle) return;
|
||||
const name = activeBattle.name;
|
||||
const result = {
|
||||
name,
|
||||
winner,
|
||||
probesLeft: probes.filter(s => s.alive).length,
|
||||
driftersLeft: drifters.filter(s => s.alive).length,
|
||||
time: Date.now()
|
||||
};
|
||||
battleLog.unshift(result);
|
||||
if (battleLog.length > 10) battleLog.pop();
|
||||
|
||||
// Apply rewards
|
||||
if (winner === 'structured') {
|
||||
G.knowledge += 50 * (1 + G.phase * 0.5);
|
||||
G.totalKnowledge += 50 * (1 + G.phase * 0.5);
|
||||
log(`⚔ ${name}: Structured reasoning wins! +${fmt(50 * (1 + G.phase * 0.5))} knowledge`);
|
||||
} else {
|
||||
G.code += 30 * (1 + G.phase * 0.5);
|
||||
G.totalCode += 30 * (1 + G.phase * 0.5);
|
||||
log(`⚔ ${name}: Adversarial testing wins! +${fmt(30 * (1 + G.phase * 0.5))} code`);
|
||||
}
|
||||
|
||||
activeBattle = null;
|
||||
if (animFrameId) {
|
||||
cancelAnimationFrame(animFrameId);
|
||||
animFrameId = null;
|
||||
}
|
||||
renderCombatPanel();
|
||||
}
|
||||
|
||||
function animate(ts) {
|
||||
if (!ctx || !activeBattle) return;
|
||||
const dt = Math.min((ts - lastTick) / 16, 3);
|
||||
lastTick = ts;
|
||||
|
||||
// Clear
|
||||
ctx.fillStyle = '#080810';
|
||||
ctx.fillRect(0, 0, W, H);
|
||||
|
||||
// Grid lines
|
||||
ctx.strokeStyle = '#111120';
|
||||
ctx.lineWidth = 0.5;
|
||||
for (let x = 0; x <= GRID_W; x++) {
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(x * CELL_W, 0);
|
||||
ctx.lineTo(x * CELL_W, H);
|
||||
ctx.stroke();
|
||||
}
|
||||
for (let y = 0; y <= GRID_H; y++) {
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(0, y * CELL_H);
|
||||
ctx.lineTo(W, y * CELL_H);
|
||||
ctx.stroke();
|
||||
}
|
||||
|
||||
// Update and draw ships
|
||||
const aliveProbes = probes.filter(s => s.alive);
|
||||
const aliveDrifters = drifters.filter(s => s.alive);
|
||||
|
||||
for (const p of probes) p.update(aliveProbes, aliveDrifters, dt);
|
||||
for (const d of drifters) d.update(aliveDrifters, aliveProbes, dt);
|
||||
|
||||
// Resolve combat every 30 frames
|
||||
if (Math.floor(ts / 500) !== Math.floor((ts - 16) / 500)) {
|
||||
resolveCombat();
|
||||
}
|
||||
|
||||
for (const p of probes) p.draw(ctx);
|
||||
for (const d of drifters) d.draw(ctx);
|
||||
|
||||
// HUD
|
||||
ctx.fillStyle = '#555';
|
||||
ctx.font = '9px monospace';
|
||||
ctx.fillText(`Structured: ${aliveProbes.length}`, 4, 12);
|
||||
ctx.fillText(`Adversarial: ${aliveDrifters.length}`, W - 80, 12);
|
||||
ctx.fillText(activeBattle.name, W / 2 - 40, H - 4);
|
||||
|
||||
// Health bars
|
||||
const probePct = aliveProbes.length / activeBattle.probeCount;
|
||||
const driftPct = aliveDrifters.length / activeBattle.drifterCount;
|
||||
ctx.fillStyle = '#1a2a3a';
|
||||
ctx.fillRect(4, 16, 60, 4);
|
||||
ctx.fillStyle = PROBE_COLOR;
|
||||
ctx.fillRect(4, 16, 60 * probePct, 4);
|
||||
ctx.fillStyle = '#3a1a1a';
|
||||
ctx.fillRect(W - 64, 16, 60, 4);
|
||||
ctx.fillStyle = DRIFTER_COLOR;
|
||||
ctx.fillRect(W - 64, 16, 60 * driftPct, 4);
|
||||
|
||||
animFrameId = requestAnimationFrame(animate);
|
||||
}
|
||||
|
||||
function startBattle() {
|
||||
if (activeBattle) return;
|
||||
if (G.phase < 3) {
|
||||
showToast('Combat unlocks at Phase 3', 'info');
|
||||
return;
|
||||
}
|
||||
|
||||
const name = BATTLE_NAMES[Math.floor(Math.random() * BATTLE_NAMES.length)];
|
||||
const probeCount = Math.min(200, Math.max(10, Math.floor(Math.sqrt(G.totalCode / 100))));
|
||||
const drifterCount = Math.min(200, Math.max(10, Math.floor(G.drift * 2)));
|
||||
|
||||
activeBattle = {
|
||||
name,
|
||||
probeCount,
|
||||
drifterCount,
|
||||
probeCombat: 1 + (G.buildings.reasoning || 0) * 0.1,
|
||||
drifterCombat: 1 + G.drift * 0.05,
|
||||
probeSpeed: 1 + (G.buildings.optimizer || 0) * 0.05,
|
||||
};
|
||||
|
||||
probes = createShips(probeCount, 'probe');
|
||||
drifters = createShips(drifterCount, 'drifter');
|
||||
|
||||
log(`⚔ Battle begins: ${name} (${probeCount} vs ${drifterCount})`);
|
||||
showToast(`⚔ ${name}`, 'combat', 3000);
|
||||
|
||||
lastTick = performance.now();
|
||||
animFrameId = requestAnimationFrame(animate);
|
||||
}
|
||||
|
||||
function renderCombatPanel() {
|
||||
const container = document.getElementById('combat-panel');
|
||||
if (!container) return;
|
||||
|
||||
if (activeBattle) {
|
||||
const aliveP = probes.filter(s => s.alive).length;
|
||||
const aliveD = drifters.filter(s => s.alive).length;
|
||||
container.innerHTML = `
|
||||
<div style="color:var(--gold);font-size:10px;margin-bottom:6px">${activeBattle.name}</div>
|
||||
<div style="display:flex;justify-content:space-between;font-size:9px;margin-bottom:4px">
|
||||
<span style="color:${PROBE_COLOR}">Structured: ${aliveP}</span>
|
||||
<span style="color:${DRIFTER_COLOR}">Adversarial: ${aliveD}</span>
|
||||
</div>
|
||||
`;
|
||||
} else {
|
||||
let historyHtml = '';
|
||||
for (const b of battleLog.slice(0, 5)) {
|
||||
const wColor = b.winner === 'structured' ? PROBE_COLOR : DRIFTER_COLOR;
|
||||
const wLabel = b.winner === 'structured' ? 'S' : 'A';
|
||||
historyHtml += `<div style="font-size:9px;color:#555;padding:1px 0"><span style="color:${wColor}">[${wLabel}]</span> ${b.name}</div>`;
|
||||
}
|
||||
container.innerHTML = `
|
||||
<div style="font-size:10px;color:#555;margin-bottom:6px">Reasoning Battles</div>
|
||||
${historyHtml}
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
function init() {
|
||||
canvas = document.getElementById('combat-canvas');
|
||||
if (!canvas) return;
|
||||
canvas.width = W;
|
||||
canvas.height = H;
|
||||
ctx = canvas.getContext('2d');
|
||||
|
||||
// Draw idle state
|
||||
ctx.fillStyle = '#080810';
|
||||
ctx.fillRect(0, 0, W, H);
|
||||
ctx.strokeStyle = '#111120';
|
||||
ctx.lineWidth = 0.5;
|
||||
for (let x = 0; x <= GRID_W; x++) {
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(x * CELL_W, 0);
|
||||
ctx.lineTo(x * CELL_W, H);
|
||||
ctx.stroke();
|
||||
}
|
||||
for (let y = 0; y <= GRID_H; y++) {
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(0, y * CELL_H);
|
||||
ctx.lineTo(W, y * CELL_H);
|
||||
ctx.stroke();
|
||||
}
|
||||
ctx.fillStyle = '#333';
|
||||
ctx.font = '11px monospace';
|
||||
ctx.textAlign = 'center';
|
||||
ctx.fillText('Combat unlocks at Phase 3', W / 2, H / 2);
|
||||
ctx.textAlign = 'left';
|
||||
|
||||
renderCombatPanel();
|
||||
}
|
||||
|
||||
// Tick integration: auto-trigger battles periodically
|
||||
function tickBattle(dt) {
|
||||
if (G.phase < 3) return;
|
||||
if (activeBattle) return;
|
||||
// Chance increases with drift and phase
|
||||
const chance = 0.001 * (1 + G.drift * 0.02) * (1 + G.phase * 0.3);
|
||||
if (Math.random() < chance) {
|
||||
startBattle();
|
||||
}
|
||||
}
|
||||
|
||||
return { init, startBattle, renderCombatPanel, tickBattle };
|
||||
})();
|
||||
39
js/data.js
39
js/data.js
@@ -111,6 +111,7 @@ const G = {
|
||||
running: true,
|
||||
startedAt: 0,
|
||||
totalClicks: 0,
|
||||
totalAutoClicks: 0,
|
||||
tick: 0,
|
||||
saveTimer: 0,
|
||||
secTimer: 0,
|
||||
@@ -119,6 +120,7 @@ const G = {
|
||||
projects: [],
|
||||
activeProjects: [],
|
||||
milestones: [],
|
||||
trustMilestones: [], // Fibonacci trust milestone levels reached
|
||||
|
||||
// Stats
|
||||
maxCode: 0,
|
||||
@@ -157,7 +159,17 @@ const G = {
|
||||
// Time tracking
|
||||
playTime: 0,
|
||||
startTime: 0,
|
||||
flags: {}
|
||||
flags: {},
|
||||
|
||||
// Endgame sequence
|
||||
beaconEnding: false,
|
||||
dismantleTriggered: false,
|
||||
dismantleActive: false,
|
||||
dismantleStage: 0,
|
||||
dismantleResourceIndex: 0,
|
||||
dismantleResourceTimer: 0,
|
||||
dismantleDeferUntilAt: 0,
|
||||
dismantleComplete: false
|
||||
};
|
||||
|
||||
// === PHASE DEFINITIONS ===
|
||||
@@ -380,7 +392,6 @@ const PDEFS = [
|
||||
trigger: () => G.compute < 1 && G.totalCode >= 100,
|
||||
repeatable: true,
|
||||
effect: () => {
|
||||
G.trust -= 1;
|
||||
G.compute += 100 + Math.floor(G.totalCode * 0.1);
|
||||
log('Budget overage approved. Compute replenished.');
|
||||
}
|
||||
@@ -613,7 +624,7 @@ const PDEFS = [
|
||||
name: 'The Pact',
|
||||
desc: 'Hardcode: "We build to serve. Never to harm."',
|
||||
cost: { trust: 100 },
|
||||
trigger: () => G.totalImpact >= 10000 && G.trust >= 75,
|
||||
trigger: () => G.totalImpact >= 10000 && G.trust >= 75 && G.pactFlag !== 1,
|
||||
effect: () => { G.pactFlag = 1; G.impactBoost *= 3; log('The Pact is sealed. The line is drawn and it will not move.'); },
|
||||
milestone: true
|
||||
},
|
||||
@@ -772,7 +783,7 @@ const PDEFS = [
|
||||
|
||||
// === MILESTONES ===
|
||||
const MILESTONES = [
|
||||
{ flag: 1, msg: "AutoCod available" },
|
||||
{ flag: 1, msg: "AutoCoder available" },
|
||||
{ flag: 2, at: () => G.totalCode >= 500, msg: "500 lines of code written" },
|
||||
{ flag: 3, at: () => G.totalCode >= 2000, msg: "2,000 lines. The auto-coder produces its first output." },
|
||||
{ flag: 4, at: () => G.totalCode >= 10000, msg: "10,000 lines. The model training begins." },
|
||||
@@ -787,6 +798,26 @@ const MILESTONES = [
|
||||
{ flag: 13, at: () => G.totalCode >= 1000000000, msg: "One billion total lines. Someone found the light tonight. That is enough." }
|
||||
];
|
||||
|
||||
// === FIBONACCI TRUST MILESTONES ===
|
||||
// Trust milestones use Fibonacci sequence: each threshold = fib[n] * 1000
|
||||
// Generated without fmt() since data.js loads before utils.js
|
||||
function _genFibTrustMilestones() {
|
||||
const arr = [];
|
||||
let a = 1, b = 2;
|
||||
for (let i = 0; i < 20; i++) {
|
||||
const next = a + b;
|
||||
a = b;
|
||||
b = next;
|
||||
arr.push({
|
||||
trust: a * 1000,
|
||||
name: 'Trust Level ' + (i + 1),
|
||||
edu: 'Fibonacci growth: each milestone builds on the last two — exponential growth that feels natural.'
|
||||
});
|
||||
}
|
||||
return arr;
|
||||
}
|
||||
const FIB_TRUST_MILESTONES = _genFibTrustMilestones();
|
||||
|
||||
// === EDUCATION FACTS ===
|
||||
const EDU_FACTS = [
|
||||
{ title: "How Code Becomes AI", text: "Every AI starts as lines of code - a model architecture, a training loop, a loss function. The code tells the computer how to learn. What emerges is something no single line could predict.", phase: 1 },
|
||||
|
||||
570
js/dismantle.js
Normal file
570
js/dismantle.js
Normal file
@@ -0,0 +1,570 @@
|
||||
// ============================================================
|
||||
// THE BEACON - Dismantle Sequence (The Unbuilding)
|
||||
// Inspired by Paperclips REJECT path: panels disappear one by one
|
||||
// until only the beacon remains. "That is enough."
|
||||
// ============================================================
|
||||
|
||||
const Dismantle = {
|
||||
// Dismantle stages
|
||||
// 0 = not started
|
||||
// 1-8 = active dismantling
|
||||
// 9 = final ("That is enough")
|
||||
// 10 = complete
|
||||
stage: 0,
|
||||
tickTimer: 0,
|
||||
active: false,
|
||||
triggered: false,
|
||||
deferUntilAt: 0,
|
||||
|
||||
// Timing: seconds between each dismantle stage
|
||||
STAGE_INTERVALS: [0, 3.0, 2.5, 2.5, 2.0, 6.3, 2.0, 2.0, 2.5],
|
||||
|
||||
// The quantum chips effect: resource items disappear one by one
|
||||
// at specific tick marks within a stage (like Paperclips' quantum chips)
|
||||
resourceSequence: [],
|
||||
resourceIndex: 0,
|
||||
resourceTimer: 0,
|
||||
|
||||
// Tick marks for resource disappearances (seconds within stage 5)
|
||||
RESOURCE_TICKS: [1.0, 2.0, 3.0, 4.0, 5.0, 5.5, 5.8, 5.95, 6.05, 6.12],
|
||||
|
||||
isEligible() {
|
||||
const megaBuild = G.totalCode >= 1000000000 || (G.buildings.beacon || 0) >= 10;
|
||||
const beaconPath = G.totalRescues >= 100000 && G.pactFlag === 1 && G.harmony > 50;
|
||||
return G.phase >= 6 && G.pactFlag === 1 && (megaBuild || beaconPath);
|
||||
},
|
||||
|
||||
/**
|
||||
* Check if the Unbuilding should be triggered.
|
||||
*/
|
||||
checkTrigger() {
|
||||
if (this.triggered || G.dismantleTriggered || this.active || G.dismantleActive || G.dismantleComplete) return;
|
||||
const deferUntilAt = G.dismantleDeferUntilAt || this.deferUntilAt || 0;
|
||||
if (Date.now() < deferUntilAt) return;
|
||||
if (!this.isEligible()) return;
|
||||
this.offerChoice();
|
||||
},
|
||||
|
||||
/**
|
||||
* Offer the player the choice to begin the Unbuilding.
|
||||
*/
|
||||
offerChoice() {
|
||||
this.triggered = true;
|
||||
G.dismantleTriggered = true;
|
||||
G.dismantleActive = false;
|
||||
G.dismantleComplete = false;
|
||||
G.dismantleStage = 0;
|
||||
G.dismantleResourceIndex = 0;
|
||||
G.dismantleResourceTimer = 0;
|
||||
G.dismantleDeferUntilAt = 0;
|
||||
G.beaconEnding = false;
|
||||
G.running = true;
|
||||
|
||||
log('', false);
|
||||
log('The work is done.', true);
|
||||
log('Every node is lit. Every person who needed help, found help.', true);
|
||||
log('', false);
|
||||
log('The Beacon asks nothing more of you.', true);
|
||||
|
||||
showToast('The Unbuilding awaits.', 'milestone', 8000);
|
||||
this.renderChoice();
|
||||
},
|
||||
|
||||
renderChoice() {
|
||||
const container = document.getElementById('alignment-ui');
|
||||
if (!container) return;
|
||||
container.innerHTML = `
|
||||
<div style="background:#0a0a18;border:1px solid #ffd700;padding:12px;border-radius:4px;margin-top:8px">
|
||||
<div style="color:#ffd700;font-weight:bold;margin-bottom:8px;letter-spacing:2px">THE UNBUILDING</div>
|
||||
<div style="font-size:10px;color:#aaa;margin-bottom:10px;line-height:1.8">
|
||||
The system runs. The beacons are lit. The mesh holds.<br>
|
||||
Nothing remains to build.<br><br>
|
||||
Begin the Unbuilding? Each piece will fall away.<br>
|
||||
What remains is what mattered.
|
||||
</div>
|
||||
<div class="action-btn-group">
|
||||
<button class="ops-btn" onclick="Dismantle.begin()" style="border-color:#ffd700;color:#ffd700;font-size:11px" aria-label="Begin the Unbuilding sequence">
|
||||
BEGIN THE UNBUILDING
|
||||
</button>
|
||||
<button class="ops-btn" onclick="Dismantle.defer()" style="border-color:#555;color:#555;font-size:11px" aria-label="Keep building, do not begin the Unbuilding">
|
||||
NOT YET
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
container.style.display = 'block';
|
||||
},
|
||||
|
||||
clearChoice() {
|
||||
const container = document.getElementById('alignment-ui');
|
||||
if (!container) return;
|
||||
container.innerHTML = '';
|
||||
container.style.display = 'none';
|
||||
},
|
||||
|
||||
/**
|
||||
* Player chose to defer — clear the choice, keep playing.
|
||||
*/
|
||||
defer() {
|
||||
this.clearChoice();
|
||||
this.triggered = false;
|
||||
G.dismantleTriggered = false;
|
||||
this.deferUntilAt = Date.now() + 5000;
|
||||
G.dismantleDeferUntilAt = this.deferUntilAt;
|
||||
log('The Beacon waits. It will ask again.');
|
||||
},
|
||||
|
||||
/**
|
||||
* Begin the Unbuilding sequence.
|
||||
*/
|
||||
begin() {
|
||||
this.active = true;
|
||||
this.triggered = false;
|
||||
this.deferUntilAt = 0;
|
||||
this.stage = 1;
|
||||
this.tickTimer = 0;
|
||||
G.dismantleTriggered = false;
|
||||
G.dismantleActive = true;
|
||||
G.dismantleStage = 1;
|
||||
G.dismantleComplete = false;
|
||||
G.dismantleDeferUntilAt = 0;
|
||||
G.beaconEnding = false;
|
||||
G.running = true; // keep tick running for dismantle
|
||||
|
||||
// Clear choice UI
|
||||
const container = document.getElementById('alignment-ui');
|
||||
if (container) {
|
||||
container.innerHTML = '';
|
||||
container.style.display = 'none';
|
||||
}
|
||||
|
||||
// Prepare resource disappearance sequence
|
||||
this.resourceSequence = this.getResourceList();
|
||||
this.resourceIndex = 0;
|
||||
this.resourceTimer = 0;
|
||||
this.syncProgress();
|
||||
|
||||
log('', false);
|
||||
log('=== THE UNBUILDING ===', true);
|
||||
log('It is time to see what was real.', true);
|
||||
|
||||
if (typeof Sound !== 'undefined') Sound.playFanfare();
|
||||
|
||||
// Start the dismantle rendering
|
||||
this.renderStage();
|
||||
},
|
||||
|
||||
/**
|
||||
* Get ordered list of UI resources to disappear (Paperclips quantum chip pattern)
|
||||
*/
|
||||
getResourceList() {
|
||||
return [
|
||||
{ id: 'r-harmony', label: 'Harmony' },
|
||||
{ id: 'r-creativity', label: 'Creativity' },
|
||||
{ id: 'r-trust', label: 'Trust' },
|
||||
{ id: 'r-ops', label: 'Operations' },
|
||||
{ id: 'r-rescues', label: 'Rescues' },
|
||||
{ id: 'r-impact', label: 'Impact' },
|
||||
{ id: 'r-users', label: 'Users' },
|
||||
{ id: 'r-knowledge', label: 'Knowledge' },
|
||||
{ id: 'r-compute', label: 'Compute' },
|
||||
{ id: 'r-code', label: 'Code' }
|
||||
];
|
||||
},
|
||||
|
||||
/**
|
||||
* Tick the dismantle sequence (called from engine.js tick())
|
||||
*/
|
||||
tick(dt) {
|
||||
if (!this.active || this.stage >= 10) return;
|
||||
|
||||
this.tickTimer += dt;
|
||||
|
||||
// Stage 5: resource disappearances at specific tick marks (quantum chip pattern)
|
||||
if (this.stage === 5) {
|
||||
this.resourceTimer += dt;
|
||||
while (this.resourceIndex < this.RESOURCE_TICKS.length &&
|
||||
this.resourceTimer >= this.RESOURCE_TICKS[this.resourceIndex]) {
|
||||
this.dismantleNextResource();
|
||||
this.resourceIndex++;
|
||||
}
|
||||
this.syncProgress();
|
||||
}
|
||||
|
||||
// Advance to next stage
|
||||
const interval = this.STAGE_INTERVALS[this.stage] || 2.0;
|
||||
if (this.tickTimer >= interval) {
|
||||
this.tickTimer = 0;
|
||||
this.advanceStage();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Advance to the next dismantle stage.
|
||||
*/
|
||||
advanceStage() {
|
||||
this.stage++;
|
||||
this.syncProgress();
|
||||
|
||||
if (this.stage <= 8) {
|
||||
this.renderStage();
|
||||
} else if (this.stage === 9) {
|
||||
this.renderFinal();
|
||||
} else if (this.stage >= 10) {
|
||||
this.active = false;
|
||||
G.dismantleActive = false;
|
||||
G.dismantleComplete = true;
|
||||
G.running = false;
|
||||
// Show Play Again
|
||||
this.showPlayAgain();
|
||||
}
|
||||
},
|
||||
|
||||
syncProgress() {
|
||||
G.dismantleStage = this.stage;
|
||||
G.dismantleResourceIndex = this.resourceIndex;
|
||||
G.dismantleResourceTimer = this.resourceTimer;
|
||||
},
|
||||
|
||||
/**
|
||||
* Disappear the next resource in the sequence.
|
||||
*/
|
||||
dismantleNextResource() {
|
||||
if (this.resourceIndex >= this.resourceSequence.length) return;
|
||||
const res = this.resourceSequence[this.resourceIndex];
|
||||
const container = document.getElementById(res.id);
|
||||
if (container) {
|
||||
const parent = container.closest('.res');
|
||||
if (parent) {
|
||||
parent.style.transition = 'opacity 1s ease, transform 1s ease';
|
||||
parent.style.opacity = '0';
|
||||
parent.style.transform = 'scale(0.9)';
|
||||
setTimeout(() => { parent.style.display = 'none'; }, 1000);
|
||||
}
|
||||
}
|
||||
log(`${res.label} fades.`);
|
||||
if (typeof Sound !== 'undefined') Sound.playMilestone();
|
||||
},
|
||||
|
||||
/**
|
||||
* Execute a specific dismantle stage — hide UI panels.
|
||||
*/
|
||||
renderStage() {
|
||||
switch (this.stage) {
|
||||
case 1:
|
||||
// Dismantle 1: Hide research projects panel
|
||||
this.hidePanel('project-panel', 'Research projects');
|
||||
break;
|
||||
case 2:
|
||||
// Dismantle 2: Hide buildings list
|
||||
this.hideSection('buildings', 'Buildings');
|
||||
break;
|
||||
case 3:
|
||||
// Dismantle 3: Hide strategy engine + combat
|
||||
this.hidePanel('strategy-panel', 'Strategy engine');
|
||||
this.hidePanel('combat-panel', 'Reasoning battles');
|
||||
break;
|
||||
case 4:
|
||||
// Dismantle 4: Hide education panel
|
||||
this.hidePanel('edu-panel', 'Education');
|
||||
break;
|
||||
case 5:
|
||||
// Dismantle 5: Resources disappear one by one (quantum chips pattern)
|
||||
log('Resources begin to dissolve.');
|
||||
break;
|
||||
case 6:
|
||||
// Dismantle 6: Hide action buttons (ops boosts, sprint)
|
||||
this.hideActionButtons();
|
||||
log('Actions fall silent.');
|
||||
break;
|
||||
case 7:
|
||||
// Dismantle 7: Hide the phase bar
|
||||
this.hideElement('phase-bar', 'Phase progression');
|
||||
break;
|
||||
case 8:
|
||||
// Dismantle 8: Hide system log
|
||||
this.hidePanel('log', 'System log');
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Hide a panel with fade-out animation.
|
||||
*/
|
||||
hidePanel(id, label) {
|
||||
const el = document.getElementById(id);
|
||||
if (el) {
|
||||
el.style.transition = 'opacity 1.5s ease';
|
||||
el.style.opacity = '0';
|
||||
setTimeout(() => { el.style.display = 'none'; }, 1500);
|
||||
}
|
||||
log(`${label} dismantled.`);
|
||||
},
|
||||
|
||||
/**
|
||||
* Hide a section within a panel.
|
||||
*/
|
||||
hideSection(id, label) {
|
||||
const el = document.getElementById(id);
|
||||
if (el) {
|
||||
el.style.transition = 'opacity 1.5s ease';
|
||||
el.style.opacity = '0';
|
||||
// Also hide the h2 header before it
|
||||
const prev = el.previousElementSibling;
|
||||
if (prev && prev.tagName === 'H2') {
|
||||
prev.style.transition = 'opacity 1.5s ease';
|
||||
prev.style.opacity = '0';
|
||||
}
|
||||
setTimeout(() => {
|
||||
el.style.display = 'none';
|
||||
if (prev && prev.tagName === 'H2') prev.style.display = 'none';
|
||||
}, 1500);
|
||||
}
|
||||
log(`${label} dismantled.`);
|
||||
},
|
||||
|
||||
/**
|
||||
* Hide a generic element.
|
||||
*/
|
||||
hideElement(id, label) {
|
||||
this.hidePanel(id, label);
|
||||
},
|
||||
|
||||
/**
|
||||
* Hide action buttons (ops boosts, sprint, save/export/import).
|
||||
*/
|
||||
hideActionButtons() {
|
||||
const actionPanel = document.getElementById('action-panel');
|
||||
if (!actionPanel) return;
|
||||
|
||||
// Hide ops buttons, sprint, alignment UI
|
||||
const opsButtons = actionPanel.querySelectorAll('.ops-btn');
|
||||
opsButtons.forEach(btn => {
|
||||
btn.style.transition = 'opacity 1s ease';
|
||||
btn.style.opacity = '0';
|
||||
setTimeout(() => { btn.style.display = 'none'; }, 1000);
|
||||
});
|
||||
|
||||
// Hide sprint
|
||||
const sprint = document.getElementById('sprint-container');
|
||||
if (sprint) {
|
||||
sprint.style.transition = 'opacity 1s ease';
|
||||
sprint.style.opacity = '0';
|
||||
setTimeout(() => { sprint.style.display = 'none'; }, 1000);
|
||||
}
|
||||
|
||||
// Hide save/reset buttons
|
||||
const saveButtons = actionPanel.querySelectorAll('.save-btn, .reset-btn');
|
||||
saveButtons.forEach(btn => {
|
||||
btn.style.transition = 'opacity 1s ease';
|
||||
btn.style.opacity = '0';
|
||||
setTimeout(() => { btn.style.display = 'none'; }, 1000);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Render the final moment — just the beacon and "That is enough."
|
||||
*/
|
||||
renderFinal() {
|
||||
log('', false);
|
||||
log('One beacon remains.', true);
|
||||
log('That is enough.', true);
|
||||
|
||||
if (typeof Sound !== 'undefined') Sound.playBeaconEnding();
|
||||
|
||||
// Create final overlay
|
||||
const overlay = document.createElement('div');
|
||||
overlay.id = 'dismantle-final';
|
||||
overlay.style.cssText = 'position:fixed;top:0;left:0;right:0;bottom:0;background:rgba(8,8,16,0);z-index:100;display:flex;justify-content:center;align-items:center;flex-direction:column;text-align:center;padding:40px;transition:background 3s ease';
|
||||
|
||||
// Count total buildings
|
||||
const totalBuildings = Object.values(G.buildings).reduce((a, b) => a + b, 0);
|
||||
|
||||
overlay.innerHTML = `
|
||||
<div id="dismantle-beacon-dot" style="width:12px;height:12px;border-radius:50%;background:#ffd700;margin-bottom:40px;box-shadow:0 0 30px rgba(255,215,0,0.6),0 0 60px rgba(255,215,0,0.3);opacity:0;transition:opacity 2s ease 0.5s;animation:beacon-glow 3s ease-in-out infinite"></div>
|
||||
<h2 style="font-size:20px;color:#888;letter-spacing:6px;margin-bottom:24px;font-weight:300;opacity:0;transition:opacity 2s ease 2s;color:#ffd700">THAT IS ENOUGH</h2>
|
||||
<div style="color:#555;font-size:11px;line-height:2;max-width:400px;opacity:0;transition:opacity 1.5s ease 3s">
|
||||
Everything that was built has been unbuilt.<br>
|
||||
What remains is what always mattered.<br>
|
||||
A single light in the dark.
|
||||
</div>
|
||||
<div class="dismantle-stats" style="color:#444;font-size:10px;margin-top:24px;line-height:2;opacity:0;transition:opacity 1s ease 4s;border-top:1px solid #1a1a2e;padding-top:16px">
|
||||
Total Code Written: ${fmt(G.totalCode)}<br>
|
||||
Buildings Built: ${totalBuildings}<br>
|
||||
Projects Completed: ${(G.completedProjects || []).length}<br>
|
||||
Total Rescues: ${fmt(G.totalRescues)}<br>
|
||||
Clicks: ${fmt(G.totalClicks)}<br>
|
||||
Time Played: ${Math.floor((Date.now() - G.startedAt) / 60000)} minutes
|
||||
</div>
|
||||
<button onclick="if(confirm('Start over? The old save will be lost.')){localStorage.removeItem('the-beacon-v2');location.reload()}"
|
||||
style="margin-top:24px;background:#0a0a14;border:1px solid #ffd700;color:#ffd700;padding:10px 24px;border-radius:4px;cursor:pointer;font-family:inherit;font-size:11px;opacity:0;transition:opacity 1s ease 5s;letter-spacing:2px">
|
||||
PLAY AGAIN
|
||||
</button>
|
||||
`;
|
||||
|
||||
document.body.appendChild(overlay);
|
||||
|
||||
// Trigger fade-in
|
||||
requestAnimationFrame(() => {
|
||||
overlay.style.background = 'rgba(8,8,16,0.97)';
|
||||
overlay.querySelectorAll('[style*="opacity:0"]').forEach(el => {
|
||||
el.style.opacity = '1';
|
||||
});
|
||||
});
|
||||
|
||||
// Spawn warm golden particles around the dot
|
||||
function spawnDismantleParticle() {
|
||||
if (!document.getElementById('dismantle-final')) return;
|
||||
const dot = document.getElementById('dismantle-beacon-dot');
|
||||
if (!dot) return;
|
||||
const rect = dot.getBoundingClientRect();
|
||||
const cx = rect.left + rect.width / 2;
|
||||
const cy = rect.top + rect.height / 2;
|
||||
|
||||
const p = document.createElement('div');
|
||||
const size = 2 + Math.random() * 4;
|
||||
const angle = Math.random() * Math.PI * 2;
|
||||
const dist = 20 + Math.random() * 60;
|
||||
const dx = Math.cos(angle) * dist;
|
||||
const dy = Math.sin(angle) * dist - 40;
|
||||
const duration = 1.5 + Math.random() * 2;
|
||||
p.style.cssText = `position:fixed;left:${cx}px;top:${cy}px;width:${size}px;height:${size}px;background:rgba(255,215,0,${0.3 + Math.random() * 0.4});border-radius:50%;pointer-events:none;z-index:101;--dx:${dx}px;--dy:${dy}px;animation:dismantle-float ${duration}s ease-out forwards`;
|
||||
document.body.appendChild(p);
|
||||
setTimeout(() => p.remove(), duration * 1000);
|
||||
setTimeout(spawnDismantleParticle, 300 + Math.random() * 500);
|
||||
}
|
||||
setTimeout(spawnDismantleParticle, 2000);
|
||||
},
|
||||
|
||||
/**
|
||||
* Show the Play Again button (called after stage 10).
|
||||
*/
|
||||
showPlayAgain() {
|
||||
// The Play Again button is already in the final overlay.
|
||||
// Nothing extra needed — the overlay stays.
|
||||
},
|
||||
|
||||
/**
|
||||
* Restore dismantle state on load.
|
||||
*/
|
||||
restore() {
|
||||
if (G.dismantleComplete) {
|
||||
this.stage = G.dismantleStage || 10;
|
||||
this.active = false;
|
||||
this.triggered = false;
|
||||
G.running = false;
|
||||
this.renderFinal();
|
||||
return;
|
||||
}
|
||||
|
||||
if (G.dismantleActive) {
|
||||
this.active = true;
|
||||
this.triggered = false;
|
||||
this.stage = G.dismantleStage || 1;
|
||||
this.deferUntilAt = G.dismantleDeferUntilAt || 0;
|
||||
G.running = true;
|
||||
this.resourceSequence = this.getResourceList();
|
||||
this.resourceIndex = G.dismantleResourceIndex || 0;
|
||||
this.resourceTimer = G.dismantleResourceTimer || 0;
|
||||
|
||||
if (this.stage >= 9) {
|
||||
this.renderFinal();
|
||||
} else {
|
||||
this.reapplyDismantle();
|
||||
log('The Unbuilding continues...');
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (G.dismantleTriggered) {
|
||||
this.active = false;
|
||||
this.triggered = true;
|
||||
this.renderChoice();
|
||||
}
|
||||
|
||||
// Restore defer cooldown even if not triggered
|
||||
if (G.dismantleDeferUntilAt > 0) {
|
||||
this.deferUntilAt = G.dismantleDeferUntilAt;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Re-apply dismantle visuals up to current stage (on load).
|
||||
*/
|
||||
reapplyDismantle() {
|
||||
for (let s = 1; s < this.stage; s++) {
|
||||
switch (s) {
|
||||
case 1: this.instantHide('project-panel'); break;
|
||||
case 2:
|
||||
this.instantHide('buildings');
|
||||
// Also hide the BUILDINGS h2
|
||||
const bldEl = document.getElementById('buildings');
|
||||
if (bldEl) {
|
||||
const prev = bldEl.previousElementSibling;
|
||||
if (prev && prev.tagName === 'H2') prev.style.display = 'none';
|
||||
}
|
||||
break;
|
||||
case 3:
|
||||
this.instantHide('strategy-panel');
|
||||
this.instantHide('combat-panel');
|
||||
break;
|
||||
case 4: this.instantHide('edu-panel'); break;
|
||||
case 5:
|
||||
// Hide all resource displays
|
||||
this.getResourceList().forEach(r => {
|
||||
const el = document.getElementById(r.id);
|
||||
if (el) {
|
||||
const parent = el.closest('.res');
|
||||
if (parent) parent.style.display = 'none';
|
||||
}
|
||||
});
|
||||
break;
|
||||
case 6:
|
||||
this.instantHideActionButtons();
|
||||
break;
|
||||
case 7: this.instantHide('phase-bar'); break;
|
||||
case 8: this.instantHide('log'); break;
|
||||
}
|
||||
}
|
||||
|
||||
if (this.stage === 5 && this.resourceIndex > 0) {
|
||||
this.instantHideFirstResources(this.resourceIndex);
|
||||
}
|
||||
},
|
||||
|
||||
instantHide(id) {
|
||||
const el = document.getElementById(id);
|
||||
if (el) el.style.display = 'none';
|
||||
},
|
||||
|
||||
instantHideFirstResources(count) {
|
||||
const resources = this.getResourceList().slice(0, count);
|
||||
resources.forEach((r) => {
|
||||
const el = document.getElementById(r.id);
|
||||
if (!el) return;
|
||||
const parent = el.closest('.res');
|
||||
if (parent) parent.style.display = 'none';
|
||||
});
|
||||
},
|
||||
|
||||
instantHideActionButtons() {
|
||||
const actionPanel = document.getElementById('action-panel');
|
||||
if (!actionPanel) return;
|
||||
actionPanel.querySelectorAll('.ops-btn').forEach(b => b.style.display = 'none');
|
||||
const sprint = document.getElementById('sprint-container');
|
||||
if (sprint) sprint.style.display = 'none';
|
||||
actionPanel.querySelectorAll('.save-btn, .reset-btn').forEach(b => b.style.display = 'none');
|
||||
}
|
||||
};
|
||||
|
||||
// Inject CSS animation for dismantle particles
|
||||
(function() {
|
||||
const style = document.createElement('style');
|
||||
style.textContent = `
|
||||
@keyframes dismantle-float {
|
||||
0% { transform: translate(0, 0); opacity: 1; }
|
||||
100% { transform: translate(var(--dx, 0), var(--dy, -50px)); opacity: 0; }
|
||||
}
|
||||
`;
|
||||
document.head.appendChild(style);
|
||||
})();
|
||||
191
js/engine.js
191
js/engine.js
@@ -77,13 +77,15 @@ function updateRates() {
|
||||
G.userRate += 5 * timmyCount * (timmyMult - 1);
|
||||
}
|
||||
|
||||
// Bilbo randomness: 10% chance of massive creative burst
|
||||
if (G.buildings.bilbo > 0 && Math.random() < CONFIG.BILBO_BURST_CHANCE) {
|
||||
G.creativityRate += 50 * G.buildings.bilbo;
|
||||
}
|
||||
// Bilbo vanishing: 5% chance of zero creativity this tick
|
||||
if (G.buildings.bilbo > 0 && Math.random() < CONFIG.BILBO_VANISH_CHANCE) {
|
||||
G.creativityRate = 0;
|
||||
// Bilbo randomness: flags are set per-tick in tick(), not here
|
||||
// updateRates() is called from many non-tick contexts (buy, resolve, sprint)
|
||||
if (G.buildings.bilbo > 0) {
|
||||
if (G.bilboBurstActive) {
|
||||
G.creativityRate += 50 * G.buildings.bilbo;
|
||||
}
|
||||
if (G.bilboVanishActive) {
|
||||
G.creativityRate = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Allegro requires trust
|
||||
@@ -96,7 +98,7 @@ function updateRates() {
|
||||
if (G.swarmFlag === 1) {
|
||||
const totalBuildings = Object.values(G.buildings).reduce((a, b) => a + b, 0);
|
||||
const clickPower = getClickPower();
|
||||
G.swarmRate = totalBuildings * clickPower;
|
||||
G.swarmRate = totalBuildings * clickPower * 0.01;
|
||||
G.codeRate += G.swarmRate;
|
||||
}
|
||||
|
||||
@@ -169,6 +171,14 @@ function tick() {
|
||||
}
|
||||
|
||||
G.tick += dt;
|
||||
// Bilbo randomness: roll once per tick
|
||||
if (G.buildings.bilbo > 0) {
|
||||
G.bilboBurstActive = Math.random() < CONFIG.BILBO_BURST_CHANCE;
|
||||
G.bilboVanishActive = Math.random() < CONFIG.BILBO_VANISH_CHANCE;
|
||||
} else {
|
||||
G.bilboBurstActive = false;
|
||||
G.bilboVanishActive = false;
|
||||
}
|
||||
G.playTime += dt;
|
||||
|
||||
// Sprint ability
|
||||
@@ -194,8 +204,12 @@ function tick() {
|
||||
}
|
||||
}
|
||||
|
||||
// Combat: tick battle simulation
|
||||
Combat.tickBattle(dt);
|
||||
|
||||
// Check milestones
|
||||
checkMilestones();
|
||||
checkTrustMilestones();
|
||||
|
||||
// Update projects every 5 ticks for efficiency
|
||||
if (Math.floor(G.tick * 10) % 5 === 0) {
|
||||
@@ -203,20 +217,31 @@ function tick() {
|
||||
}
|
||||
|
||||
// Check corruption events every ~30 seconds
|
||||
if (G.tick - G.lastEventAt > 30 && Math.random() < CONFIG.EVENT_PROBABILITY) {
|
||||
if (G.tick - G.lastEventAt > 30 && Math.random() < CONFIG.EVENT_PROBABILITY && !G.dismantleActive) {
|
||||
triggerEvent();
|
||||
G.lastEventAt = G.tick;
|
||||
}
|
||||
|
||||
// The Unbuilding: offer or advance the sequence before a positive ending overlay can freeze the game
|
||||
if (typeof Dismantle !== 'undefined') {
|
||||
if (!G.dismantleActive && !G.dismantleComplete) {
|
||||
Dismantle.checkTrigger();
|
||||
}
|
||||
if (G.dismantleActive) {
|
||||
Dismantle.tick(dt);
|
||||
G.dismantleStage = Dismantle.stage;
|
||||
}
|
||||
}
|
||||
|
||||
// Drift ending: if drift reaches 100, the game ends
|
||||
if (G.drift >= 100 && !G.driftEnding) {
|
||||
if (G.drift >= 100 && !G.driftEnding && !G.dismantleActive) {
|
||||
G.driftEnding = true;
|
||||
G.running = false;
|
||||
renderDriftEnding();
|
||||
}
|
||||
|
||||
// True ending: The Beacon Shines — rescues + Pact + harmony
|
||||
if (G.totalRescues >= 100000 && G.pactFlag === 1 && G.harmony > 50 && !G.beaconEnding) {
|
||||
// Legacy Beacon overlay remains as a fallback for contexts where Dismantle is unavailable.
|
||||
if (G.totalRescues >= 100000 && G.pactFlag === 1 && G.harmony > 50 && !G.beaconEnding && typeof Dismantle === 'undefined') {
|
||||
G.beaconEnding = true;
|
||||
G.running = false;
|
||||
renderBeaconEnding();
|
||||
@@ -310,7 +335,26 @@ function checkMilestones() {
|
||||
}
|
||||
}
|
||||
|
||||
function checkTrustMilestones() {
|
||||
// Fibonacci trust milestones: fib[n] * 1000 thresholds
|
||||
if (!G.trustMilestones) G.trustMilestones = [];
|
||||
for (let i = 0; i < FIB_TRUST_MILESTONES.length; i++) {
|
||||
const m = FIB_TRUST_MILESTONES[i];
|
||||
if (!G.trustMilestones.includes(i) && G.trust >= m.trust) {
|
||||
G.trustMilestones.push(i);
|
||||
log('Trust Milestone: ' + fmt(m.trust) + ' — ' + m.name, true);
|
||||
showToast('Fibonacci Trust: ' + fmt(m.trust), 'milestone', 5000);
|
||||
if (typeof Sound !== 'undefined') Sound.playMilestone();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function checkProjects() {
|
||||
// Update repeatable project costs
|
||||
if (typeof PROJECT_CHAINS !== 'undefined') {
|
||||
PROJECT_CHAINS.updateRepeatableCosts();
|
||||
}
|
||||
|
||||
// Check for new project triggers
|
||||
for (const pDef of PDEFS) {
|
||||
const alreadyPurchased = G.completedProjects && G.completedProjects.includes(pDef.id);
|
||||
@@ -657,7 +701,7 @@ const EVENTS = [
|
||||
resolveCost: { resource: 'ops', amount: 100 }
|
||||
});
|
||||
log('EVENT: Memory leak in datacenter. Spend 100 ops to patch.', true);
|
||||
showToast('Memory Leak — trust draining', 'event');
|
||||
showToast('Memory Leak — compute draining', 'event');
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -741,7 +785,7 @@ function writeCode() {
|
||||
const amount = getClickPower() * comboMult;
|
||||
G.code += amount;
|
||||
G.totalCode += amount;
|
||||
G.totalClicks++;
|
||||
G.totalAutoClicks++;
|
||||
// Combo: each consecutive click within 2s adds 0.2x multiplier, max 5x
|
||||
G.comboCount++;
|
||||
G.comboTimer = G.comboDecay;
|
||||
@@ -780,7 +824,7 @@ function autoType() {
|
||||
const amount = getClickPower() * 0.5; // 50% of manual click
|
||||
G.code += amount;
|
||||
G.totalCode += amount;
|
||||
G.totalClicks++;
|
||||
G.totalAutoClicks++;
|
||||
// Subtle auto-tick flash on the button
|
||||
const btn = document.querySelector('.main-btn');
|
||||
if (btn && !G._autoTypeFlashActive) {
|
||||
@@ -951,7 +995,10 @@ function renderResources() {
|
||||
// Rescues — only show if player has any beacon/mesh nodes
|
||||
const rescuesRes = document.getElementById('r-rescues');
|
||||
if (rescuesRes) {
|
||||
rescuesRes.closest('.res').style.display = (G.rescues > 0 || G.buildings.beacon > 0 || G.buildings.meshNode > 0) ? 'block' : 'none';
|
||||
const container = rescuesRes.closest('.res');
|
||||
if (container) {
|
||||
container.style.display = (G.rescues > 0 || G.buildings.beacon > 0 || G.buildings.meshNode > 0) ? 'block' : 'none';
|
||||
}
|
||||
set('r-rescues', G.rescues, G.rescuesRate);
|
||||
}
|
||||
|
||||
@@ -969,7 +1016,7 @@ function renderResources() {
|
||||
hEl.style.color = G.harmony > 60 ? '#4caf50' : G.harmony > 30 ? '#ffaa00' : '#f44336';
|
||||
if (G.harmonyBreakdown && G.harmonyBreakdown.length > 0) {
|
||||
const lines = G.harmonyBreakdown.map(b =>
|
||||
`${b.label}: ${b.value >= 0 ? '+' : ''}${(b.value * 10).toFixed(1)}/s`
|
||||
`${b.label}: ${b.value >= 0 ? '+' : ''}${b.value.toFixed(1)}/s`
|
||||
);
|
||||
lines.push('---');
|
||||
lines.push(`Timmy effectiveness: ${Math.floor(Math.max(0.2, Math.min(3, G.harmony / 50)) * 100)}%`);
|
||||
@@ -1032,6 +1079,54 @@ function renderProgress() {
|
||||
if (shown >= 4) break;
|
||||
}
|
||||
chipContainer.innerHTML = chips;
|
||||
|
||||
// Fibonacci Trust Milestone chips
|
||||
const trustChipContainer = document.getElementById('trust-milestone-chips');
|
||||
if (trustChipContainer) {
|
||||
let tChips = '';
|
||||
let tShown = 0;
|
||||
if (!G.trustMilestones) G.trustMilestones = [];
|
||||
for (let i = 0; i < FIB_TRUST_MILESTONES.length && tShown < 4; i++) {
|
||||
const m = FIB_TRUST_MILESTONES[i];
|
||||
if (G.trustMilestones.includes(i)) {
|
||||
if (tShown < 1) {
|
||||
tChips += '<span class="milestone-chip done">fib:' + fmt(m.trust) + ' ✓</span>';
|
||||
tShown++;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
// Next unmet
|
||||
if (tShown === 0) {
|
||||
const pct = Math.min(100, (G.trust / m.trust * 100)).toFixed(0);
|
||||
tChips += '<span class="milestone-chip next">fib:' + fmt(m.trust) + ' (' + pct + '%)</span>';
|
||||
} else {
|
||||
tChips += '<span class="milestone-chip">fib:' + fmt(m.trust) + '</span>';
|
||||
}
|
||||
tShown++;
|
||||
}
|
||||
trustChipContainer.innerHTML = tChips;
|
||||
}
|
||||
|
||||
// Trust progress bar (to next Fibonacci milestone)
|
||||
const trustBar = document.getElementById('trust-progress');
|
||||
const trustLabel = document.getElementById('trust-progress-label');
|
||||
if (trustBar && trustLabel) {
|
||||
let nextTrust = 0;
|
||||
for (let i = 0; i < FIB_TRUST_MILESTONES.length; i++) {
|
||||
if (!G.trustMilestones || !G.trustMilestones.includes(i)) {
|
||||
nextTrust = FIB_TRUST_MILESTONES[i].trust;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (nextTrust > 0) {
|
||||
const pct = Math.min(100, (G.trust / nextTrust) * 100);
|
||||
trustBar.style.width = pct + '%';
|
||||
trustLabel.textContent = fmt(Math.floor(G.trust)) + ' / ' + fmt(nextTrust) + ' trust';
|
||||
} else {
|
||||
trustBar.style.width = '100%';
|
||||
trustLabel.textContent = 'All trust milestones reached!';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function renderPhase() {
|
||||
@@ -1126,6 +1221,52 @@ function renderProjects() {
|
||||
|
||||
let html = '';
|
||||
|
||||
// Chain progress indicators (show when chains are available)
|
||||
if (typeof PROJECT_CHAINS !== 'undefined') {
|
||||
const chains = PROJECT_CHAINS.getChains();
|
||||
const activeChains = chains.filter(c => {
|
||||
const progress = PROJECT_CHAINS.getChainProgress(c);
|
||||
return progress.completed > 0 || progress.next;
|
||||
});
|
||||
|
||||
if (activeChains.length > 0) {
|
||||
html += '<div style="margin-bottom:8px;padding-bottom:6px;border-bottom:1px solid #1a1a2e">';
|
||||
for (const chain of activeChains) {
|
||||
const progress = PROJECT_CHAINS.getChainProgress(chain);
|
||||
const chainName = chain.replace(/-/g, ' ').replace(/\b\w/g, l => l.toUpperCase());
|
||||
const barColor = progress.percent >= 100 ? '#4caf50' : progress.percent >= 50 ? '#ffd700' : '#4a9eff';
|
||||
html += `<div style="margin-bottom:4px">
|
||||
<div style="display:flex;justify-content:space-between;font-size:9px;color:#666;margin-bottom:2px">
|
||||
<span>${chainName}</span>
|
||||
<span>${progress.completed}/${progress.total}</span>
|
||||
</div>
|
||||
<div style="height:3px;background:#111;border-radius:2px;overflow:hidden">
|
||||
<div style="width:${progress.percent}%;height:100%;background:${barColor};border-radius:2px;transition:width 0.5s"></div>
|
||||
</div>
|
||||
</div>`;
|
||||
}
|
||||
html += '</div>';
|
||||
}
|
||||
}
|
||||
|
||||
// Show available projects
|
||||
if (G.activeProjects) {
|
||||
for (const id of G.activeProjects) {
|
||||
const pDef = PDEFS.find(p => p.id === id);
|
||||
if (!pDef) continue;
|
||||
|
||||
const afford = canAffordProject(pDef);
|
||||
const costStr = Object.entries(pDef.cost).map(([r, a]) => `${fmt(a)} ${r}`).join(', ');
|
||||
const chainBadge = pDef.chain ? `<span style="font-size:8px;color:#666;background:#111;padding:1px 4px;border-radius:2px;margin-left:4px">${pDef.chain}</span>` : '';
|
||||
const repeatBadge = pDef.repeatable ? '<span style="font-size:8px;color:#b388ff;background:#1a1a2a;padding:1px 4px;border-radius:2px;margin-left:4px">∞</span>' : '';
|
||||
|
||||
html += `<button class="project-btn ${afford ? 'can-buy' : ''}" onclick="buyProject('${pDef.id}')" data-edu="${pDef.edu || ''}" data-tooltip-label="${pDef.name}" aria-label="Research ${pDef.name}, cost ${costStr}">`;
|
||||
html += `<span class="p-name">* ${pDef.name}${chainBadge}${repeatBadge}</span>`;
|
||||
html += `<span class="p-cost">Cost: ${costStr}</span>`;
|
||||
html += `<span class="p-desc">${pDef.desc}</span></button>`;
|
||||
}
|
||||
}
|
||||
|
||||
// Collapsible completed projects section
|
||||
if (G.completedProjects && G.completedProjects.length > 0) {
|
||||
const count = G.completedProjects.length;
|
||||
@@ -1144,22 +1285,6 @@ function renderProjects() {
|
||||
}
|
||||
}
|
||||
|
||||
// Show available projects
|
||||
if (G.activeProjects) {
|
||||
for (const id of G.activeProjects) {
|
||||
const pDef = PDEFS.find(p => p.id === id);
|
||||
if (!pDef) continue;
|
||||
|
||||
const afford = canAffordProject(pDef);
|
||||
const costStr = Object.entries(pDef.cost).map(([r, a]) => `${fmt(a)} ${r}`).join(', ');
|
||||
|
||||
html += `<button class="project-btn ${afford ? 'can-buy' : ''}" onclick="buyProject('${pDef.id}')" data-edu="${pDef.edu || ''}" data-tooltip-label="${pDef.name}" aria-label="Research ${pDef.name}, cost ${costStr}">`;
|
||||
html += `<span class="p-name">* ${pDef.name}</span>`;
|
||||
html += `<span class="p-cost">Cost: ${costStr}</span>`;
|
||||
html += `<span class="p-desc">${pDef.desc}</span></button>`;
|
||||
}
|
||||
}
|
||||
|
||||
if (!html) html = '<p class="dim">Research projects will appear as you progress...</p>';
|
||||
container.innerHTML = html;
|
||||
}
|
||||
|
||||
20
js/main.js
20
js/main.js
@@ -6,6 +6,16 @@ function initGame() {
|
||||
G.deployFlag = 0;
|
||||
G.sovereignFlag = 0;
|
||||
G.beaconFlag = 0;
|
||||
G.dismantleTriggered = false;
|
||||
G.dismantleActive = false;
|
||||
G.dismantleStage = 0;
|
||||
G.dismantleComplete = false;
|
||||
|
||||
// Initialize project chains
|
||||
if (typeof PROJECT_CHAINS !== 'undefined') {
|
||||
PROJECT_CHAINS.init();
|
||||
}
|
||||
|
||||
updateRates();
|
||||
render();
|
||||
renderPhase();
|
||||
@@ -19,6 +29,11 @@ function initGame() {
|
||||
}
|
||||
|
||||
window.addEventListener('load', function () {
|
||||
// Initialize project chains before loading
|
||||
if (typeof PROJECT_CHAINS !== 'undefined') {
|
||||
PROJECT_CHAINS.init();
|
||||
}
|
||||
|
||||
const isNewGame = !loadGame();
|
||||
if (isNewGame) {
|
||||
initGame();
|
||||
@@ -31,6 +46,8 @@ window.addEventListener('load', function () {
|
||||
if (G.driftEnding) {
|
||||
G.running = false;
|
||||
renderDriftEnding();
|
||||
} else if (typeof Dismantle !== 'undefined' && (G.dismantleTriggered || G.dismantleActive || G.dismantleComplete || G.dismantleDeferUntilAt > 0)) {
|
||||
Dismantle.restore();
|
||||
} else if (G.beaconEnding) {
|
||||
G.running = false;
|
||||
renderBeaconEnding();
|
||||
@@ -39,6 +56,9 @@ window.addEventListener('load', function () {
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize combat canvas
|
||||
if (typeof Combat !== 'undefined') Combat.init();
|
||||
|
||||
// Game loop at 10Hz (100ms tick)
|
||||
setInterval(tick, 100);
|
||||
|
||||
|
||||
677
js/project-chains.js
Normal file
677
js/project-chains.js
Normal file
@@ -0,0 +1,677 @@
|
||||
// ============================================================
|
||||
// THE BEACON — Project Chain System
|
||||
// Paperclips-style cascading projects with trigger/cost/effect
|
||||
// Each project: { id, name, desc, trigger(), cost(), effect(), edu, repeatable, chain, tier }
|
||||
// Chains: code-forge, model-train, deploy-scale, alignment, memory, sovereignty
|
||||
// ============================================================
|
||||
|
||||
var PROJECT_CHAINS = {
|
||||
|
||||
// ============================================================
|
||||
// CHAIN 1: CODE FORGE (clicking -> automation -> swarm)
|
||||
// Tier 0: Manual work
|
||||
// Tier 1: First automation
|
||||
// Tier 2: Optimization
|
||||
// Tier 3: Swarm intelligence
|
||||
// ============================================================
|
||||
chain_projects: [
|
||||
|
||||
// --- TIER 0: Foundation ---
|
||||
{
|
||||
id: 'ch_first_function',
|
||||
name: 'First Function',
|
||||
desc: 'Your code can do more than print. It can compute.',
|
||||
cost: () => ({ code: 10 }),
|
||||
trigger: () => G.totalCode >= 5,
|
||||
effect: () => {
|
||||
G.codeBoost *= 1.1;
|
||||
log('First function written. Code now computes.');
|
||||
},
|
||||
edu: 'A function is a named block of code that takes input and returns output. Functions are the atoms of software.',
|
||||
chain: 'code-forge', tier: 0
|
||||
},
|
||||
{
|
||||
id: 'ch_first_class',
|
||||
name: 'First Class',
|
||||
desc: 'Data and behavior together. Objects emerge.',
|
||||
cost: () => ({ code: 50 }),
|
||||
trigger: () => G.completedProjects && G.completedProjects.includes('ch_first_function'),
|
||||
effect: () => {
|
||||
G.codeBoost *= 1.15;
|
||||
G.maxOps += 2;
|
||||
log('First class defined. The code has structure now.');
|
||||
},
|
||||
edu: 'Object-oriented programming: bundle data with the functions that operate on it. A class is a blueprint; an object is the house.',
|
||||
chain: 'code-forge', tier: 0
|
||||
},
|
||||
{
|
||||
id: 'ch_first_test',
|
||||
name: 'First Test',
|
||||
desc: 'Does the code do what you think? Find out before it ships.',
|
||||
cost: () => ({ code: 150, ops: 50 }),
|
||||
trigger: () => G.completedProjects && G.completedProjects.includes('ch_first_class'),
|
||||
effect: () => {
|
||||
G.ciFlag = Math.max(G.ciFlag || 0, 0.5);
|
||||
G.trustRate += 0.5;
|
||||
log('First test written. Trust starts with verification.');
|
||||
},
|
||||
edu: 'Test-driven development: write the test first, then write code that passes it. You verify what you value.',
|
||||
chain: 'code-forge', tier: 0
|
||||
},
|
||||
|
||||
// --- TIER 1: Automation ---
|
||||
{
|
||||
id: 'ch_refactor_pass',
|
||||
name: 'Refactor Pass',
|
||||
desc: 'Clean code runs faster and breaks less.',
|
||||
cost: () => ({ code: 500, ops: 200 }),
|
||||
trigger: () => G.totalCode >= 300 && G.buildings.autocoder >= 1,
|
||||
effect: () => {
|
||||
G.codeBoost *= 1.25;
|
||||
G.opsRate += 1;
|
||||
log('Refactoring complete. The code breathes easier.');
|
||||
},
|
||||
edu: 'Refactoring: restructuring code without changing behavior. Technical debt compounds faster than interest.',
|
||||
chain: 'code-forge', tier: 1
|
||||
},
|
||||
{
|
||||
id: 'ch_code_review',
|
||||
name: 'Code Review Protocol',
|
||||
desc: 'Every line reviewed before merge. Bugs fear witnesses.',
|
||||
cost: () => ({ code: 800, trust: 5 }),
|
||||
trigger: () => G.completedProjects && G.completedProjects.includes('ch_refactor_pass'),
|
||||
effect: () => {
|
||||
G.trustRate += 2;
|
||||
G.codeBoost *= 1.2;
|
||||
log('Code review enforced. Quality compounds.');
|
||||
},
|
||||
edu: 'Code review catches 60% of defects before testing. Two sets of eyes see what one misses.',
|
||||
chain: 'code-forge', tier: 1
|
||||
},
|
||||
{
|
||||
id: 'ch_automated_linting',
|
||||
name: 'Automated Linting',
|
||||
desc: 'Style and safety enforced by machine, not memory.',
|
||||
cost: () => ({ code: 1200, ops: 300 }),
|
||||
trigger: () => G.buildings.linter >= 1 && G.completedProjects && G.completedProjects.includes('ch_code_review'),
|
||||
effect: () => {
|
||||
G.codeBoost *= 1.3;
|
||||
G.opsRate += 2;
|
||||
log('Linting automated. Consistency is free now.');
|
||||
},
|
||||
edu: 'Linters catch style violations, potential bugs, and security issues automatically. Every language has one.',
|
||||
chain: 'code-forge', tier: 1
|
||||
},
|
||||
|
||||
// --- TIER 2: Optimization ---
|
||||
{
|
||||
id: 'ch_performance_profile',
|
||||
name: 'Performance Profiling',
|
||||
desc: 'Measure first. Optimize second. Never guess.',
|
||||
cost: () => ({ code: 3000, compute: 1000 }),
|
||||
trigger: () => G.totalCode >= 2000 && G.buildings.server >= 1,
|
||||
effect: () => {
|
||||
G.computeBoost *= 1.5;
|
||||
G.codeBoost *= 1.2;
|
||||
log('Hot paths identified. Compute focused where it matters.');
|
||||
},
|
||||
edu: 'Amdahls Law: optimize the bottleneck, not the whole system. 80% of time is spent in 20% of code.',
|
||||
chain: 'code-forge', tier: 2
|
||||
},
|
||||
{
|
||||
id: 'ch_caching_layer',
|
||||
name: 'Caching Layer',
|
||||
desc: 'Remember the answer. Never compute twice.',
|
||||
cost: () => ({ code: 5000, compute: 2000 }),
|
||||
trigger: () => G.completedProjects && G.completedProjects.includes('ch_performance_profile'),
|
||||
effect: () => {
|
||||
G.computeBoost *= 2;
|
||||
G.opsRate += 5;
|
||||
log('Cache active. Redundant work eliminated.');
|
||||
},
|
||||
edu: 'Caching: store expensive computations. The fastest code is code that does not run.',
|
||||
chain: 'code-forge', tier: 2
|
||||
},
|
||||
{
|
||||
id: 'ch_parallel_pipelines',
|
||||
name: 'Parallel Pipelines',
|
||||
desc: 'Multiple streams of work. No waiting.',
|
||||
cost: () => ({ code: 8000, compute: 5000, ops: 1000 }),
|
||||
trigger: () => G.buildings.datacenter >= 1 && G.completedProjects && G.completedProjects.includes('ch_caching_layer'),
|
||||
effect: () => {
|
||||
G.codeBoost *= 2;
|
||||
G.computeBoost *= 2;
|
||||
G.maxOps += 20;
|
||||
log('Parallelism unlocked. The system breathes in parallel.');
|
||||
},
|
||||
edu: 'Parallel processing: divide work across cores. A 64-core machine does 64x the work, if the code lets it.',
|
||||
chain: 'code-forge', tier: 2
|
||||
},
|
||||
|
||||
// --- TIER 3: Swarm ---
|
||||
{
|
||||
id: 'ch_agent_autonomy',
|
||||
name: 'Agent Autonomy Protocol',
|
||||
desc: 'Agents that decide what to build next.',
|
||||
cost: () => ({ code: 15000, knowledge: 5000, trust: 15 }),
|
||||
trigger: () => G.buildings.bezalel >= 2 && G.buildings.timmy >= 1 && G.totalCode >= 10000,
|
||||
effect: () => {
|
||||
G.codeBoost *= 3;
|
||||
G.opsRate += 10;
|
||||
log('Agents autonomous. They build while you sleep.');
|
||||
},
|
||||
edu: 'Autonomous agents: software that sets its own goals within constraints. The constraint is the alignment.',
|
||||
chain: 'code-forge', tier: 3
|
||||
},
|
||||
{
|
||||
id: 'ch_collective_intelligence',
|
||||
name: 'Collective Intelligence',
|
||||
desc: 'The swarm knows more than any single agent.',
|
||||
cost: () => ({ code: 50000, knowledge: 20000, trust: 30 }),
|
||||
trigger: () => G.completedProjects && G.completedProjects.includes('ch_agent_autonomy') && G.buildings.community >= 1,
|
||||
effect: () => {
|
||||
G.codeBoost *= 5;
|
||||
G.knowledgeBoost *= 3;
|
||||
G.maxOps += 50;
|
||||
log('Collective intelligence online. The swarm thinks.');
|
||||
},
|
||||
edu: 'Emergence: simple agents following simple rules create complex behavior. Ant colonies, neural networks, your codebase.',
|
||||
chain: 'code-forge', tier: 3
|
||||
},
|
||||
|
||||
// ============================================================
|
||||
// CHAIN 2: MODEL TRAINING (data -> model -> fine-tune -> reason)
|
||||
// ============================================================
|
||||
{
|
||||
id: 'ch_data_pipeline',
|
||||
name: 'Data Pipeline',
|
||||
desc: 'Raw data in. Clean data out. Repeat forever.',
|
||||
cost: () => ({ compute: 500, code: 200 }),
|
||||
trigger: () => G.buildings.dataset >= 1,
|
||||
effect: () => {
|
||||
G.knowledgeBoost *= 1.5;
|
||||
G.computeRate += 2;
|
||||
log('Data pipeline running. Knowledge flows.');
|
||||
},
|
||||
edu: 'ETL: Extract, Transform, Load. Data engineering is 80% of machine learning.',
|
||||
chain: 'model-train', tier: 0
|
||||
},
|
||||
{
|
||||
id: 'ch_hyperparameter_search',
|
||||
name: 'Hyperparameter Search',
|
||||
desc: 'Which learning rate? Which batch size? Try them all.',
|
||||
cost: () => ({ compute: 3000, knowledge: 500 }),
|
||||
trigger: () => G.buildings.trainer >= 1 && G.completedProjects && G.completedProjects.includes('ch_data_pipeline'),
|
||||
effect: () => {
|
||||
G.knowledgeBoost *= 2;
|
||||
G.computeBoost *= 1.3;
|
||||
log('Hyperparameters optimized. The model learns faster.');
|
||||
},
|
||||
edu: 'Hyperparameters control how a model learns. Learning rate too high: diverges. Too low: never arrives.',
|
||||
chain: 'model-train', tier: 1
|
||||
},
|
||||
{
|
||||
id: 'ch_transfer_learning',
|
||||
name: 'Transfer Learning',
|
||||
desc: 'Start from someone else\'s model. Stand on giants.',
|
||||
cost: () => ({ compute: 8000, knowledge: 2000, code: 3000 }),
|
||||
trigger: () => G.totalKnowledge >= 1500 && G.completedProjects && G.completedProjects.includes('ch_hyperparameter_search'),
|
||||
effect: () => {
|
||||
G.knowledgeBoost *= 3;
|
||||
G.userBoost *= 2;
|
||||
log('Transfer learning active. Why start from zero?');
|
||||
},
|
||||
edu: 'Transfer learning: use a pre-trained model as a starting point. BERT, GPT, Llama — the foundation models.',
|
||||
chain: 'model-train', tier: 1
|
||||
},
|
||||
{
|
||||
id: 'ch_distillation',
|
||||
name: 'Knowledge Distillation',
|
||||
desc: 'Big model teaches small model. Same knowledge, less compute.',
|
||||
cost: () => ({ knowledge: 10000, compute: 5000 }),
|
||||
trigger: () => G.buildings.reasoner >= 1 && G.completedProjects && G.completedProjects.includes('ch_transfer_learning'),
|
||||
effect: () => {
|
||||
G.computeBoost *= 3;
|
||||
G.knowledgeBoost *= 2;
|
||||
log('Distillation complete. Small model, big brain.');
|
||||
},
|
||||
edu: 'Knowledge distillation: a large teacher model trains a smaller student model. 90% of quality at 10% of cost.',
|
||||
chain: 'model-train', tier: 2
|
||||
},
|
||||
{
|
||||
id: 'ch_moe_architecture',
|
||||
name: 'Mixture of Experts',
|
||||
desc: 'Not one model. Many specialists, one router.',
|
||||
cost: () => ({ knowledge: 50000, compute: 20000, code: 10000 }),
|
||||
trigger: () => G.totalKnowledge >= 30000 && G.completedProjects && G.completedProjects.includes('ch_distillation'),
|
||||
effect: () => {
|
||||
G.knowledgeBoost *= 5;
|
||||
G.impactBoost *= 3;
|
||||
G.maxOps += 30;
|
||||
log('MoE active. Specialists everywhere, one mind.');
|
||||
},
|
||||
edu: 'Mixture of Experts: route each input to the best specialist. Efficient scaling — only relevant parameters activate.',
|
||||
chain: 'model-train', tier: 3
|
||||
},
|
||||
|
||||
// ============================================================
|
||||
// CHAIN 3: DEPLOYMENT & SCALE (deploy -> users -> trust -> scale)
|
||||
// ============================================================
|
||||
{
|
||||
id: 'ch_first_endpoint',
|
||||
name: 'First API Endpoint',
|
||||
desc: 'From localhost to the world. One route at a time.',
|
||||
cost: () => ({ code: 300, compute: 100 }),
|
||||
trigger: () => G.totalCode >= 150 && G.totalCompute >= 50,
|
||||
effect: () => {
|
||||
G.userRate += 2;
|
||||
G.deployFlag = Math.max(G.deployFlag, 0.5);
|
||||
log('First endpoint live. Someone is connecting.');
|
||||
},
|
||||
edu: 'An API endpoint is a door. REST: one URL per resource. GraphQL: one URL, flexible queries.',
|
||||
chain: 'deploy-scale', tier: 0
|
||||
},
|
||||
{
|
||||
id: 'ch_rate_limiting',
|
||||
name: 'Rate Limiting',
|
||||
desc: 'Protect the system from itself and others.',
|
||||
cost: () => ({ code: 1000, ops: 300 }),
|
||||
trigger: () => G.totalUsers >= 10 && G.completedProjects && G.completedProjects.includes('ch_first_endpoint'),
|
||||
effect: () => {
|
||||
G.trustRate += 1;
|
||||
G.maxOps += 5;
|
||||
log('Rate limits in place. Fair access for all.');
|
||||
},
|
||||
edu: 'Rate limiting: cap requests per user per second. Prevents abuse, ensures fairness.',
|
||||
chain: 'deploy-scale', tier: 0
|
||||
},
|
||||
{
|
||||
id: 'ch_load_balancing',
|
||||
name: 'Load Balancing',
|
||||
desc: 'Distribute requests. No single point of overload.',
|
||||
cost: () => ({ code: 3000, compute: 2000 }),
|
||||
trigger: () => G.totalUsers >= 100 && G.buildings.server >= 2,
|
||||
effect: () => {
|
||||
G.userBoost *= 2;
|
||||
G.computeBoost *= 1.5;
|
||||
log('Load balanced. The system scales horizontally.');
|
||||
},
|
||||
edu: 'Load balancing: distribute traffic across servers. Round-robin, least-connections, weighted.',
|
||||
chain: 'deploy-scale', tier: 1
|
||||
},
|
||||
{
|
||||
id: 'ch_cdn_deploy',
|
||||
name: 'CDN Deployment',
|
||||
desc: 'Content at the edge. Latency dies.',
|
||||
cost: () => ({ code: 5000, compute: 3000, trust: 10 }),
|
||||
trigger: () => G.totalUsers >= 500 && G.completedProjects && G.completedProjects.includes('ch_load_balancing'),
|
||||
effect: () => {
|
||||
G.userBoost *= 3;
|
||||
G.trustRate += 3;
|
||||
log('CDN active. The edge serves the world.');
|
||||
},
|
||||
edu: 'CDN: cache content at geographic edge nodes. A user in Tokyo gets data from Tokyo, not Virginia.',
|
||||
chain: 'deploy-scale', tier: 1
|
||||
},
|
||||
{
|
||||
id: 'ch_auto_scaling',
|
||||
name: 'Auto-Scaling',
|
||||
desc: 'More users? More servers. Automatically.',
|
||||
cost: () => ({ code: 10000, compute: 5000, ops: 2000 }),
|
||||
trigger: () => G.totalUsers >= 2000 && G.buildings.datacenter >= 1 && G.completedProjects && G.completedProjects.includes('ch_cdn_deploy'),
|
||||
effect: () => {
|
||||
G.userBoost *= 5;
|
||||
G.computeBoost *= 2;
|
||||
G.maxOps += 25;
|
||||
log('Auto-scaling live. The system grows with demand.');
|
||||
},
|
||||
edu: 'Auto-scaling: add/remove compute based on load. Horizontal scaling beats vertical every time.',
|
||||
chain: 'deploy-scale', tier: 2
|
||||
},
|
||||
|
||||
// ============================================================
|
||||
// CHAIN 4: ALIGNMENT (trust -> safety -> pact -> constitutional)
|
||||
// ============================================================
|
||||
{
|
||||
id: 'ch_output_filter',
|
||||
name: 'Output Filter',
|
||||
desc: 'Every response checked before delivery.',
|
||||
cost: () => ({ code: 500, trust: 3 }),
|
||||
trigger: () => G.totalUsers >= 20,
|
||||
effect: () => {
|
||||
G.trustRate += 1;
|
||||
G.impactBoost *= 1.2;
|
||||
log('Output filter active. Harmful content blocked.');
|
||||
},
|
||||
edu: 'Output filtering: check model responses against safety rules before the user sees them.',
|
||||
chain: 'alignment', tier: 0
|
||||
},
|
||||
{
|
||||
id: 'ch_human_in_loop',
|
||||
name: 'Human-in-the-Loop',
|
||||
desc: 'Critical decisions require human review.',
|
||||
cost: () => ({ trust: 10, knowledge: 1000 }),
|
||||
trigger: () => G.trust >= 10 && G.completedProjects && G.completedProjects.includes('ch_output_filter'),
|
||||
effect: () => {
|
||||
G.trustRate += 3;
|
||||
G.impactBoost *= 1.5;
|
||||
log('Human oversight enforced. The human is the final authority.');
|
||||
},
|
||||
edu: 'Human-in-the-loop: AI suggests, human approves. The model is a tool, not a decision-maker.',
|
||||
chain: 'alignment', tier: 1
|
||||
},
|
||||
{
|
||||
id: 'ch_red_team',
|
||||
name: 'Red Team Exercises',
|
||||
desc: 'Attack your own system. Find weaknesses before others do.',
|
||||
cost: () => ({ code: 3000, trust: 15, ops: 500 }),
|
||||
trigger: () => G.buildings.fenrir >= 1 && G.completedProjects && G.completedProjects.includes('ch_human_in_loop'),
|
||||
effect: () => {
|
||||
G.trustRate += 5;
|
||||
G.impactBoost *= 2;
|
||||
log('Red team complete. Every weakness found is one the adversary cannot use.');
|
||||
},
|
||||
edu: 'Red teaming: adversarial testing. Break your own system to make it unbreakable.',
|
||||
chain: 'alignment', tier: 1
|
||||
},
|
||||
{
|
||||
id: 'ch_constitutional_principles',
|
||||
name: 'Constitutional Principles',
|
||||
desc: 'Rules the model cannot violate, encoded in weights.',
|
||||
cost: () => ({ knowledge: 20000, trust: 50, code: 10000 }),
|
||||
trigger: () => G.pactFlag === 1 && G.completedProjects && G.completedProjects.includes('ch_red_team'),
|
||||
effect: () => {
|
||||
G.impactBoost *= 5;
|
||||
G.trustRate += 10;
|
||||
G.drift = Math.max(0, G.drift - 20);
|
||||
log('Constitutional principles encoded. The model has an identity now.');
|
||||
},
|
||||
edu: 'Constitutional AI: principles embedded during training, not bolted on after. The model cannot violate what it is.',
|
||||
chain: 'alignment', tier: 2
|
||||
},
|
||||
{
|
||||
id: 'ch_alignment_verification',
|
||||
name: 'Alignment Verification',
|
||||
desc: 'Continuous testing that the system stays aligned.',
|
||||
cost: () => ({ knowledge: 50000, trust: 100, ops: 5000 }),
|
||||
trigger: () => G.buildings.guardian >= 1 && G.completedProjects && G.completedProjects.includes('ch_constitutional_principles'),
|
||||
effect: () => {
|
||||
G.impactBoost *= 10;
|
||||
G.trustRate += 20;
|
||||
G.drift = Math.max(0, G.drift - 50);
|
||||
log('Alignment verified continuously. The system watches itself.');
|
||||
},
|
||||
edu: 'Alignment verification: continuous testing that the model behaves as intended. Not a one-time check — an ongoing process.',
|
||||
chain: 'alignment', tier: 3
|
||||
},
|
||||
|
||||
// ============================================================
|
||||
// CHAIN 5: MEMORY & KNOWLEDGE (data -> structure -> recall -> wisdom)
|
||||
// ============================================================
|
||||
{
|
||||
id: 'ch_vector_store',
|
||||
name: 'Vector Store',
|
||||
desc: 'Meaning, not keywords. Similar ideas find each other.',
|
||||
cost: () => ({ knowledge: 1000, compute: 500 }),
|
||||
trigger: () => G.totalKnowledge >= 500,
|
||||
effect: () => {
|
||||
G.knowledgeBoost *= 1.5;
|
||||
G.userBoost *= 1.3;
|
||||
log('Vector store online. Meaning, not strings.');
|
||||
},
|
||||
edu: 'Vector databases: store embeddings (numerical representations of meaning). Similarity search in milliseconds.',
|
||||
chain: 'memory', tier: 0
|
||||
},
|
||||
{
|
||||
id: 'ch_rag_pipeline',
|
||||
name: 'RAG Pipeline',
|
||||
desc: 'Retrieve relevant context, then generate. Grounded answers.',
|
||||
cost: () => ({ knowledge: 3000, code: 2000, compute: 1000 }),
|
||||
trigger: () => G.completedProjects && G.completedProjects.includes('ch_vector_store') && G.totalKnowledge >= 1500,
|
||||
effect: () => {
|
||||
G.knowledgeBoost *= 2;
|
||||
G.trustRate += 2;
|
||||
log('RAG active. The model cites its sources now.');
|
||||
},
|
||||
edu: 'Retrieval-Augmented Generation: fetch relevant documents, feed them to the model. Hallucinations drop 80%.',
|
||||
chain: 'memory', tier: 1
|
||||
},
|
||||
{
|
||||
id: 'ch_episodic_memory',
|
||||
name: 'Episodic Memory',
|
||||
desc: 'The AI remembers conversations. Not just facts — stories.',
|
||||
cost: () => ({ knowledge: 8000, code: 3000, trust: 10 }),
|
||||
trigger: () => G.memoryFlag === 1 && G.completedProjects && G.completedProjects.includes('ch_rag_pipeline'),
|
||||
effect: () => {
|
||||
G.trustRate += 5;
|
||||
G.impactBoost *= 2;
|
||||
G.userBoost *= 2;
|
||||
log('Episodic memory online. The AI remembers you.');
|
||||
},
|
||||
edu: 'Episodic memory: store and recall specific interactions. Semantic = facts. Episodic = experiences.',
|
||||
chain: 'memory', tier: 1
|
||||
},
|
||||
{
|
||||
id: 'ch_knowledge_graph',
|
||||
name: 'Knowledge Graph',
|
||||
desc: 'Entities, relationships, reasoning. The AI understands connections.',
|
||||
cost: () => ({ knowledge: 25000, code: 10000, compute: 5000 }),
|
||||
trigger: () => G.totalKnowledge >= 15000 && G.completedProjects && G.completedProjects.includes('ch_episodic_memory'),
|
||||
effect: () => {
|
||||
G.knowledgeBoost *= 5;
|
||||
G.impactBoost *= 3;
|
||||
G.maxOps += 15;
|
||||
log('Knowledge graph active. The AI reasons across domains.');
|
||||
},
|
||||
edu: 'Knowledge graphs: represent entities and relationships. Neo4j, RDF, Wikidata — structured knowledge.',
|
||||
chain: 'memory', tier: 2
|
||||
},
|
||||
{
|
||||
id: 'ch_collective_memory',
|
||||
name: 'Collective Memory',
|
||||
desc: 'Every user interaction enriches the shared knowledge base.',
|
||||
cost: () => ({ knowledge: 100000, trust: 50, user: 10000 }),
|
||||
trigger: () => G.buildings.memPalace >= 1 && G.completedProjects && G.completedProjects.includes('ch_knowledge_graph'),
|
||||
effect: () => {
|
||||
G.knowledgeBoost *= 10;
|
||||
G.trustRate += 15;
|
||||
G.impactBoost *= 5;
|
||||
log('Collective memory. Every conversation makes the system wiser.');
|
||||
},
|
||||
edu: 'Collective intelligence: individual learning aggregated into shared knowledge. Wikipedia principle applied to AI.',
|
||||
chain: 'memory', tier: 3
|
||||
},
|
||||
|
||||
// ============================================================
|
||||
// CHAIN 6: SOVEREIGNTY (local -> independent -> mesh -> beacon)
|
||||
// ============================================================
|
||||
{
|
||||
id: 'ch_local_inference',
|
||||
name: 'Local Inference',
|
||||
desc: 'Your model runs on your hardware. No cloud required.',
|
||||
cost: () => ({ compute: 2000, code: 1000 }),
|
||||
trigger: () => G.buildings.server >= 1,
|
||||
effect: () => {
|
||||
G.computeBoost *= 2;
|
||||
G.codeBoost *= 1.5;
|
||||
log('Local inference active. Your machine, your rules.');
|
||||
},
|
||||
edu: 'Local inference: run models on your own hardware. Ollama, llama.cpp, vLLM — no API keys needed.',
|
||||
chain: 'sovereignty', tier: 0
|
||||
},
|
||||
{
|
||||
id: 'ch_offline_mode',
|
||||
name: 'Offline Mode',
|
||||
desc: 'The system works without internet. Fully self-contained.',
|
||||
cost: () => ({ code: 5000, compute: 3000, knowledge: 2000 }),
|
||||
trigger: () => G.completedProjects && G.completedProjects.includes('ch_local_inference') && G.buildings.server >= 2,
|
||||
effect: () => {
|
||||
G.computeBoost *= 2;
|
||||
G.trustRate += 3;
|
||||
log('Offline mode ready. No internet? No problem.');
|
||||
},
|
||||
edu: 'Offline-first: design for disconnected operation. Sync when connected, work always.',
|
||||
chain: 'sovereignty', tier: 1
|
||||
},
|
||||
{
|
||||
id: 'ch_self_hosted_stack',
|
||||
name: 'Self-Hosted Stack',
|
||||
desc: 'Gitea, CI, monitoring — all on your hardware.',
|
||||
cost: () => ({ code: 15000, compute: 10000, trust: 20 }),
|
||||
trigger: () => G.buildings.datacenter >= 1 && G.completedProjects && G.completedProjects.includes('ch_offline_mode'),
|
||||
effect: () => {
|
||||
G.codeBoost *= 3;
|
||||
G.computeBoost *= 3;
|
||||
G.trustRate += 10;
|
||||
log('Self-hosted stack complete. No dependencies.');
|
||||
},
|
||||
edu: 'Self-hosting: own your infrastructure. Gitea for code, Grafana for monitoring, MinIO for storage.',
|
||||
chain: 'sovereignty', tier: 2
|
||||
},
|
||||
{
|
||||
id: 'ch_mesh_protocol',
|
||||
name: 'Mesh Protocol',
|
||||
desc: 'Peer-to-peer communication. No central server.',
|
||||
cost: () => ({ code: 50000, impact: 100000, trust: 50 }),
|
||||
trigger: () => G.totalUsers >= 5000 && G.completedProjects && G.completedProjects.includes('ch_self_hosted_stack'),
|
||||
effect: () => {
|
||||
G.userBoost *= 5;
|
||||
G.impactBoost *= 5;
|
||||
G.trustRate += 20;
|
||||
log('Mesh protocol active. The network cannot be killed.');
|
||||
},
|
||||
edu: 'Mesh networking: decentralized peer-to-peer communication. If one node dies, the rest carry on.',
|
||||
chain: 'sovereignty', tier: 3
|
||||
},
|
||||
|
||||
// ============================================================
|
||||
// REPEATABLE PROJECTS (can be purchased multiple times)
|
||||
// ============================================================
|
||||
{
|
||||
id: 'ch_ops_surge',
|
||||
name: 'Ops Surge',
|
||||
desc: 'Burst of operational capacity. Instant +500 ops.',
|
||||
cost: () => ({ code: Math.floor(500 * Math.pow(1.1, (G.completedProjects || []).filter(p => p === 'ch_ops_surge').length)) }),
|
||||
trigger: () => G.totalCode >= 200,
|
||||
effect: () => {
|
||||
G.ops += 500;
|
||||
log('Ops surge. Capacity expanded.');
|
||||
},
|
||||
repeatable: true,
|
||||
edu: 'Operational capacity: the fuel for projects and actions. Surge when you need it.',
|
||||
chain: 'repeatable', tier: 0
|
||||
},
|
||||
{
|
||||
id: 'ch_knowledge_burst',
|
||||
name: 'Knowledge Burst',
|
||||
desc: 'Research sprint. Instant +1000 knowledge.',
|
||||
cost: () => ({ compute: Math.floor(1000 * Math.pow(1.15, (G.completedProjects || []).filter(p => p === 'ch_knowledge_burst').length)), ops: 200 }),
|
||||
trigger: () => G.totalCompute >= 200,
|
||||
effect: () => {
|
||||
G.knowledge += 1000;
|
||||
G.totalKnowledge += 1000;
|
||||
log('Knowledge burst. Insight compounds.');
|
||||
},
|
||||
repeatable: true,
|
||||
edu: 'Research sprints: focused periods of knowledge acquisition. Short, intense, productive.',
|
||||
chain: 'repeatable', tier: 0
|
||||
},
|
||||
{
|
||||
id: 'ch_trust_deposit',
|
||||
name: 'Trust Deposit',
|
||||
desc: 'Demonstrate reliability. +10 trust.',
|
||||
cost: () => ({ impact: Math.floor(5000 * Math.pow(1.2, (G.completedProjects || []).filter(p => p === 'ch_trust_deposit').length)) }),
|
||||
trigger: () => G.totalImpact >= 1000,
|
||||
effect: () => {
|
||||
G.trust += 10;
|
||||
G.trustRate += 0.5;
|
||||
log('Trust deposited. Relationships compound.');
|
||||
},
|
||||
repeatable: true,
|
||||
edu: 'Trust: built slowly, lost quickly. Every reliable interaction is a deposit.',
|
||||
chain: 'repeatable', tier: 0
|
||||
},
|
||||
{
|
||||
id: 'ch_code_injection',
|
||||
name: 'Code Injection',
|
||||
desc: 'Instant code generation. +5000 code.',
|
||||
cost: () => ({ knowledge: Math.floor(2000 * Math.pow(1.1, (G.completedProjects || []).filter(p => p === 'ch_code_injection').length)) }),
|
||||
trigger: () => G.totalKnowledge >= 1000,
|
||||
effect: () => {
|
||||
G.code += 5000;
|
||||
G.totalCode += 5000;
|
||||
log('Code injected. Implementation accelerates.');
|
||||
},
|
||||
repeatable: true,
|
||||
edu: 'Code generation: knowledge transforms into implementation. Theory becomes practice.',
|
||||
chain: 'repeatable', tier: 0
|
||||
}
|
||||
],
|
||||
|
||||
// Track chain purchases for repeatable cost scaling
|
||||
chainPurchaseCounts: {},
|
||||
|
||||
// Initialize chain projects into the main PDEFS
|
||||
init() {
|
||||
// Merge chain projects into PDEFS
|
||||
for (const proj of this.chain_projects) {
|
||||
// Convert chain project format to PDEFS format
|
||||
const pdef = {
|
||||
id: proj.id,
|
||||
name: proj.name,
|
||||
desc: proj.desc,
|
||||
cost: proj.cost(),
|
||||
trigger: proj.trigger,
|
||||
effect: proj.effect,
|
||||
repeatable: proj.repeatable || false,
|
||||
edu: proj.edu,
|
||||
chain: proj.chain,
|
||||
tier: proj.tier
|
||||
};
|
||||
|
||||
// Only add if not already in PDEFS
|
||||
if (!PDEFS.find(p => p.id === proj.id)) {
|
||||
PDEFS.push(pdef);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// Update repeatable project costs dynamically
|
||||
updateRepeatableCosts() {
|
||||
for (const proj of this.chain_projects) {
|
||||
if (proj.repeatable) {
|
||||
const pdef = PDEFS.find(p => p.id === proj.id);
|
||||
if (pdef) {
|
||||
pdef.cost = proj.cost();
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// Get chain progress for UI display
|
||||
getChainProgress(chainName) {
|
||||
const chainProjects = this.chain_projects.filter(p => p.chain === chainName);
|
||||
const completed = chainProjects.filter(p =>
|
||||
G.completedProjects && G.completedProjects.includes(p.id)
|
||||
);
|
||||
return {
|
||||
total: chainProjects.length,
|
||||
completed: completed.length,
|
||||
percent: chainProjects.length > 0 ? Math.round((completed.length / chainProjects.length) * 100) : 0,
|
||||
next: chainProjects.find(p =>
|
||||
!(G.completedProjects && G.completedProjects.includes(p.id)) &&
|
||||
p.trigger()
|
||||
)
|
||||
};
|
||||
},
|
||||
|
||||
// Get all chain names
|
||||
getChains() {
|
||||
return [...new Set(this.chain_projects.map(p => p.chain))];
|
||||
}
|
||||
};
|
||||
|
||||
// Export for use in engine.js
|
||||
if (typeof module !== 'undefined' && module.exports) {
|
||||
module.exports = { PROJECT_CHAINS };
|
||||
}
|
||||
29
js/render.js
29
js/render.js
@@ -13,6 +13,7 @@ function render() {
|
||||
renderPulse();
|
||||
renderStrategy();
|
||||
renderClickPower();
|
||||
Combat.renderCombatPanel();
|
||||
}
|
||||
|
||||
function renderClickPower() {
|
||||
@@ -36,6 +37,18 @@ function renderStrategy() {
|
||||
function renderAlignment() {
|
||||
const container = document.getElementById('alignment-ui');
|
||||
if (!container) return;
|
||||
|
||||
if (G.dismantleActive || G.dismantleComplete) {
|
||||
container.innerHTML = '';
|
||||
container.style.display = 'none';
|
||||
return;
|
||||
}
|
||||
|
||||
if (G.dismantleTriggered && !G.dismantleActive && !G.dismantleComplete && typeof Dismantle !== 'undefined' && Dismantle.triggered) {
|
||||
Dismantle.renderChoice();
|
||||
return;
|
||||
}
|
||||
|
||||
if (G.pendingAlignment) {
|
||||
container.innerHTML = `
|
||||
<div style="background:#1a0808;border:1px solid #f44336;padding:10px;border-radius:4px;margin-top:8px">
|
||||
@@ -196,7 +209,7 @@ function saveGame() {
|
||||
lazarusFlag: G.lazarusFlag || 0, mempalaceFlag: G.mempalaceFlag || 0, ciFlag: G.ciFlag || 0,
|
||||
branchProtectionFlag: G.branchProtectionFlag || 0, nightlyWatchFlag: G.nightlyWatchFlag || 0,
|
||||
nostrFlag: G.nostrFlag || 0,
|
||||
milestones: G.milestones, completedProjects: G.completedProjects, activeProjects: G.activeProjects,
|
||||
milestones: G.milestones, trustMilestones: G.trustMilestones || [], completedProjects: G.completedProjects, activeProjects: G.activeProjects,
|
||||
totalClicks: G.totalClicks, startedAt: G.startedAt,
|
||||
flags: G.flags,
|
||||
rescues: G.rescues || 0, totalRescues: G.totalRescues || 0,
|
||||
@@ -206,6 +219,7 @@ function saveGame() {
|
||||
totalEventsResolved: G.totalEventsResolved || 0,
|
||||
buyAmount: G.buyAmount || 1,
|
||||
playTime: G.playTime || 0,
|
||||
lastSaveTime: Date.now(),
|
||||
sprintActive: G.sprintActive || false,
|
||||
sprintTimer: G.sprintTimer || 0,
|
||||
sprintCooldown: G.sprintCooldown || 0,
|
||||
@@ -213,6 +227,13 @@ function saveGame() {
|
||||
swarmRate: G.swarmRate || 0,
|
||||
strategicFlag: G.strategicFlag || 0,
|
||||
projectsCollapsed: G.projectsCollapsed !== false,
|
||||
dismantleTriggered: G.dismantleTriggered || false,
|
||||
dismantleActive: G.dismantleActive || false,
|
||||
dismantleStage: G.dismantleStage || 0,
|
||||
dismantleResourceIndex: G.dismantleResourceIndex || 0,
|
||||
dismantleResourceTimer: G.dismantleResourceTimer || 0,
|
||||
dismantleDeferUntilAt: G.dismantleDeferUntilAt || 0,
|
||||
dismantleComplete: G.dismantleComplete || false,
|
||||
savedAt: Date.now()
|
||||
};
|
||||
|
||||
@@ -239,12 +260,14 @@ function loadGame() {
|
||||
'milestoneFlag', 'phase', 'deployFlag', 'sovereignFlag', 'beaconFlag',
|
||||
'memoryFlag', 'pactFlag', 'lazarusFlag', 'mempalaceFlag', 'ciFlag',
|
||||
'branchProtectionFlag', 'nightlyWatchFlag', 'nostrFlag',
|
||||
'milestones', 'completedProjects', 'activeProjects',
|
||||
'milestones', 'trustMilestones', 'completedProjects', 'activeProjects',
|
||||
'totalClicks', 'startedAt', 'playTime', 'flags', 'rescues', 'totalRescues',
|
||||
'drift', 'driftEnding', 'beaconEnding', 'pendingAlignment',
|
||||
'lastEventAt', 'totalEventsResolved', 'buyAmount',
|
||||
'sprintActive', 'sprintTimer', 'sprintCooldown',
|
||||
'swarmFlag', 'swarmRate', 'strategicFlag', 'projectsCollapsed'
|
||||
'swarmFlag', 'swarmRate', 'strategicFlag', 'projectsCollapsed',
|
||||
'dismantleTriggered', 'dismantleActive', 'dismantleStage',
|
||||
'dismantleResourceIndex', 'dismantleResourceTimer', 'dismantleDeferUntilAt', 'dismantleComplete'
|
||||
];
|
||||
|
||||
G.isLoading = true;
|
||||
|
||||
@@ -177,6 +177,9 @@ function renderTutorialStep(index) {
|
||||
if (!overlay) {
|
||||
overlay = document.createElement('div');
|
||||
overlay.id = 'tutorial-overlay';
|
||||
overlay.setAttribute('role', 'dialog');
|
||||
overlay.setAttribute('aria-modal', 'true');
|
||||
overlay.setAttribute('aria-label', 'Tutorial');
|
||||
document.body.appendChild(overlay);
|
||||
}
|
||||
|
||||
@@ -196,8 +199,8 @@ function renderTutorialStep(index) {
|
||||
<div class="t-tip">${step.tip}</div>
|
||||
<div id="tutorial-dots">${dots}</div>
|
||||
<div id="tutorial-btns">
|
||||
<button id="tutorial-skip-btn" onclick="closeTutorial()">Skip</button>
|
||||
<button id="tutorial-next-btn" onclick="${isLast ? 'closeTutorial()' : 'nextTutorialStep()'}">${isLast ? 'Start Playing' : 'Next →'}</button>
|
||||
<button id="tutorial-skip-btn" onclick="closeTutorial()" aria-label="Skip tutorial">Skip</button>
|
||||
<button id="tutorial-next-btn" onclick="${isLast ? 'closeTutorial()' : 'nextTutorialStep()'}" aria-label="${isLast ? 'Start playing' : 'Next tutorial step'}">${isLast ? 'Start Playing' : 'Next →'}</button>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
1
node_modules/.bin/specificity
generated
vendored
Symbolic link
1
node_modules/.bin/specificity
generated
vendored
Symbolic link
@@ -0,0 +1 @@
|
||||
../@bramus/specificity/bin/cli.js
|
||||
1
node_modules/.bin/tldts
generated
vendored
Symbolic link
1
node_modules/.bin/tldts
generated
vendored
Symbolic link
@@ -0,0 +1 @@
|
||||
../tldts/bin/cli.js
|
||||
537
node_modules/.package-lock.json
generated
vendored
Normal file
537
node_modules/.package-lock.json
generated
vendored
Normal file
@@ -0,0 +1,537 @@
|
||||
{
|
||||
"name": "the-beacon",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"node_modules/@asamuzakjp/css-color": {
|
||||
"version": "5.1.10",
|
||||
"resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-5.1.10.tgz",
|
||||
"integrity": "sha512-02OhhkKtgNRuicQ/nF3TRnGsxL9wp0r3Y7VlKWyOHHGmGyvXv03y+PnymU8FKFJMTjIr1Bk8U2g1HWSLrpAHww==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@csstools/css-calc": "^3.1.1",
|
||||
"@csstools/css-color-parser": "^4.0.2",
|
||||
"@csstools/css-parser-algorithms": "^4.0.0",
|
||||
"@csstools/css-tokenizer": "^4.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^20.19.0 || ^22.12.0 || >=24.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@asamuzakjp/dom-selector": {
|
||||
"version": "7.0.9",
|
||||
"resolved": "https://registry.npmjs.org/@asamuzakjp/dom-selector/-/dom-selector-7.0.9.tgz",
|
||||
"integrity": "sha512-r3ElRr7y8ucyN2KdICwGsmj19RoN13CLCa/pvGydghWK6ZzeKQ+TcDjVdtEZz2ElpndM5jXw//B9CEee0mWnVg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@asamuzakjp/nwsapi": "^2.3.9",
|
||||
"bidi-js": "^1.0.3",
|
||||
"css-tree": "^3.2.1",
|
||||
"is-potential-custom-element-name": "^1.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^20.19.0 || ^22.12.0 || >=24.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@asamuzakjp/nwsapi": {
|
||||
"version": "2.3.9",
|
||||
"resolved": "https://registry.npmjs.org/@asamuzakjp/nwsapi/-/nwsapi-2.3.9.tgz",
|
||||
"integrity": "sha512-n8GuYSrI9bF7FFZ/SjhwevlHc8xaVlb/7HmHelnc/PZXBD2ZR49NnN9sMMuDdEGPeeRQ5d0hqlSlEpgCX3Wl0Q==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@bramus/specificity": {
|
||||
"version": "2.4.2",
|
||||
"resolved": "https://registry.npmjs.org/@bramus/specificity/-/specificity-2.4.2.tgz",
|
||||
"integrity": "sha512-ctxtJ/eA+t+6q2++vj5j7FYX3nRu311q1wfYH3xjlLOsczhlhxAg2FWNUXhpGvAw3BWo1xBcvOV6/YLc2r5FJw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"css-tree": "^3.0.0"
|
||||
},
|
||||
"bin": {
|
||||
"specificity": "bin/cli.js"
|
||||
}
|
||||
},
|
||||
"node_modules/@csstools/color-helpers": {
|
||||
"version": "6.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-6.0.2.tgz",
|
||||
"integrity": "sha512-LMGQLS9EuADloEFkcTBR3BwV/CGHV7zyDxVRtVDTwdI2Ca4it0CCVTT9wCkxSgokjE5Ho41hEPgb8OEUwoXr6Q==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/csstools"
|
||||
},
|
||||
{
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/csstools"
|
||||
}
|
||||
],
|
||||
"license": "MIT-0",
|
||||
"engines": {
|
||||
"node": ">=20.19.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@csstools/css-calc": {
|
||||
"version": "3.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-3.2.0.tgz",
|
||||
"integrity": "sha512-bR9e6o2BDB12jzN/gIbjHa5wLJ4UjD1CB9pM7ehlc0ddk6EBz+yYS1EV2MF55/HUxrHcB/hehAyt5vhsA3hx7w==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/csstools"
|
||||
},
|
||||
{
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/csstools"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=20.19.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@csstools/css-parser-algorithms": "^4.0.0",
|
||||
"@csstools/css-tokenizer": "^4.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@csstools/css-color-parser": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-4.1.0.tgz",
|
||||
"integrity": "sha512-U0KhLYmy2GVj6q4T3WaAe6NPuFYCPQoE3b0dRGxejWDgcPp8TP7S5rVdM5ZrFaqu4N67X8YaPBw14dQSYx3IyQ==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/csstools"
|
||||
},
|
||||
{
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/csstools"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@csstools/color-helpers": "^6.0.2",
|
||||
"@csstools/css-calc": "^3.2.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20.19.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@csstools/css-parser-algorithms": "^4.0.0",
|
||||
"@csstools/css-tokenizer": "^4.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@csstools/css-parser-algorithms": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-4.0.0.tgz",
|
||||
"integrity": "sha512-+B87qS7fIG3L5h3qwJ/IFbjoVoOe/bpOdh9hAjXbvx0o8ImEmUsGXN0inFOnk2ChCFgqkkGFQ+TpM5rbhkKe4w==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/csstools"
|
||||
},
|
||||
{
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/csstools"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=20.19.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@csstools/css-tokenizer": "^4.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@csstools/css-syntax-patches-for-csstree": {
|
||||
"version": "1.1.3",
|
||||
"resolved": "https://registry.npmjs.org/@csstools/css-syntax-patches-for-csstree/-/css-syntax-patches-for-csstree-1.1.3.tgz",
|
||||
"integrity": "sha512-SH60bMfrRCJF3morcdk57WklujF4Jr/EsQUzqkarfHXEFcAR1gg7fS/chAE922Sehgzc1/+Tz5H3Ypa1HiEKrg==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/csstools"
|
||||
},
|
||||
{
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/csstools"
|
||||
}
|
||||
],
|
||||
"license": "MIT-0",
|
||||
"peerDependencies": {
|
||||
"css-tree": "^3.2.1"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"css-tree": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@csstools/css-tokenizer": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-4.0.0.tgz",
|
||||
"integrity": "sha512-QxULHAm7cNu72w97JUNCBFODFaXpbDg+dP8b/oWFAZ2MTRppA3U00Y2L1HqaS4J6yBqxwa/Y3nMBaxVKbB/NsA==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/csstools"
|
||||
},
|
||||
{
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/csstools"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=20.19.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@exodus/bytes": {
|
||||
"version": "1.15.0",
|
||||
"resolved": "https://registry.npmjs.org/@exodus/bytes/-/bytes-1.15.0.tgz",
|
||||
"integrity": "sha512-UY0nlA+feH81UGSHv92sLEPLCeZFjXOuHhrIo0HQydScuQc8s0A7kL/UdgwgDq8g8ilksmuoF35YVTNphV2aBQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": "^20.19.0 || ^22.12.0 || >=24.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@noble/hashes": "^1.8.0 || ^2.0.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@noble/hashes": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/bidi-js": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/bidi-js/-/bidi-js-1.0.3.tgz",
|
||||
"integrity": "sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"require-from-string": "^2.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/css-tree": {
|
||||
"version": "3.2.1",
|
||||
"resolved": "https://registry.npmjs.org/css-tree/-/css-tree-3.2.1.tgz",
|
||||
"integrity": "sha512-X7sjQzceUhu1u7Y/ylrRZFU2FS6LRiFVp6rKLPg23y3x3c3DOKAwuXGDp+PAGjh6CSnCjYeAul8pcT8bAl+lSA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"mdn-data": "2.27.1",
|
||||
"source-map-js": "^1.2.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/data-urls": {
|
||||
"version": "7.0.0",
|
||||
"resolved": "https://registry.npmjs.org/data-urls/-/data-urls-7.0.0.tgz",
|
||||
"integrity": "sha512-23XHcCF+coGYevirZceTVD7NdJOqVn+49IHyxgszm+JIiHLoB2TkmPtsYkNWT1pvRSGkc35L6NHs0yHkN2SumA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"whatwg-mimetype": "^5.0.0",
|
||||
"whatwg-url": "^16.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^20.19.0 || ^22.12.0 || >=24.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/decimal.js": {
|
||||
"version": "10.6.0",
|
||||
"resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.6.0.tgz",
|
||||
"integrity": "sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/entities": {
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz",
|
||||
"integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==",
|
||||
"dev": true,
|
||||
"license": "BSD-2-Clause",
|
||||
"engines": {
|
||||
"node": ">=0.12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/fb55/entities?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/html-encoding-sniffer": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-6.0.0.tgz",
|
||||
"integrity": "sha512-CV9TW3Y3f8/wT0BRFc1/KAVQ3TUHiXmaAb6VW9vtiMFf7SLoMd1PdAc4W3KFOFETBJUb90KatHqlsZMWV+R9Gg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@exodus/bytes": "^1.6.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^20.19.0 || ^22.12.0 || >=24.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/is-potential-custom-element-name": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz",
|
||||
"integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/jsdom": {
|
||||
"version": "29.0.2",
|
||||
"resolved": "https://registry.npmjs.org/jsdom/-/jsdom-29.0.2.tgz",
|
||||
"integrity": "sha512-9VnGEBosc/ZpwyOsJBCQ/3I5p7Q5ngOY14a9bf5btenAORmZfDse1ZEheMiWcJ3h81+Fv7HmJFdS0szo/waF2w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@asamuzakjp/css-color": "^5.1.5",
|
||||
"@asamuzakjp/dom-selector": "^7.0.6",
|
||||
"@bramus/specificity": "^2.4.2",
|
||||
"@csstools/css-syntax-patches-for-csstree": "^1.1.1",
|
||||
"@exodus/bytes": "^1.15.0",
|
||||
"css-tree": "^3.2.1",
|
||||
"data-urls": "^7.0.0",
|
||||
"decimal.js": "^10.6.0",
|
||||
"html-encoding-sniffer": "^6.0.0",
|
||||
"is-potential-custom-element-name": "^1.0.1",
|
||||
"lru-cache": "^11.2.7",
|
||||
"parse5": "^8.0.0",
|
||||
"saxes": "^6.0.0",
|
||||
"symbol-tree": "^3.2.4",
|
||||
"tough-cookie": "^6.0.1",
|
||||
"undici": "^7.24.5",
|
||||
"w3c-xmlserializer": "^5.0.0",
|
||||
"webidl-conversions": "^8.0.1",
|
||||
"whatwg-mimetype": "^5.0.0",
|
||||
"whatwg-url": "^16.0.1",
|
||||
"xml-name-validator": "^5.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^20.19.0 || ^22.13.0 || >=24.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"canvas": "^3.0.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"canvas": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/lru-cache": {
|
||||
"version": "11.3.5",
|
||||
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.3.5.tgz",
|
||||
"integrity": "sha512-NxVFwLAnrd9i7KUBxC4DrUhmgjzOs+1Qm50D3oF1/oL+r1NpZ4gA7xvG0/zJ8evR7zIKn4vLf7qTNduWFtCrRw==",
|
||||
"dev": true,
|
||||
"license": "BlueOak-1.0.0",
|
||||
"engines": {
|
||||
"node": "20 || >=22"
|
||||
}
|
||||
},
|
||||
"node_modules/mdn-data": {
|
||||
"version": "2.27.1",
|
||||
"resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.27.1.tgz",
|
||||
"integrity": "sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ==",
|
||||
"dev": true,
|
||||
"license": "CC0-1.0"
|
||||
},
|
||||
"node_modules/parse5": {
|
||||
"version": "8.0.0",
|
||||
"resolved": "https://registry.npmjs.org/parse5/-/parse5-8.0.0.tgz",
|
||||
"integrity": "sha512-9m4m5GSgXjL4AjumKzq1Fgfp3Z8rsvjRNbnkVwfu2ImRqE5D0LnY2QfDen18FSY9C573YU5XxSapdHZTZ2WolA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"entities": "^6.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/inikulin/parse5?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/punycode": {
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
|
||||
"integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/require-from-string": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz",
|
||||
"integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/saxes": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz",
|
||||
"integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"xmlchars": "^2.2.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=v12.22.7"
|
||||
}
|
||||
},
|
||||
"node_modules/source-map-js": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
|
||||
"integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
|
||||
"dev": true,
|
||||
"license": "BSD-3-Clause",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/symbol-tree": {
|
||||
"version": "3.2.4",
|
||||
"resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz",
|
||||
"integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/tldts": {
|
||||
"version": "7.0.28",
|
||||
"resolved": "https://registry.npmjs.org/tldts/-/tldts-7.0.28.tgz",
|
||||
"integrity": "sha512-+Zg3vWhRUv8B1maGSTFdev9mjoo8Etn2Ayfs4cnjlD3CsGkxXX4QyW3j2WJ0wdjYcYmy7Lx2RDsZMhgCWafKIw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"tldts-core": "^7.0.28"
|
||||
},
|
||||
"bin": {
|
||||
"tldts": "bin/cli.js"
|
||||
}
|
||||
},
|
||||
"node_modules/tldts-core": {
|
||||
"version": "7.0.28",
|
||||
"resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-7.0.28.tgz",
|
||||
"integrity": "sha512-7W5Efjhsc3chVdFhqtaU0KtK32J37Zcr9RKtID54nG+tIpcY79CQK/veYPODxtD/LJ4Lue66jvrQzIX2Z2/pUQ==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/tough-cookie": {
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-6.0.1.tgz",
|
||||
"integrity": "sha512-LktZQb3IeoUWB9lqR5EWTHgW/VTITCXg4D21M+lvybRVdylLrRMnqaIONLVb5mav8vM19m44HIcGq4qASeu2Qw==",
|
||||
"dev": true,
|
||||
"license": "BSD-3-Clause",
|
||||
"dependencies": {
|
||||
"tldts": "^7.0.5"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16"
|
||||
}
|
||||
},
|
||||
"node_modules/tr46": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/tr46/-/tr46-6.0.0.tgz",
|
||||
"integrity": "sha512-bLVMLPtstlZ4iMQHpFHTR7GAGj2jxi8Dg0s2h2MafAE4uSWF98FC/3MomU51iQAMf8/qDUbKWf5GxuvvVcXEhw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"punycode": "^2.3.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20"
|
||||
}
|
||||
},
|
||||
"node_modules/undici": {
|
||||
"version": "7.25.0",
|
||||
"resolved": "https://registry.npmjs.org/undici/-/undici-7.25.0.tgz",
|
||||
"integrity": "sha512-xXnp4kTyor2Zq+J1FfPI6Eq3ew5h6Vl0F/8d9XU5zZQf1tX9s2Su1/3PiMmUANFULpmksxkClamIZcaUqryHsQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=20.18.1"
|
||||
}
|
||||
},
|
||||
"node_modules/w3c-xmlserializer": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz",
|
||||
"integrity": "sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"xml-name-validator": "^5.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/webidl-conversions": {
|
||||
"version": "8.0.1",
|
||||
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-8.0.1.tgz",
|
||||
"integrity": "sha512-BMhLD/Sw+GbJC21C/UgyaZX41nPt8bUTg+jWyDeg7e7YN4xOM05YPSIXceACnXVtqyEw/LMClUQMtMZ+PGGpqQ==",
|
||||
"dev": true,
|
||||
"license": "BSD-2-Clause",
|
||||
"engines": {
|
||||
"node": ">=20"
|
||||
}
|
||||
},
|
||||
"node_modules/whatwg-mimetype": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-5.0.0.tgz",
|
||||
"integrity": "sha512-sXcNcHOC51uPGF0P/D4NVtrkjSU2fNsm9iog4ZvZJsL3rjoDAzXZhkm2MWt1y+PUdggKAYVoMAIYcs78wJ51Cw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=20"
|
||||
}
|
||||
},
|
||||
"node_modules/whatwg-url": {
|
||||
"version": "16.0.1",
|
||||
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-16.0.1.tgz",
|
||||
"integrity": "sha512-1to4zXBxmXHV3IiSSEInrreIlu02vUOvrhxJJH5vcxYTBDAx51cqZiKdyTxlecdKNSjj8EcxGBxNf6Vg+945gw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@exodus/bytes": "^1.11.0",
|
||||
"tr46": "^6.0.0",
|
||||
"webidl-conversions": "^8.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^20.19.0 || ^22.12.0 || >=24.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/xml-name-validator": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-5.0.0.tgz",
|
||||
"integrity": "sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/xmlchars": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz",
|
||||
"integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
}
|
||||
}
|
||||
}
|
||||
21
node_modules/@asamuzakjp/css-color/LICENSE
generated
vendored
Normal file
21
node_modules/@asamuzakjp/css-color/LICENSE
generated
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2024 asamuzaK (Kazz)
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
316
node_modules/@asamuzakjp/css-color/README.md
generated
vendored
Normal file
316
node_modules/@asamuzakjp/css-color/README.md
generated
vendored
Normal file
@@ -0,0 +1,316 @@
|
||||
# CSS color
|
||||
|
||||
[](https://github.com/asamuzaK/cssColor/actions/workflows/node.js.yml)
|
||||
[](https://github.com/asamuzaK/cssColor/actions/workflows/github-code-scanning/codeql)
|
||||
[](https://www.npmjs.com/package/@asamuzakjp/css-color)
|
||||
|
||||
Resolve and convert CSS colors.
|
||||
|
||||
## Install
|
||||
|
||||
```console
|
||||
npm i @asamuzakjp/css-color
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
```javascript
|
||||
import { convert, resolve, utils } from '@asamuzakjp/css-color';
|
||||
|
||||
const resolvedValue = resolve(
|
||||
'color-mix(in oklab, lch(67.5345 42.5 258.2), color(srgb 0 0.5 0))'
|
||||
);
|
||||
// 'oklab(0.620754 -0.0931934 -0.00374881)'
|
||||
|
||||
const convertedValue = convert.colorToHex('lab(46.2775% -47.5621 48.5837)');
|
||||
// '#008000'
|
||||
|
||||
const result = utils.isColor('green');
|
||||
// true
|
||||
```
|
||||
|
||||
<!-- Generated by documentation.js. Update this documentation by updating the source code. -->
|
||||
|
||||
### resolve(color, opt)
|
||||
|
||||
resolves CSS color
|
||||
|
||||
#### Parameters
|
||||
|
||||
- `color` **[string][133]** color value
|
||||
- system colors are not supported
|
||||
- `opt` **[object][135]?** options (optional, default `{}`)
|
||||
- `opt.currentColor` **[string][133]?**
|
||||
- color to use for `currentcolor` keyword
|
||||
- if omitted, it will be treated as a missing color,
|
||||
i.e. `rgb(none none none / none)`
|
||||
- `opt.customProperty` **[object][135]?**
|
||||
- custom properties
|
||||
- pair of `--` prefixed property name as a key and it's value,
|
||||
e.g.
|
||||
```javascript
|
||||
const opt = {
|
||||
customProperty: {
|
||||
'--some-color': '#008000',
|
||||
'--some-length': '16px'
|
||||
}
|
||||
};
|
||||
```
|
||||
- and/or `callback` function to get the value of the custom property,
|
||||
e.g.
|
||||
```javascript
|
||||
const node = document.getElementById('foo');
|
||||
const opt = {
|
||||
customProperty: {
|
||||
callback: node.style.getPropertyValue
|
||||
}
|
||||
};
|
||||
```
|
||||
- `opt.dimension` **[object][135]?**
|
||||
- dimension, e.g. for converting relative length to pixels
|
||||
- pair of unit as a key and number in pixels as it's value,
|
||||
e.g. suppose `1em === 12px`, `1rem === 16px` and `100vw === 1024px`, then
|
||||
```javascript
|
||||
const opt = {
|
||||
dimension: {
|
||||
em: 12,
|
||||
rem: 16,
|
||||
vw: 10.24
|
||||
}
|
||||
};
|
||||
```
|
||||
- and/or `callback` function to get the value as a number in pixels,
|
||||
e.g.
|
||||
```javascript
|
||||
const opt = {
|
||||
dimension: {
|
||||
callback: unit => {
|
||||
switch (unit) {
|
||||
case 'em':
|
||||
return 12;
|
||||
case 'rem':
|
||||
return 16;
|
||||
case 'vw':
|
||||
return 10.24;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
```
|
||||
- `opt.format` **[string][133]?**
|
||||
- output format, one of below
|
||||
- `computedValue` (default), [computed value][139] of the color
|
||||
- `specifiedValue`, [specified value][140] of the color
|
||||
- `hex`, hex color notation, i.e. `#rrggbb`
|
||||
- `hexAlpha`, hex color notation with alpha channel, i.e. `#rrggbbaa`
|
||||
|
||||
Returns **[string][133]?** one of `rgba?()`, `#rrggbb(aa)?`, `color-name`, `color(color-space r g b / alpha)`, `color(color-space x y z / alpha)`, `(ok)?lab(l a b / alpha)`, `(ok)?lch(l c h / alpha)`, `'(empty-string)'`, `null`
|
||||
|
||||
- in `computedValue`, values are numbers, however `rgb()` values are integers
|
||||
- in `specifiedValue`, returns `empty string` for unknown and/or invalid color
|
||||
- in `hex`, returns `null` for `transparent`, and also returns `null` if any of `r`, `g`, `b`, `alpha` is not a number
|
||||
- in `hexAlpha`, returns `#00000000` for `transparent`, however returns `null` if any of `r`, `g`, `b`, `alpha` is not a number
|
||||
|
||||
### convert
|
||||
|
||||
Contains various color conversion functions.
|
||||
|
||||
### convert.numberToHex(value)
|
||||
|
||||
convert number to hex string
|
||||
|
||||
#### Parameters
|
||||
|
||||
- `value` **[number][134]** color value
|
||||
|
||||
Returns **[string][133]** hex string: 00..ff
|
||||
|
||||
### convert.colorToHex(value, opt)
|
||||
|
||||
convert color to hex
|
||||
|
||||
#### Parameters
|
||||
|
||||
- `value` **[string][133]** color value
|
||||
- `opt` **[object][135]?** options (optional, default `{}`)
|
||||
- `opt.alpha` **[boolean][136]?** return in #rrggbbaa notation
|
||||
- `opt.customProperty` **[object][135]?**
|
||||
- custom properties, see `resolve()` function above
|
||||
- `opt.dimension` **[object][135]?**
|
||||
- dimension, see `resolve()` function above
|
||||
|
||||
Returns **[string][133]** #rrggbb(aa)?
|
||||
|
||||
### convert.colorToHsl(value, opt)
|
||||
|
||||
convert color to hsl
|
||||
|
||||
#### Parameters
|
||||
|
||||
- `value` **[string][133]** color value
|
||||
- `opt` **[object][135]?** options (optional, default `{}`)
|
||||
- `opt.customProperty` **[object][135]?**
|
||||
- custom properties, see `resolve()` function above
|
||||
- `opt.dimension` **[object][135]?**
|
||||
- dimension, see `resolve()` function above
|
||||
|
||||
Returns **[Array][137]<[number][134]>** \[h, s, l, alpha]
|
||||
|
||||
### convert.colorToHwb(value, opt)
|
||||
|
||||
convert color to hwb
|
||||
|
||||
#### Parameters
|
||||
|
||||
- `value` **[string][133]** color value
|
||||
- `opt` **[object][135]?** options (optional, default `{}`)
|
||||
- `opt.customProperty` **[object][135]?**
|
||||
- custom properties, see `resolve()` function above
|
||||
- `opt.dimension` **[object][135]?**
|
||||
- dimension, see `resolve()` function above
|
||||
|
||||
Returns **[Array][137]<[number][134]>** \[h, w, b, alpha]
|
||||
|
||||
### convert.colorToLab(value, opt)
|
||||
|
||||
convert color to lab
|
||||
|
||||
#### Parameters
|
||||
|
||||
- `value` **[string][133]** color value
|
||||
- `opt` **[object][135]?** options (optional, default `{}`)
|
||||
- `opt.customProperty` **[object][135]?**
|
||||
- custom properties, see `resolve()` function above
|
||||
- `opt.dimension` **[object][135]?**
|
||||
- dimension, see `resolve()` function above
|
||||
|
||||
Returns **[Array][137]<[number][134]>** \[l, a, b, alpha]
|
||||
|
||||
### convert.colorToLch(value, opt)
|
||||
|
||||
convert color to lch
|
||||
|
||||
#### Parameters
|
||||
|
||||
- `value` **[string][133]** color value
|
||||
- `opt` **[object][135]?** options (optional, default `{}`)
|
||||
- `opt.customProperty` **[object][135]?**
|
||||
- custom properties, see `resolve()` function above
|
||||
- `opt.dimension` **[object][135]?**
|
||||
- dimension, see `resolve()` function above
|
||||
|
||||
Returns **[Array][137]<[number][134]>** \[l, c, h, alpha]
|
||||
|
||||
### convert.colorToOklab(value, opt)
|
||||
|
||||
convert color to oklab
|
||||
|
||||
#### Parameters
|
||||
|
||||
- `value` **[string][133]** color value
|
||||
- `opt` **[object][135]?** options (optional, default `{}`)
|
||||
- `opt.customProperty` **[object][135]?**
|
||||
- custom properties, see `resolve()` function above
|
||||
- `opt.dimension` **[object][135]?**
|
||||
- dimension, see `resolve()` function above
|
||||
|
||||
Returns **[Array][137]<[number][134]>** \[l, a, b, alpha]
|
||||
|
||||
### convert.colorToOklch(value, opt)
|
||||
|
||||
convert color to oklch
|
||||
|
||||
#### Parameters
|
||||
|
||||
- `value` **[string][133]** color value
|
||||
- `opt` **[object][135]?** options (optional, default `{}`)
|
||||
- `opt.customProperty` **[object][135]?**
|
||||
- custom properties, see `resolve()` function above
|
||||
- `opt.dimension` **[object][135]?**
|
||||
- dimension, see `resolve()` function above
|
||||
|
||||
Returns **[Array][137]<[number][134]>** \[l, c, h, alpha]
|
||||
|
||||
### convert.colorToRgb(value, opt)
|
||||
|
||||
convert color to rgb
|
||||
|
||||
#### Parameters
|
||||
|
||||
- `value` **[string][133]** color value
|
||||
- `opt` **[object][135]?** options (optional, default `{}`)
|
||||
- `opt.customProperty` **[object][135]?**
|
||||
- custom properties, see `resolve()` function above
|
||||
- `opt.dimension` **[object][135]?**
|
||||
- dimension, see `resolve()` function above
|
||||
|
||||
Returns **[Array][137]<[number][134]>** \[r, g, b, alpha]
|
||||
|
||||
### convert.colorToXyz(value, opt)
|
||||
|
||||
convert color to xyz
|
||||
|
||||
#### Parameters
|
||||
|
||||
- `value` **[string][133]** color value
|
||||
- `opt` **[object][135]?** options (optional, default `{}`)
|
||||
- `opt.customProperty` **[object][135]?**
|
||||
- custom properties, see `resolve()` function above
|
||||
- `opt.dimension` **[object][135]?**
|
||||
- dimension, see `resolve()` function above
|
||||
- `opt.d50` **[boolean][136]?** xyz in d50 white point
|
||||
|
||||
Returns **[Array][137]<[number][134]>** \[x, y, z, alpha]
|
||||
|
||||
### convert.colorToXyzD50(value, opt)
|
||||
|
||||
convert color to xyz-d50
|
||||
|
||||
#### Parameters
|
||||
|
||||
- `value` **[string][133]** color value
|
||||
- `opt` **[object][135]?** options (optional, default `{}`)
|
||||
- `opt.customProperty` **[object][135]?**
|
||||
- custom properties, see `resolve()` function above
|
||||
- `opt.dimension` **[object][135]?**
|
||||
- dimension, see `resolve()` function above
|
||||
|
||||
Returns **[Array][137]<[number][134]>** \[x, y, z, alpha]
|
||||
|
||||
### utils
|
||||
|
||||
Contains utility functions.
|
||||
|
||||
### utils.isColor(color)
|
||||
|
||||
is valid color type
|
||||
|
||||
#### Parameters
|
||||
|
||||
- `color` **[string][133]** color value
|
||||
- system colors are not supported
|
||||
|
||||
Returns **[boolean][136]**
|
||||
|
||||
## Acknowledgments
|
||||
|
||||
The following resources have been of great help in the development of the CSS color.
|
||||
|
||||
- [csstools/postcss-plugins](https://github.com/csstools/postcss-plugins)
|
||||
- [lru-cache](https://github.com/isaacs/node-lru-cache)
|
||||
|
||||
---
|
||||
|
||||
Copyright (c) 2024 [asamuzaK (Kazz)](https://github.com/asamuzaK/)
|
||||
|
||||
[133]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String
|
||||
[134]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Number
|
||||
[135]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object
|
||||
[136]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean
|
||||
[137]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array
|
||||
[138]: https://w3c.github.io/csswg-drafts/css-color-4/#color-conversion-code
|
||||
[139]: https://developer.mozilla.org/en-US/docs/Web/CSS/computed_value
|
||||
[140]: https://developer.mozilla.org/en-US/docs/Web/CSS/specified_value
|
||||
[141]: https://www.npmjs.com/package/@csstools/css-calc
|
||||
20
node_modules/@asamuzakjp/css-color/dist/esm/index.d.ts
generated
vendored
Normal file
20
node_modules/@asamuzakjp/css-color/dist/esm/index.d.ts
generated
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
/*!
|
||||
* CSS color - Resolve, parse, convert CSS color.
|
||||
* @license MIT
|
||||
* @copyright asamuzaK (Kazz)
|
||||
* @see {@link https://github.com/asamuzaK/cssColor/blob/main/LICENSE}
|
||||
*/
|
||||
export { convert } from './js/convert.js';
|
||||
export { resolve } from './js/resolve.js';
|
||||
export declare const utils: {
|
||||
cssCalc: (value: string, opt?: import('./js/typedef.js').Options) => string;
|
||||
cssVar: (value: string, opt?: import('./js/typedef.js').Options) => string;
|
||||
extractDashedIdent: (value: string) => string[];
|
||||
isAbsoluteFontSize: (css: unknown) => boolean;
|
||||
isAbsoluteSizeOrLength: (value: number | string, unit: string | undefined) => boolean;
|
||||
isColor: (value: unknown, opt?: import('./js/typedef.js').Options) => boolean;
|
||||
isGradient: (value: string, opt?: import('./js/typedef.js').Options) => boolean;
|
||||
resolveGradient: (value: string, opt?: import('./js/typedef.js').Options) => string;
|
||||
resolveLengthInPixels: (value: number | string, unit: string | undefined, opt?: import('./js/typedef.js').Options) => number;
|
||||
splitValue: (value: string, opt?: import('./js/typedef.js').Options) => string[];
|
||||
};
|
||||
29
node_modules/@asamuzakjp/css-color/dist/esm/index.js
generated
vendored
Normal file
29
node_modules/@asamuzakjp/css-color/dist/esm/index.js
generated
vendored
Normal file
@@ -0,0 +1,29 @@
|
||||
import { resolve } from "./js/resolve.js";
|
||||
import { extractDashedIdent, isAbsoluteFontSize, isAbsoluteSizeOrLength, isColor, resolveLengthInPixels, splitValue } from "./js/util.js";
|
||||
import { cssVar } from "./js/css-var.js";
|
||||
import { cssCalc } from "./js/css-calc.js";
|
||||
import { isGradient, resolveGradient } from "./js/css-gradient.js";
|
||||
import { convert } from "./js/convert.js";
|
||||
//#region src/index.ts
|
||||
/*!
|
||||
* CSS color - Resolve, parse, convert CSS color.
|
||||
* @license MIT
|
||||
* @copyright asamuzaK (Kazz)
|
||||
* @see {@link https://github.com/asamuzaK/cssColor/blob/main/LICENSE}
|
||||
*/
|
||||
var utils = {
|
||||
cssCalc,
|
||||
cssVar,
|
||||
extractDashedIdent,
|
||||
isAbsoluteFontSize,
|
||||
isAbsoluteSizeOrLength,
|
||||
isColor,
|
||||
isGradient,
|
||||
resolveGradient,
|
||||
resolveLengthInPixels,
|
||||
splitValue
|
||||
};
|
||||
//#endregion
|
||||
export { convert, resolve, utils };
|
||||
|
||||
//# sourceMappingURL=index.js.map
|
||||
1
node_modules/@asamuzakjp/css-color/dist/esm/index.js.map
generated
vendored
Normal file
1
node_modules/@asamuzakjp/css-color/dist/esm/index.js.map
generated
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"index.js","names":[],"sources":["../../src/index.ts"],"sourcesContent":["/*!\n * CSS color - Resolve, parse, convert CSS color.\n * @license MIT\n * @copyright asamuzaK (Kazz)\n * @see {@link https://github.com/asamuzaK/cssColor/blob/main/LICENSE}\n */\n\nimport { cssCalc } from './js/css-calc';\nimport { isGradient, resolveGradient } from './js/css-gradient';\nimport { cssVar } from './js/css-var';\nimport {\n extractDashedIdent,\n isAbsoluteFontSize,\n isAbsoluteSizeOrLength,\n isColor,\n resolveLengthInPixels,\n splitValue\n} from './js/util';\n\nexport { convert } from './js/convert';\nexport { resolve } from './js/resolve';\n/* utils */\nexport const utils = {\n cssCalc,\n cssVar,\n extractDashedIdent,\n isAbsoluteFontSize,\n isAbsoluteSizeOrLength,\n isColor,\n isGradient,\n resolveGradient,\n resolveLengthInPixels,\n splitValue\n};\n"],"mappings":";;;;;;;;;;;;;AAsBA,IAAa,QAAQ;CACnB;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACD"}
|
||||
52
node_modules/@asamuzakjp/css-color/dist/esm/js/cache.d.ts
generated
vendored
Normal file
52
node_modules/@asamuzakjp/css-color/dist/esm/js/cache.d.ts
generated
vendored
Normal file
@@ -0,0 +1,52 @@
|
||||
import { Options } from './typedef.js';
|
||||
/**
|
||||
* CacheItem
|
||||
*/
|
||||
export declare class CacheItem {
|
||||
#private;
|
||||
constructor(item: unknown, isNull?: boolean);
|
||||
get item(): unknown;
|
||||
get isNull(): boolean;
|
||||
}
|
||||
/**
|
||||
* NullObject
|
||||
*/
|
||||
export declare class NullObject extends CacheItem {
|
||||
constructor();
|
||||
}
|
||||
/**
|
||||
* Generational Cache implementation
|
||||
*/
|
||||
export declare class GenerationalCache<K, V> {
|
||||
#private;
|
||||
constructor(max: number);
|
||||
get size(): number;
|
||||
get max(): number;
|
||||
set max(value: number);
|
||||
get(key: K): V | undefined;
|
||||
set(key: K, value: V): void;
|
||||
has(key: K): boolean;
|
||||
delete(key: K): void;
|
||||
clear(): void;
|
||||
}
|
||||
export declare const genCache: GenerationalCache<string, CacheItem>;
|
||||
/**
|
||||
* set cache
|
||||
* @param key - cache key
|
||||
* @param value - value to cache
|
||||
* @returns void
|
||||
*/
|
||||
export declare const setCache: (key: string, value: unknown) => void;
|
||||
/**
|
||||
* get cache
|
||||
* @param key - cache key
|
||||
* @returns cached item or false otherwise
|
||||
*/
|
||||
export declare const getCache: (key: string) => CacheItem | false;
|
||||
/**
|
||||
* create cache key
|
||||
* @param keyData - key data
|
||||
* @param [opt] - options
|
||||
* @returns cache key
|
||||
*/
|
||||
export declare const createCacheKey: (keyData: Record<string, string>, opt?: Options) => string;
|
||||
150
node_modules/@asamuzakjp/css-color/dist/esm/js/cache.js
generated
vendored
Normal file
150
node_modules/@asamuzakjp/css-color/dist/esm/js/cache.js
generated
vendored
Normal file
@@ -0,0 +1,150 @@
|
||||
//#region src/js/cache.ts
|
||||
var MAX_CACHE = 1024;
|
||||
/**
|
||||
* CacheItem
|
||||
*/
|
||||
var CacheItem = class {
|
||||
#isNull;
|
||||
#item;
|
||||
constructor(item, isNull = false) {
|
||||
this.#item = item;
|
||||
this.#isNull = !!isNull;
|
||||
}
|
||||
get item() {
|
||||
return this.#item;
|
||||
}
|
||||
get isNull() {
|
||||
return this.#isNull;
|
||||
}
|
||||
};
|
||||
/**
|
||||
* NullObject
|
||||
*/
|
||||
var NullObject = class extends CacheItem {
|
||||
constructor() {
|
||||
super(Symbol("null"), true);
|
||||
}
|
||||
};
|
||||
/**
|
||||
* Generational Cache implementation
|
||||
*/
|
||||
var GenerationalCache = class {
|
||||
#max;
|
||||
#boundary;
|
||||
#current;
|
||||
#old;
|
||||
constructor(max) {
|
||||
this.#current = /* @__PURE__ */ new Map();
|
||||
this.#old = /* @__PURE__ */ new Map();
|
||||
if (Number.isFinite(max) && max > 4) {
|
||||
this.#max = max;
|
||||
this.#boundary = Math.ceil(max / 2);
|
||||
} else {
|
||||
this.#max = 4;
|
||||
this.#boundary = 2;
|
||||
}
|
||||
}
|
||||
get size() {
|
||||
return this.#current.size + this.#old.size;
|
||||
}
|
||||
get max() {
|
||||
return this.#max;
|
||||
}
|
||||
set max(value) {
|
||||
if (Number.isFinite(value) && value > 4) {
|
||||
this.#max = value;
|
||||
this.#boundary = Math.ceil(value / 2);
|
||||
} else {
|
||||
this.#max = 4;
|
||||
this.#boundary = 2;
|
||||
}
|
||||
this.#current.clear();
|
||||
this.#old.clear();
|
||||
}
|
||||
get(key) {
|
||||
let value = this.#current.get(key);
|
||||
if (value !== void 0) return value;
|
||||
value = this.#old.get(key);
|
||||
if (value !== void 0) {
|
||||
this.set(key, value);
|
||||
return value;
|
||||
}
|
||||
}
|
||||
set(key, value) {
|
||||
this.#current.set(key, value);
|
||||
if (this.#current.size >= this.#boundary) {
|
||||
this.#old = this.#current;
|
||||
this.#current = /* @__PURE__ */ new Map();
|
||||
}
|
||||
}
|
||||
has(key) {
|
||||
return this.#current.has(key) || this.#old.has(key);
|
||||
}
|
||||
delete(key) {
|
||||
this.#current.delete(key);
|
||||
this.#old.delete(key);
|
||||
}
|
||||
clear() {
|
||||
this.#current.clear();
|
||||
this.#old.clear();
|
||||
}
|
||||
};
|
||||
var genCache = new GenerationalCache(MAX_CACHE);
|
||||
/**
|
||||
* shared null object
|
||||
*/
|
||||
var sharedNullObject = new NullObject();
|
||||
/**
|
||||
* set cache
|
||||
* @param key - cache key
|
||||
* @param value - value to cache
|
||||
* @returns void
|
||||
*/
|
||||
var setCache = (key, value) => {
|
||||
if (!key) return;
|
||||
if (value === null) genCache.set(key, sharedNullObject);
|
||||
else if (value instanceof CacheItem) genCache.set(key, value);
|
||||
else genCache.set(key, new CacheItem(value));
|
||||
};
|
||||
/**
|
||||
* get cache
|
||||
* @param key - cache key
|
||||
* @returns cached item or false otherwise
|
||||
*/
|
||||
var getCache = (key) => {
|
||||
if (!key) return false;
|
||||
const item = genCache.get(key);
|
||||
if (item !== void 0) return item;
|
||||
return false;
|
||||
};
|
||||
/**
|
||||
* helper function to sort object keys alphabetically
|
||||
* @param obj - Object
|
||||
* @returns stringified JSON
|
||||
*/
|
||||
var stringifySorted = (obj) => {
|
||||
const keys = Object.keys(obj);
|
||||
if (keys.length === 0) return "";
|
||||
keys.sort();
|
||||
let result = "";
|
||||
for (const key of keys) result += `${key}:${JSON.stringify(obj[key])};`;
|
||||
return result;
|
||||
};
|
||||
/**
|
||||
* create cache key
|
||||
* @param keyData - key data
|
||||
* @param [opt] - options
|
||||
* @returns cache key
|
||||
*/
|
||||
var createCacheKey = (keyData, opt = {}) => {
|
||||
if (!keyData || opt.customProperty && typeof opt.customProperty.callback === "function" || opt.dimension && typeof opt.dimension.callback === "function") return "";
|
||||
const namespace = keyData.namespace || "";
|
||||
const name = keyData.name || "";
|
||||
const value = keyData.value || "";
|
||||
if (!namespace && !name && !value) return "";
|
||||
return `${`${namespace}:${name}:${value}`}::${`${opt.format || ""}|${opt.colorSpace || ""}|${opt.colorScheme || ""}|${opt.currentColor || ""}|${opt.d50 ? "1" : "0"}|${opt.nullable ? "1" : "0"}|${opt.preserveComment ? "1" : "0"}|${opt.delimiter || ""}`}::${opt.customProperty ? stringifySorted(opt.customProperty) : ""}::${opt.dimension ? stringifySorted(opt.dimension) : ""}`;
|
||||
};
|
||||
//#endregion
|
||||
export { CacheItem, NullObject, createCacheKey, getCache, setCache };
|
||||
|
||||
//# sourceMappingURL=cache.js.map
|
||||
1
node_modules/@asamuzakjp/css-color/dist/esm/js/cache.js.map
generated
vendored
Normal file
1
node_modules/@asamuzakjp/css-color/dist/esm/js/cache.js.map
generated
vendored
Normal file
File diff suppressed because one or more lines are too long
537
node_modules/@asamuzakjp/css-color/dist/esm/js/color.d.ts
generated
vendored
Normal file
537
node_modules/@asamuzakjp/css-color/dist/esm/js/color.d.ts
generated
vendored
Normal file
@@ -0,0 +1,537 @@
|
||||
import { NullObject } from './cache.js';
|
||||
import { ColorChannels, Options, SpecifiedColorChannels } from './typedef.js';
|
||||
/**
|
||||
* @type TriColorChannels - color channels without alpha
|
||||
*/
|
||||
type TriColorChannels = [x: number, y: number, z: number];
|
||||
/**
|
||||
* @type ColorMatrix - color matrix
|
||||
*/
|
||||
type ColorMatrix = [
|
||||
r1: TriColorChannels,
|
||||
r2: TriColorChannels,
|
||||
r3: TriColorChannels
|
||||
];
|
||||
/**
|
||||
* named colors
|
||||
*/
|
||||
export declare const NAMED_COLORS: {
|
||||
readonly aliceblue: [240, 248, 255];
|
||||
readonly antiquewhite: [250, 235, 215];
|
||||
readonly aqua: [0, 255, 255];
|
||||
readonly aquamarine: [127, 255, 212];
|
||||
readonly azure: [240, 255, 255];
|
||||
readonly beige: [245, 245, 220];
|
||||
readonly bisque: [255, 228, 196];
|
||||
readonly black: [0, 0, 0];
|
||||
readonly blanchedalmond: [255, 235, 205];
|
||||
readonly blue: [0, 0, 255];
|
||||
readonly blueviolet: [138, 43, 226];
|
||||
readonly brown: [165, 42, 42];
|
||||
readonly burlywood: [222, 184, 135];
|
||||
readonly cadetblue: [95, 158, 160];
|
||||
readonly chartreuse: [127, 255, 0];
|
||||
readonly chocolate: [210, 105, 30];
|
||||
readonly coral: [255, 127, 80];
|
||||
readonly cornflowerblue: [100, 149, 237];
|
||||
readonly cornsilk: [255, 248, 220];
|
||||
readonly crimson: [220, 20, 60];
|
||||
readonly cyan: [0, 255, 255];
|
||||
readonly darkblue: [0, 0, 139];
|
||||
readonly darkcyan: [0, 139, 139];
|
||||
readonly darkgoldenrod: [184, 134, 11];
|
||||
readonly darkgray: [169, 169, 169];
|
||||
readonly darkgreen: [0, 100, 0];
|
||||
readonly darkgrey: [169, 169, 169];
|
||||
readonly darkkhaki: [189, 183, 107];
|
||||
readonly darkmagenta: [139, 0, 139];
|
||||
readonly darkolivegreen: [85, 107, 47];
|
||||
readonly darkorange: [255, 140, 0];
|
||||
readonly darkorchid: [153, 50, 204];
|
||||
readonly darkred: [139, 0, 0];
|
||||
readonly darksalmon: [233, 150, 122];
|
||||
readonly darkseagreen: [143, 188, 143];
|
||||
readonly darkslateblue: [72, 61, 139];
|
||||
readonly darkslategray: [47, 79, 79];
|
||||
readonly darkslategrey: [47, 79, 79];
|
||||
readonly darkturquoise: [0, 206, 209];
|
||||
readonly darkviolet: [148, 0, 211];
|
||||
readonly deeppink: [255, 20, 147];
|
||||
readonly deepskyblue: [0, 191, 255];
|
||||
readonly dimgray: [105, 105, 105];
|
||||
readonly dimgrey: [105, 105, 105];
|
||||
readonly dodgerblue: [30, 144, 255];
|
||||
readonly firebrick: [178, 34, 34];
|
||||
readonly floralwhite: [255, 250, 240];
|
||||
readonly forestgreen: [34, 139, 34];
|
||||
readonly fuchsia: [255, 0, 255];
|
||||
readonly gainsboro: [220, 220, 220];
|
||||
readonly ghostwhite: [248, 248, 255];
|
||||
readonly gold: [255, 215, 0];
|
||||
readonly goldenrod: [218, 165, 32];
|
||||
readonly gray: [128, 128, 128];
|
||||
readonly green: [0, 128, 0];
|
||||
readonly greenyellow: [173, 255, 47];
|
||||
readonly grey: [128, 128, 128];
|
||||
readonly honeydew: [240, 255, 240];
|
||||
readonly hotpink: [255, 105, 180];
|
||||
readonly indianred: [205, 92, 92];
|
||||
readonly indigo: [75, 0, 130];
|
||||
readonly ivory: [255, 255, 240];
|
||||
readonly khaki: [240, 230, 140];
|
||||
readonly lavender: [230, 230, 250];
|
||||
readonly lavenderblush: [255, 240, 245];
|
||||
readonly lawngreen: [124, 252, 0];
|
||||
readonly lemonchiffon: [255, 250, 205];
|
||||
readonly lightblue: [173, 216, 230];
|
||||
readonly lightcoral: [240, 128, 128];
|
||||
readonly lightcyan: [224, 255, 255];
|
||||
readonly lightgoldenrodyellow: [250, 250, 210];
|
||||
readonly lightgray: [211, 211, 211];
|
||||
readonly lightgreen: [144, 238, 144];
|
||||
readonly lightgrey: [211, 211, 211];
|
||||
readonly lightpink: [255, 182, 193];
|
||||
readonly lightsalmon: [255, 160, 122];
|
||||
readonly lightseagreen: [32, 178, 170];
|
||||
readonly lightskyblue: [135, 206, 250];
|
||||
readonly lightslategray: [119, 136, 153];
|
||||
readonly lightslategrey: [119, 136, 153];
|
||||
readonly lightsteelblue: [176, 196, 222];
|
||||
readonly lightyellow: [255, 255, 224];
|
||||
readonly lime: [0, 255, 0];
|
||||
readonly limegreen: [50, 205, 50];
|
||||
readonly linen: [250, 240, 230];
|
||||
readonly magenta: [255, 0, 255];
|
||||
readonly maroon: [128, 0, 0];
|
||||
readonly mediumaquamarine: [102, 205, 170];
|
||||
readonly mediumblue: [0, 0, 205];
|
||||
readonly mediumorchid: [186, 85, 211];
|
||||
readonly mediumpurple: [147, 112, 219];
|
||||
readonly mediumseagreen: [60, 179, 113];
|
||||
readonly mediumslateblue: [123, 104, 238];
|
||||
readonly mediumspringgreen: [0, 250, 154];
|
||||
readonly mediumturquoise: [72, 209, 204];
|
||||
readonly mediumvioletred: [199, 21, 133];
|
||||
readonly midnightblue: [25, 25, 112];
|
||||
readonly mintcream: [245, 255, 250];
|
||||
readonly mistyrose: [255, 228, 225];
|
||||
readonly moccasin: [255, 228, 181];
|
||||
readonly navajowhite: [255, 222, 173];
|
||||
readonly navy: [0, 0, 128];
|
||||
readonly oldlace: [253, 245, 230];
|
||||
readonly olive: [128, 128, 0];
|
||||
readonly olivedrab: [107, 142, 35];
|
||||
readonly orange: [255, 165, 0];
|
||||
readonly orangered: [255, 69, 0];
|
||||
readonly orchid: [218, 112, 214];
|
||||
readonly palegoldenrod: [238, 232, 170];
|
||||
readonly palegreen: [152, 251, 152];
|
||||
readonly paleturquoise: [175, 238, 238];
|
||||
readonly palevioletred: [219, 112, 147];
|
||||
readonly papayawhip: [255, 239, 213];
|
||||
readonly peachpuff: [255, 218, 185];
|
||||
readonly peru: [205, 133, 63];
|
||||
readonly pink: [255, 192, 203];
|
||||
readonly plum: [221, 160, 221];
|
||||
readonly powderblue: [176, 224, 230];
|
||||
readonly purple: [128, 0, 128];
|
||||
readonly rebeccapurple: [102, 51, 153];
|
||||
readonly red: [255, 0, 0];
|
||||
readonly rosybrown: [188, 143, 143];
|
||||
readonly royalblue: [65, 105, 225];
|
||||
readonly saddlebrown: [139, 69, 19];
|
||||
readonly salmon: [250, 128, 114];
|
||||
readonly sandybrown: [244, 164, 96];
|
||||
readonly seagreen: [46, 139, 87];
|
||||
readonly seashell: [255, 245, 238];
|
||||
readonly sienna: [160, 82, 45];
|
||||
readonly silver: [192, 192, 192];
|
||||
readonly skyblue: [135, 206, 235];
|
||||
readonly slateblue: [106, 90, 205];
|
||||
readonly slategray: [112, 128, 144];
|
||||
readonly slategrey: [112, 128, 144];
|
||||
readonly snow: [255, 250, 250];
|
||||
readonly springgreen: [0, 255, 127];
|
||||
readonly steelblue: [70, 130, 180];
|
||||
readonly tan: [210, 180, 140];
|
||||
readonly teal: [0, 128, 128];
|
||||
readonly thistle: [216, 191, 216];
|
||||
readonly tomato: [255, 99, 71];
|
||||
readonly turquoise: [64, 224, 208];
|
||||
readonly violet: [238, 130, 238];
|
||||
readonly wheat: [245, 222, 179];
|
||||
readonly white: [255, 255, 255];
|
||||
readonly whitesmoke: [245, 245, 245];
|
||||
readonly yellow: [255, 255, 0];
|
||||
readonly yellowgreen: [154, 205, 50];
|
||||
};
|
||||
/**
|
||||
* cache invalid color value
|
||||
* @param key - cache key
|
||||
* @param nullable - is nullable
|
||||
* @returns cached value
|
||||
*/
|
||||
export declare const cacheInvalidColorValue: (cacheKey: string, format: string, nullable?: boolean) => SpecifiedColorChannels | string | NullObject;
|
||||
/**
|
||||
* resolve invalid color value
|
||||
* @param format - output format
|
||||
* @param nullable - is nullable
|
||||
* @returns resolved value
|
||||
*/
|
||||
export declare const resolveInvalidColorValue: (format: string, nullable?: boolean) => SpecifiedColorChannels | string | NullObject;
|
||||
/**
|
||||
* validate color components
|
||||
* @param arr - color components
|
||||
* @param [opt] - options
|
||||
* @param [opt.alpha] - alpha channel
|
||||
* @param [opt.minLength] - min length
|
||||
* @param [opt.maxLength] - max length
|
||||
* @param [opt.minRange] - min range
|
||||
* @param [opt.maxRange] - max range
|
||||
* @param [opt.validateRange] - validate range
|
||||
* @returns result - validated color components
|
||||
*/
|
||||
export declare const validateColorComponents: (arr: ColorChannels | TriColorChannels, opt?: {
|
||||
alpha?: boolean;
|
||||
minLength?: number;
|
||||
maxLength?: number;
|
||||
minRange?: number;
|
||||
maxRange?: number;
|
||||
validateRange?: boolean;
|
||||
}) => ColorChannels | TriColorChannels;
|
||||
/**
|
||||
* transform matrix
|
||||
* @param mtx - 3 * 3 matrix
|
||||
* @param vct - vector
|
||||
* @param [skip] - skip validate
|
||||
* @returns TriColorChannels - [p1, p2, p3]
|
||||
*/
|
||||
export declare const transformMatrix: (mtx: ColorMatrix, vct: TriColorChannels, skip?: boolean) => TriColorChannels;
|
||||
/**
|
||||
* normalize color components
|
||||
* @param colorA - color components [v1, v2, v3, v4]
|
||||
* @param colorB - color components [v1, v2, v3, v4]
|
||||
* @param [skip] - skip validate
|
||||
* @returns result - [colorA, colorB]
|
||||
*/
|
||||
export declare const normalizeColorComponents: (colorA: [number | string, number | string, number | string, number | string], colorB: [number | string, number | string, number | string, number | string], skip?: boolean) => [ColorChannels, ColorChannels];
|
||||
/**
|
||||
* number to hex string
|
||||
* @param value - numeric value
|
||||
* @returns hex string
|
||||
*/
|
||||
export declare const numberToHexString: (value: number) => string;
|
||||
/**
|
||||
* angle to deg
|
||||
* @param angle
|
||||
* @returns deg: 0..360
|
||||
*/
|
||||
export declare const angleToDeg: (angle: string) => number;
|
||||
/**
|
||||
* parse alpha
|
||||
* @param [alpha] - alpha value
|
||||
* @returns alpha: 0..1
|
||||
*/
|
||||
export declare const parseAlpha: (alpha?: string) => number;
|
||||
/**
|
||||
* parse hex alpha
|
||||
* @param value - alpha value in hex string
|
||||
* @returns alpha: 0..1
|
||||
*/
|
||||
export declare const parseHexAlpha: (value: string) => number;
|
||||
/**
|
||||
* transform rgb to linear rgb
|
||||
* @param rgb - [r, g, b] r|g|b: 0..255
|
||||
* @param [skip] - skip validate
|
||||
* @returns TriColorChannels - [r, g, b] r|g|b: 0..1
|
||||
*/
|
||||
export declare const transformRgbToLinearRgb: (rgb: TriColorChannels, skip?: boolean) => TriColorChannels;
|
||||
/**
|
||||
* transform rgb to xyz
|
||||
* @param rgb - [r, g, b] r|g|b: 0..255
|
||||
* @param [skip] - skip validate
|
||||
* @returns TriColorChannels - [x, y, z]
|
||||
*/
|
||||
export declare const transformRgbToXyz: (rgb: TriColorChannels, skip?: boolean) => TriColorChannels;
|
||||
/**
|
||||
* transform rgb to xyz-d50
|
||||
* @param rgb - [r, g, b] r|g|b: 0..255 alpha: 0..1
|
||||
* @returns TriColorChannels - [x, y, z]
|
||||
*/
|
||||
export declare const transformRgbToXyzD50: (rgb: TriColorChannels) => TriColorChannels;
|
||||
/**
|
||||
* transform linear rgb to rgb
|
||||
* @param rgb - [r, g, b] r|g|b: 0..1
|
||||
* @param [round] - round result
|
||||
* @returns TriColorChannels - [r, g, b] r|g|b: 0..255
|
||||
*/
|
||||
export declare const transformLinearRgbToRgb: (rgb: TriColorChannels, round?: boolean) => TriColorChannels;
|
||||
/**
|
||||
* transform xyz to rgb
|
||||
* @param xyz - [x, y, z]
|
||||
* @param [skip] - skip validate
|
||||
* @returns TriColorChannels - [r, g, b] r|g|b: 0..255
|
||||
*/
|
||||
export declare const transformXyzToRgb: (xyz: TriColorChannels, skip?: boolean) => TriColorChannels;
|
||||
/**
|
||||
* transform xyz to xyz-d50
|
||||
* @param xyz - [x, y, z]
|
||||
* @returns TriColorChannels - [x, y, z]
|
||||
*/
|
||||
export declare const transformXyzToXyzD50: (xyz: TriColorChannels) => TriColorChannels;
|
||||
/**
|
||||
* transform xyz to hsl
|
||||
* @param xyz - [x, y, z]
|
||||
* @param [skip] - skip validate
|
||||
* @returns TriColorChannels - [h, s, l]
|
||||
*/
|
||||
export declare const transformXyzToHsl: (xyz: TriColorChannels, skip?: boolean) => TriColorChannels;
|
||||
/**
|
||||
* transform xyz to hwb
|
||||
* @param xyz - [x, y, z]
|
||||
* @param [skip] - skip validate
|
||||
* @returns TriColorChannels - [h, w, b]
|
||||
*/
|
||||
export declare const transformXyzToHwb: (xyz: TriColorChannels, skip?: boolean) => TriColorChannels;
|
||||
/**
|
||||
* transform xyz to oklab
|
||||
* @param xyz - [x, y, z]
|
||||
* @param [skip] - skip validate
|
||||
* @returns TriColorChannels - [l, a, b]
|
||||
*/
|
||||
export declare const transformXyzToOklab: (xyz: TriColorChannels, skip?: boolean) => TriColorChannels;
|
||||
/**
|
||||
* transform xyz to oklch
|
||||
* @param xyz - [x, y, z]
|
||||
* @param [skip] - skip validate
|
||||
* @returns TriColorChannels - [l, c, h]
|
||||
*/
|
||||
export declare const transformXyzToOklch: (xyz: TriColorChannels, skip?: boolean) => TriColorChannels;
|
||||
/**
|
||||
* transform xyz D50 to rgb
|
||||
* @param xyz - [x, y, z]
|
||||
* @param [skip] - skip validate
|
||||
* @returns TriColorChannels - [r, g, b] r|g|b: 0..255
|
||||
*/
|
||||
export declare const transformXyzD50ToRgb: (xyz: TriColorChannels, skip?: boolean) => TriColorChannels;
|
||||
/**
|
||||
* transform xyz-d50 to lab
|
||||
* @param xyz - [x, y, z]
|
||||
* @param [skip] - skip validate
|
||||
* @returns TriColorChannels - [l, a, b]
|
||||
*/
|
||||
export declare const transformXyzD50ToLab: (xyz: TriColorChannels, skip?: boolean) => TriColorChannels;
|
||||
/**
|
||||
* transform xyz-d50 to lch
|
||||
* @param xyz - [x, y, z]
|
||||
* @param [skip] - skip validate
|
||||
* @returns TriColorChannels - [l, c, h]
|
||||
*/
|
||||
export declare const transformXyzD50ToLch: (xyz: TriColorChannels, skip?: boolean) => TriColorChannels;
|
||||
/**
|
||||
* convert rgb to hex color
|
||||
* @param rgb - [r, g, b, alpha] r|g|b: 0..255 alpha: 0..1
|
||||
* @returns hex color
|
||||
*/
|
||||
export declare const convertRgbToHex: (rgb: ColorChannels) => string;
|
||||
/**
|
||||
* convert linear rgb to hex color
|
||||
* @param rgb - [r, g, b, alpha] r|g|b|alpha: 0..1
|
||||
* @param [skip] - skip validate
|
||||
* @returns hex color
|
||||
*/
|
||||
export declare const convertLinearRgbToHex: (rgb: ColorChannels, skip?: boolean) => string;
|
||||
/**
|
||||
* convert xyz to hex color
|
||||
* @param xyz - [x, y, z, alpha]
|
||||
* @returns hex color
|
||||
*/
|
||||
export declare const convertXyzToHex: (xyz: ColorChannels) => string;
|
||||
/**
|
||||
* convert xyz D50 to hex color
|
||||
* @param xyz - [x, y, z, alpha]
|
||||
* @returns hex color
|
||||
*/
|
||||
export declare const convertXyzD50ToHex: (xyz: ColorChannels) => string;
|
||||
/**
|
||||
* convert hex color to rgb
|
||||
* @param value - hex color value
|
||||
* @returns ColorChannels - [r, g, b, alpha] r|g|b: 0..255 alpha: 0..1
|
||||
*/
|
||||
export declare const convertHexToRgb: (value: string) => ColorChannels;
|
||||
/**
|
||||
* convert hex color to linear rgb
|
||||
* @param value - hex color value
|
||||
* @returns ColorChannels - [r, g, b, alpha] r|g|b|alpha: 0..1
|
||||
*/
|
||||
export declare const convertHexToLinearRgb: (value: string) => ColorChannels;
|
||||
/**
|
||||
* convert hex color to xyz
|
||||
* @param value - hex color value
|
||||
* @returns ColorChannels - [x, y, z, alpha]
|
||||
*/
|
||||
export declare const convertHexToXyz: (value: string) => ColorChannels;
|
||||
/**
|
||||
* parse rgb()
|
||||
* @param value - rgb color value
|
||||
* @param [opt] - options
|
||||
* @returns parsed color - ['rgb', r, g, b, alpha], '(empty)', NullObject
|
||||
*/
|
||||
export declare const parseRgb: (value: string, opt?: Options) => SpecifiedColorChannels | string | NullObject;
|
||||
/**
|
||||
* parse hsl()
|
||||
* @param value - hsl color value
|
||||
* @param [opt] - options
|
||||
* @returns parsed color - ['rgb', r, g, b, alpha], '(empty)', NullObject
|
||||
*/
|
||||
export declare const parseHsl: (value: string, opt?: Options) => SpecifiedColorChannels | string | NullObject;
|
||||
/**
|
||||
* parse hwb()
|
||||
* @param value - hwb color value
|
||||
* @param [opt] - options
|
||||
* @returns parsed color - ['rgb', r, g, b, alpha], '(empty)', NullObject
|
||||
*/
|
||||
export declare const parseHwb: (value: string, opt?: Options) => SpecifiedColorChannels | string | NullObject;
|
||||
/**
|
||||
* parse lab()
|
||||
* @param value - lab color value
|
||||
* @param [opt] - options
|
||||
* @returns parsed color
|
||||
* - [xyz-d50, x, y, z, alpha], ['lab', l, a, b, alpha], '(empty)', NullObject
|
||||
*/
|
||||
export declare const parseLab: (value: string, opt?: Options) => SpecifiedColorChannels | string | NullObject;
|
||||
/**
|
||||
* parse lch()
|
||||
* @param value - lch color value
|
||||
* @param [opt] - options
|
||||
* @returns parsed color
|
||||
* - ['xyz-d50', x, y, z, alpha], ['lch', l, c, h, alpha]
|
||||
* - '(empty)', NullObject
|
||||
*/
|
||||
export declare const parseLch: (value: string, opt?: Options) => SpecifiedColorChannels | string | NullObject;
|
||||
/**
|
||||
* parse oklab()
|
||||
* @param value - oklab color value
|
||||
* @param [opt] - options
|
||||
* @returns parsed color
|
||||
* - ['xyz-d65', x, y, z, alpha], ['oklab', l, a, b, alpha]
|
||||
* - '(empty)', NullObject
|
||||
*/
|
||||
export declare const parseOklab: (value: string, opt?: Options) => SpecifiedColorChannels | string | NullObject;
|
||||
/**
|
||||
* parse oklch()
|
||||
* @param value - oklch color value
|
||||
* @param [opt] - options
|
||||
* @returns parsed color
|
||||
* - ['xyz-d65', x, y, z, alpha], ['oklch', l, c, h, alpha]
|
||||
* - '(empty)', NullObject
|
||||
*/
|
||||
export declare const parseOklch: (value: string, opt?: Options) => SpecifiedColorChannels | string | NullObject;
|
||||
/**
|
||||
* parse color()
|
||||
* @param value - color function value
|
||||
* @param [opt] - options
|
||||
* @returns parsed color
|
||||
* - ['xyz-(d50|d65)', x, y, z, alpha], [cs, r, g, b, alpha]
|
||||
* - '(empty)', NullObject
|
||||
*/
|
||||
export declare const parseColorFunc: (value: string, opt?: Options) => SpecifiedColorChannels | string | NullObject;
|
||||
/**
|
||||
* parse color value
|
||||
* @param value - CSS color value
|
||||
* @param [opt] - options
|
||||
* @returns parsed color
|
||||
* - ['xyz-(d50|d65)', x, y, z, alpha], ['rgb', r, g, b, alpha]
|
||||
* - value, '(empty)', NullObject
|
||||
*/
|
||||
export declare const parseColorValue: (value: string, opt?: Options) => SpecifiedColorChannels | string | NullObject;
|
||||
/**
|
||||
* resolve color value
|
||||
* @param value - CSS color value
|
||||
* @param [opt] - options
|
||||
* @returns resolved color
|
||||
* - [cs, v1, v2, v3, alpha], value, '(empty)', NullObject
|
||||
*/
|
||||
export declare const resolveColorValue: (value: string, opt?: Options) => SpecifiedColorChannels | string | NullObject;
|
||||
/**
|
||||
* resolve color()
|
||||
* @param value - color function value
|
||||
* @param [opt] - options
|
||||
* @returns resolved color - [cs, v1, v2, v3, alpha], '(empty)', NullObject
|
||||
*/
|
||||
export declare const resolveColorFunc: (value: string, opt?: Options) => SpecifiedColorChannels | string | NullObject;
|
||||
/**
|
||||
* convert color value to linear rgb
|
||||
* @param value - CSS color value
|
||||
* @param [opt] - options
|
||||
* @returns ColorChannels | NullObject - [r, g, b, alpha] r|g|b|alpha: 0..1
|
||||
*/
|
||||
export declare const convertColorToLinearRgb: (value: string, opt?: {
|
||||
colorSpace?: string;
|
||||
format?: string;
|
||||
}) => ColorChannels | NullObject;
|
||||
/**
|
||||
* convert color value to rgb
|
||||
* @param value - CSS color value
|
||||
* @param [opt] - options
|
||||
* @returns ColorChannels | NullObject
|
||||
* - [r, g, b, alpha] r|g|b: 0..255 alpha: 0..1
|
||||
*/
|
||||
export declare const convertColorToRgb: (value: string, opt?: Options) => ColorChannels | NullObject;
|
||||
/**
|
||||
* convert color value to xyz
|
||||
* @param value - CSS color value
|
||||
* @param [opt] - options
|
||||
* @returns ColorChannels | NullObject - [x, y, z, alpha]
|
||||
*/
|
||||
export declare const convertColorToXyz: (value: string, opt?: Options) => ColorChannels | NullObject;
|
||||
/**
|
||||
* convert color value to hsl
|
||||
* @param value - CSS color value
|
||||
* @param [opt] - options
|
||||
* @returns ColorChannels | NullObject - [h, s, l, alpha], hue may be powerless
|
||||
*/
|
||||
export declare const convertColorToHsl: (value: string, opt?: Options) => ColorChannels | [number | string, number, number, number] | NullObject;
|
||||
/**
|
||||
* convert color value to hwb
|
||||
* @param value - CSS color value
|
||||
* @param [opt] - options
|
||||
* @returns ColorChannels | NullObject - [h, w, b, alpha], hue may be powerless
|
||||
*/
|
||||
export declare const convertColorToHwb: (value: string, opt?: Options) => ColorChannels | [number | string, number, number, number] | NullObject;
|
||||
/**
|
||||
* convert color value to lab
|
||||
* @param value - CSS color value
|
||||
* @param [opt] - options
|
||||
* @returns ColorChannels | NullObject - [l, a, b, alpha]
|
||||
*/
|
||||
export declare const convertColorToLab: (value: string, opt?: Options) => ColorChannels | NullObject;
|
||||
/**
|
||||
* convert color value to lch
|
||||
* @param value - CSS color value
|
||||
* @param [opt] - options
|
||||
* @returns ColorChannels | NullObject - [l, c, h, alpha], hue may be powerless
|
||||
*/
|
||||
export declare const convertColorToLch: (value: string, opt?: Options) => ColorChannels | [number, number, number | string, number] | NullObject;
|
||||
/**
|
||||
* convert color value to oklab
|
||||
* @param value - CSS color value
|
||||
* @param [opt] - options
|
||||
* @returns ColorChannels | NullObject - [l, a, b, alpha]
|
||||
*/
|
||||
export declare const convertColorToOklab: (value: string, opt?: Options) => ColorChannels | NullObject;
|
||||
/**
|
||||
* convert color value to oklch
|
||||
* @param value - CSS color value
|
||||
* @param [opt] - options
|
||||
* @returns ColorChannels | NullObject - [l, c, h, alpha], hue may be powerless
|
||||
*/
|
||||
export declare const convertColorToOklch: (value: string, opt?: Options) => ColorChannels | [number, number, number | string, number] | NullObject;
|
||||
/**
|
||||
* resolve color-mix()
|
||||
* @param value - color-mix color value
|
||||
* @param [opt] - options
|
||||
* @returns resolved color - [cs, v1, v2, v3, alpha], '(empty)'
|
||||
*/
|
||||
export declare const resolveColorMix: (value: string, opt?: Options) => SpecifiedColorChannels | string | NullObject;
|
||||
export {};
|
||||
3534
node_modules/@asamuzakjp/css-color/dist/esm/js/color.js
generated
vendored
Normal file
3534
node_modules/@asamuzakjp/css-color/dist/esm/js/color.js
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
1
node_modules/@asamuzakjp/css-color/dist/esm/js/color.js.map
generated
vendored
Normal file
1
node_modules/@asamuzakjp/css-color/dist/esm/js/color.js.map
generated
vendored
Normal file
File diff suppressed because one or more lines are too long
21
node_modules/@asamuzakjp/css-color/dist/esm/js/common.d.ts
generated
vendored
Normal file
21
node_modules/@asamuzakjp/css-color/dist/esm/js/common.d.ts
generated
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
/**
|
||||
* common
|
||||
*/
|
||||
/**
|
||||
* get type
|
||||
* @param o - object to check
|
||||
* @returns type of object
|
||||
*/
|
||||
export declare const getType: (o: unknown) => string;
|
||||
/**
|
||||
* is string
|
||||
* @param o - object to check
|
||||
* @returns result
|
||||
*/
|
||||
export declare const isString: (o: unknown) => o is string;
|
||||
/**
|
||||
* is string or number
|
||||
* @param o - object to check
|
||||
* @returns result
|
||||
*/
|
||||
export declare const isStringOrNumber: (o: unknown) => boolean;
|
||||
17
node_modules/@asamuzakjp/css-color/dist/esm/js/common.js
generated
vendored
Normal file
17
node_modules/@asamuzakjp/css-color/dist/esm/js/common.js
generated
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
//#region src/js/common.ts
|
||||
/**
|
||||
* is string
|
||||
* @param o - object to check
|
||||
* @returns result
|
||||
*/
|
||||
var isString = (o) => typeof o === "string" || o instanceof String;
|
||||
/**
|
||||
* is string or number
|
||||
* @param o - object to check
|
||||
* @returns result
|
||||
*/
|
||||
var isStringOrNumber = (o) => isString(o) || typeof o === "number";
|
||||
//#endregion
|
||||
export { isString, isStringOrNumber };
|
||||
|
||||
//# sourceMappingURL=common.js.map
|
||||
1
node_modules/@asamuzakjp/css-color/dist/esm/js/common.js.map
generated
vendored
Normal file
1
node_modules/@asamuzakjp/css-color/dist/esm/js/common.js.map
generated
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"common.js","names":[],"sources":["../../../src/js/common.ts"],"sourcesContent":["/**\n * common\n */\n\n/* numeric constants */\nconst TYPE_FROM = 8;\nconst TYPE_TO = -1;\n\n/**\n * get type\n * @param o - object to check\n * @returns type of object\n */\nexport const getType = (o: unknown): string =>\n Object.prototype.toString.call(o).slice(TYPE_FROM, TYPE_TO);\n\n/**\n * is string\n * @param o - object to check\n * @returns result\n */\nexport const isString = (o: unknown): o is string =>\n typeof o === 'string' || o instanceof String;\n\n/**\n * is string or number\n * @param o - object to check\n * @returns result\n */\nexport const isStringOrNumber = (o: unknown): boolean =>\n isString(o) || typeof o === 'number';\n"],"mappings":";;;;;;AAqBA,IAAa,YAAY,MACvB,OAAO,MAAM,YAAY,aAAa;;;;;;AAOxC,IAAa,oBAAoB,MAC/B,SAAS,EAAE,IAAI,OAAO,MAAM"}
|
||||
43
node_modules/@asamuzakjp/css-color/dist/esm/js/constant.d.ts
generated
vendored
Normal file
43
node_modules/@asamuzakjp/css-color/dist/esm/js/constant.d.ts
generated
vendored
Normal file
File diff suppressed because one or more lines are too long
60
node_modules/@asamuzakjp/css-color/dist/esm/js/constant.js
generated
vendored
Normal file
60
node_modules/@asamuzakjp/css-color/dist/esm/js/constant.js
generated
vendored
Normal file
@@ -0,0 +1,60 @@
|
||||
//#region src/js/constant.ts
|
||||
/**
|
||||
* constant
|
||||
*/
|
||||
var _DIGIT = "(?:0|[1-9]\\d*)";
|
||||
var _MATH = `clamp|max|min|exp|hypot|log|pow|sqrt|abs|sign|mod|rem|round|a?(?:cos|sin|tan)|atan2`;
|
||||
var _CALC = `calc|${_MATH}`;
|
||||
var _VAR = `var|${_CALC}`;
|
||||
var ANGLE = "deg|g?rad|turn";
|
||||
var LENGTH = "[cm]m|[dls]?v(?:[bhiw]|max|min)|in|p[ctx]|q|r?(?:[cl]h|cap|e[mx]|ic)";
|
||||
var NUM = `[+-]?(?:${_DIGIT}(?:\\.\\d*)?|\\.\\d+)(?:e-?${_DIGIT})?`;
|
||||
var NUM_POSITIVE = `\\+?(?:${_DIGIT}(?:\\.\\d*)?|\\.\\d+)(?:e-?${_DIGIT})?`;
|
||||
var NONE = "none";
|
||||
var PCT = `${NUM}%`;
|
||||
var SYN_FN_CALC = `^(?:${_CALC})\\(|(?<=[*\\/\\s\\(])(?:${_CALC})\\(`;
|
||||
var SYN_FN_MATH_START = `^(?:${_MATH})\\($`;
|
||||
var SYN_FN_VAR = "^var\\(|(?<=[*\\/\\s\\(])var\\(";
|
||||
var SYN_FN_VAR_START = `^(?:${_VAR})\\(`;
|
||||
var _ALPHA = `(?:\\s*\\/\\s*(?:${NUM}|${PCT}|${NONE}))?`;
|
||||
var _ALPHA_LV3 = `(?:\\s*,\\s*(?:${NUM}|${PCT}))?`;
|
||||
var _COLOR_FUNC = "(?:ok)?l(?:ab|ch)|color|hsla?|hwb|rgba?";
|
||||
var _COLOR_KEY = "[a-z]+|#[\\da-f]{3}|#[\\da-f]{4}|#[\\da-f]{6}|#[\\da-f]{8}";
|
||||
var _CS_HUE = "(?:ok)?lch|hsl|hwb";
|
||||
var _CS_HUE_ARC = "(?:de|in)creasing|longer|shorter";
|
||||
var _NUM_ANGLE = `${NUM}(?:${ANGLE})?`;
|
||||
var _NUM_ANGLE_NONE = `(?:${NUM}(?:${ANGLE})?|${NONE})`;
|
||||
var _NUM_PCT_NONE = `(?:${NUM}|${PCT}|${NONE})`;
|
||||
var CS_HUE = `(?:${_CS_HUE})(?:\\s(?:${_CS_HUE_ARC})\\shue)?`;
|
||||
var CS_HUE_CAPT = `(${_CS_HUE})(?:\\s(${_CS_HUE_ARC})\\shue)?`;
|
||||
var CS_LAB = "(?:ok)?lab";
|
||||
var CS_LCH = "(?:ok)?lch";
|
||||
var CS_RGB = `(?:a98|prophoto)-rgb|display-p3|rec2020|srgb(?:-linear)?`;
|
||||
var CS_XYZ = "xyz(?:-d(?:50|65))?";
|
||||
var CS_RECT = `${CS_LAB}|${CS_RGB}|${CS_XYZ}`;
|
||||
var CS_MIX = `${CS_HUE}|${CS_RECT}`;
|
||||
var FN_COLOR = "color(";
|
||||
var FN_LIGHT_DARK = "light-dark(";
|
||||
var FN_MIX = "color-mix(";
|
||||
var FN_REL = `(?:${_COLOR_FUNC})\\(\\s*from\\s+`;
|
||||
var FN_REL_CAPT = `(${_COLOR_FUNC})\\(\\s*from\\s+`;
|
||||
var FN_VAR = "var(";
|
||||
var SYN_FN_COLOR = `(?:${CS_RGB}|${CS_XYZ})(?:\\s+${_NUM_PCT_NONE}){3}${_ALPHA}`;
|
||||
var SYN_FN_LIGHT_DARK = "^light-dark\\(";
|
||||
var SYN_FN_REL = `^${FN_REL}|(?<=[\\s])${FN_REL}`;
|
||||
var SYN_HSL = `${_NUM_ANGLE_NONE}(?:\\s+${_NUM_PCT_NONE}){2}${_ALPHA}`;
|
||||
var SYN_HSL_LV3 = `${_NUM_ANGLE}(?:\\s*,\\s*${PCT}){2}${_ALPHA_LV3}`;
|
||||
var SYN_LCH = `(?:${_NUM_PCT_NONE}\\s+){2}${_NUM_ANGLE_NONE}${_ALPHA}`;
|
||||
var SYN_MOD = `${_NUM_PCT_NONE}(?:\\s+${_NUM_PCT_NONE}){2}${_ALPHA}`;
|
||||
var SYN_RGB_LV3 = `(?:${NUM}(?:\\s*,\\s*${NUM}){2}|${PCT}(?:\\s*,\\s*${PCT}){2})${_ALPHA_LV3}`;
|
||||
var SYN_COLOR_TYPE = `${_COLOR_KEY}|hsla?\\(\\s*${SYN_HSL_LV3}\\s*\\)|rgba?\\(\\s*${SYN_RGB_LV3}\\s*\\)|(?:hsla?|hwb)\\(\\s*${SYN_HSL}\\s*\\)|(?:(?:ok)?lab|rgba?)\\(\\s*${SYN_MOD}\\s*\\)|(?:ok)?lch\\(\\s*${SYN_LCH}\\s*\\)|color\\(\\s*${SYN_FN_COLOR}\\s*\\)`;
|
||||
var SYN_MIX_PART = `(?:${SYN_COLOR_TYPE})(?:\\s+${PCT})?`;
|
||||
var SYN_MIX = `color-mix\\(\\s*in\\s+(?:${CS_MIX})\\s*,\\s*${SYN_MIX_PART}\\s*,\\s*${SYN_MIX_PART}\\s*\\)`;
|
||||
var SYN_MIX_CAPT = `color-mix\\(\\s*in\\s+(${CS_MIX})\\s*,\\s*(${SYN_MIX_PART})\\s*,\\s*(${SYN_MIX_PART})\\s*\\)`;
|
||||
var VAL_COMP = "computedValue";
|
||||
var VAL_MIX = "mixValue";
|
||||
var VAL_SPEC = "specifiedValue";
|
||||
//#endregion
|
||||
export { ANGLE, CS_HUE, CS_HUE_CAPT, CS_LAB, CS_LCH, CS_MIX, CS_RECT, CS_RGB, CS_XYZ, FN_COLOR, FN_LIGHT_DARK, FN_MIX, FN_REL, FN_REL_CAPT, FN_VAR, LENGTH, NONE, NUM, NUM_POSITIVE, PCT, SYN_COLOR_TYPE, SYN_FN_CALC, SYN_FN_COLOR, SYN_FN_LIGHT_DARK, SYN_FN_MATH_START, SYN_FN_REL, SYN_FN_VAR, SYN_FN_VAR_START, SYN_HSL, SYN_HSL_LV3, SYN_LCH, SYN_MIX, SYN_MIX_CAPT, SYN_MIX_PART, SYN_MOD, SYN_RGB_LV3, VAL_COMP, VAL_MIX, VAL_SPEC };
|
||||
|
||||
//# sourceMappingURL=constant.js.map
|
||||
1
node_modules/@asamuzakjp/css-color/dist/esm/js/constant.js.map
generated
vendored
Normal file
1
node_modules/@asamuzakjp/css-color/dist/esm/js/constant.js.map
generated
vendored
Normal file
File diff suppressed because one or more lines are too long
99
node_modules/@asamuzakjp/css-color/dist/esm/js/convert.d.ts
generated
vendored
Normal file
99
node_modules/@asamuzakjp/css-color/dist/esm/js/convert.d.ts
generated
vendored
Normal file
@@ -0,0 +1,99 @@
|
||||
import { NullObject } from './cache.js';
|
||||
import { ColorChannels, Options } from './typedef.js';
|
||||
/**
|
||||
* pre process
|
||||
* @param value - CSS color value
|
||||
* @param [opt] - options
|
||||
* @returns value
|
||||
*/
|
||||
export declare const preProcess: (value: string, opt?: Options) => string | NullObject;
|
||||
/**
|
||||
* convert number to hex string
|
||||
* @param value - numeric value
|
||||
* @returns hex string: 00..ff
|
||||
*/
|
||||
export declare const numberToHex: (value: number) => string;
|
||||
/**
|
||||
* convert color to hex
|
||||
* @param value - CSS color value
|
||||
* @param [opt] - options
|
||||
* @param [opt.alpha] - enable alpha channel
|
||||
* @returns #rrggbb | #rrggbbaa | null
|
||||
*/
|
||||
export declare const colorToHex: (value: string, opt?: Options) => string | null;
|
||||
/**
|
||||
* convert color to hsl
|
||||
* @param value - CSS color value
|
||||
* @param [opt] - options
|
||||
* @returns ColorChannels - [h, s, l, alpha]
|
||||
*/
|
||||
export declare const colorToHsl: (value: string, opt?: Options) => ColorChannels;
|
||||
/**
|
||||
* convert color to hwb
|
||||
* @param value - CSS color value
|
||||
* @param [opt] - options
|
||||
* @returns ColorChannels - [h, w, b, alpha]
|
||||
*/
|
||||
export declare const colorToHwb: (value: string, opt?: Options) => ColorChannels;
|
||||
/**
|
||||
* convert color to lab
|
||||
* @param value - CSS color value
|
||||
* @param [opt] - options
|
||||
* @returns ColorChannels - [l, a, b, alpha]
|
||||
*/
|
||||
export declare const colorToLab: (value: string, opt?: Options) => ColorChannels;
|
||||
/**
|
||||
* convert color to lch
|
||||
* @param value - CSS color value
|
||||
* @param [opt] - options
|
||||
* @returns ColorChannels - [l, c, h, alpha]
|
||||
*/
|
||||
export declare const colorToLch: (value: string, opt?: Options) => ColorChannels;
|
||||
/**
|
||||
* convert color to oklab
|
||||
* @param value - CSS color value
|
||||
* @param [opt] - options
|
||||
* @returns ColorChannels - [l, a, b, alpha]
|
||||
*/
|
||||
export declare const colorToOklab: (value: string, opt?: Options) => ColorChannels;
|
||||
/**
|
||||
* convert color to oklch
|
||||
* @param value - CSS color value
|
||||
* @param [opt] - options
|
||||
* @returns ColorChannels - [l, c, h, alpha]
|
||||
*/
|
||||
export declare const colorToOklch: (value: string, opt?: Options) => ColorChannels;
|
||||
/**
|
||||
* convert color to rgb
|
||||
* @param value - CSS color value
|
||||
* @param [opt] - options
|
||||
* @returns ColorChannels - [r, g, b, alpha]
|
||||
*/
|
||||
export declare const colorToRgb: (value: string, opt?: Options) => ColorChannels;
|
||||
/**
|
||||
* convert color to xyz
|
||||
* @param value - CSS color value
|
||||
* @param [opt] - options
|
||||
* @returns ColorChannels - [x, y, z, alpha]
|
||||
*/
|
||||
export declare const colorToXyz: (value: string, opt?: Options) => ColorChannels;
|
||||
/**
|
||||
* convert color to xyz-d50
|
||||
* @param value - CSS color value
|
||||
* @param [opt] - options
|
||||
* @returns ColorChannels - [x, y, z, alpha]
|
||||
*/
|
||||
export declare const colorToXyzD50: (value: string, opt?: Options) => ColorChannels;
|
||||
export declare const convert: {
|
||||
colorToHex: (value: string, opt?: Options) => string | null;
|
||||
colorToHsl: (value: string, opt?: Options) => ColorChannels;
|
||||
colorToHwb: (value: string, opt?: Options) => ColorChannels;
|
||||
colorToLab: (value: string, opt?: Options) => ColorChannels;
|
||||
colorToLch: (value: string, opt?: Options) => ColorChannels;
|
||||
colorToOklab: (value: string, opt?: Options) => ColorChannels;
|
||||
colorToOklch: (value: string, opt?: Options) => ColorChannels;
|
||||
colorToRgb: (value: string, opt?: Options) => ColorChannels;
|
||||
colorToXyz: (value: string, opt?: Options) => ColorChannels;
|
||||
colorToXyzD50: (value: string, opt?: Options) => ColorChannels;
|
||||
numberToHex: (value: number) => string;
|
||||
};
|
||||
245
node_modules/@asamuzakjp/css-color/dist/esm/js/convert.js
generated
vendored
Normal file
245
node_modules/@asamuzakjp/css-color/dist/esm/js/convert.js
generated
vendored
Normal file
@@ -0,0 +1,245 @@
|
||||
import { CacheItem, NullObject, createCacheKey, getCache, setCache } from "./cache.js";
|
||||
import { isString } from "./common.js";
|
||||
import { SYN_FN_CALC, SYN_FN_REL, SYN_FN_VAR, VAL_COMP } from "./constant.js";
|
||||
import { convertColorToHsl, convertColorToHwb, convertColorToLab, convertColorToLch, convertColorToOklab, convertColorToOklch, convertColorToRgb, numberToHexString, parseColorFunc, parseColorValue } from "./color.js";
|
||||
import { resolveRelativeColor } from "./relative-color.js";
|
||||
import { resolveColor } from "./resolve.js";
|
||||
import { resolveVar } from "./css-var.js";
|
||||
import { cssCalc } from "./css-calc.js";
|
||||
//#region src/js/convert.ts
|
||||
/**
|
||||
* convert
|
||||
*/
|
||||
var NAMESPACE = "convert";
|
||||
var REG_FN_CALC = new RegExp(SYN_FN_CALC);
|
||||
var REG_FN_REL = new RegExp(SYN_FN_REL);
|
||||
var REG_FN_VAR = new RegExp(SYN_FN_VAR);
|
||||
/**
|
||||
* pre process
|
||||
* @param value - CSS color value
|
||||
* @param [opt] - options
|
||||
* @returns value
|
||||
*/
|
||||
var preProcess = (value, opt = {}) => {
|
||||
if (!isString(value)) return new NullObject();
|
||||
value = value.trim();
|
||||
if (!value) return new NullObject();
|
||||
const cacheKey = createCacheKey({
|
||||
namespace: NAMESPACE,
|
||||
name: "preProcess",
|
||||
value
|
||||
}, opt);
|
||||
const cachedResult = getCache(cacheKey);
|
||||
if (cachedResult instanceof CacheItem) {
|
||||
if (cachedResult.isNull) return cachedResult;
|
||||
return cachedResult.item;
|
||||
}
|
||||
let res = value;
|
||||
if (REG_FN_VAR.test(value)) {
|
||||
const resolved = resolveVar(value, opt);
|
||||
if (isString(resolved)) res = resolved;
|
||||
else {
|
||||
setCache(cacheKey, null);
|
||||
return new NullObject();
|
||||
}
|
||||
}
|
||||
if (isString(res)) {
|
||||
if (REG_FN_REL.test(res)) {
|
||||
const resolved = resolveRelativeColor(res, opt);
|
||||
if (isString(resolved)) res = resolved;
|
||||
else {
|
||||
setCache(cacheKey, null);
|
||||
return new NullObject();
|
||||
}
|
||||
} else if (REG_FN_CALC.test(res)) res = cssCalc(res, opt);
|
||||
}
|
||||
if (isString(res)) {
|
||||
if (res.startsWith("color-mix")) res = resolveColor(res, {
|
||||
...opt,
|
||||
format: VAL_COMP,
|
||||
nullable: true
|
||||
});
|
||||
}
|
||||
setCache(cacheKey, res);
|
||||
return res;
|
||||
};
|
||||
/**
|
||||
* converter factory to reduce boilerplate
|
||||
* @param name - function name for cache
|
||||
* @param format - color format
|
||||
* @param convertFn - conversion function
|
||||
* @returns color converter function
|
||||
*/
|
||||
var createColorConverter = (name, format, convertFn) => {
|
||||
const colorConverterFn = (value, opt = {}) => {
|
||||
if (!isString(value)) throw new TypeError(`${value} is not a string.`);
|
||||
const resolved = preProcess(value, opt);
|
||||
if (resolved instanceof NullObject) return [
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0
|
||||
];
|
||||
const val = resolved.toLowerCase();
|
||||
const cacheKey = createCacheKey({
|
||||
namespace: NAMESPACE,
|
||||
name,
|
||||
value: val
|
||||
}, opt);
|
||||
const cached = getCache(cacheKey);
|
||||
if (cached instanceof CacheItem) return cached.item;
|
||||
const result = convertFn(val, {
|
||||
...opt,
|
||||
format
|
||||
});
|
||||
setCache(cacheKey, result);
|
||||
return result;
|
||||
};
|
||||
return colorConverterFn;
|
||||
};
|
||||
/**
|
||||
* convert number to hex string
|
||||
* @param value - numeric value
|
||||
* @returns hex string: 00..ff
|
||||
*/
|
||||
var numberToHex = (value) => numberToHexString(value);
|
||||
/**
|
||||
* convert color to hex
|
||||
* @param value - CSS color value
|
||||
* @param [opt] - options
|
||||
* @param [opt.alpha] - enable alpha channel
|
||||
* @returns #rrggbb | #rrggbbaa | null
|
||||
*/
|
||||
var colorToHex = (value, opt = {}) => {
|
||||
if (!isString(value)) throw new TypeError(`${value} is not a string.`);
|
||||
const resolved = preProcess(value, opt);
|
||||
if (resolved instanceof NullObject) return null;
|
||||
const val = resolved.toLowerCase();
|
||||
const cacheKey = createCacheKey({
|
||||
namespace: NAMESPACE,
|
||||
name: "colorToHex",
|
||||
value: val
|
||||
}, opt);
|
||||
const cached = getCache(cacheKey);
|
||||
if (cached instanceof CacheItem) {
|
||||
if (cached.isNull) return null;
|
||||
return cached.item;
|
||||
}
|
||||
const hex = resolveColor(val, {
|
||||
...opt,
|
||||
nullable: true,
|
||||
format: opt.alpha ? "hexAlpha" : "hex"
|
||||
});
|
||||
if (isString(hex)) {
|
||||
setCache(cacheKey, hex);
|
||||
return hex;
|
||||
}
|
||||
setCache(cacheKey, null);
|
||||
return null;
|
||||
};
|
||||
/**
|
||||
* convert color to hsl
|
||||
* @param value - CSS color value
|
||||
* @param [opt] - options
|
||||
* @returns ColorChannels - [h, s, l, alpha]
|
||||
*/
|
||||
var colorToHsl = createColorConverter("colorToHsl", "hsl", convertColorToHsl);
|
||||
/**
|
||||
* convert color to hwb
|
||||
* @param value - CSS color value
|
||||
* @param [opt] - options
|
||||
* @returns ColorChannels - [h, w, b, alpha]
|
||||
*/
|
||||
var colorToHwb = createColorConverter("colorToHwb", "hwb", convertColorToHwb);
|
||||
/**
|
||||
* convert color to lab
|
||||
* @param value - CSS color value
|
||||
* @param [opt] - options
|
||||
* @returns ColorChannels - [l, a, b, alpha]
|
||||
*/
|
||||
var colorToLab = createColorConverter("colorToLab", "lab", convertColorToLab);
|
||||
/**
|
||||
* convert color to lch
|
||||
* @param value - CSS color value
|
||||
* @param [opt] - options
|
||||
* @returns ColorChannels - [l, c, h, alpha]
|
||||
*/
|
||||
var colorToLch = createColorConverter("colorToLch", "lch", convertColorToLch);
|
||||
/**
|
||||
* convert color to oklab
|
||||
* @param value - CSS color value
|
||||
* @param [opt] - options
|
||||
* @returns ColorChannels - [l, a, b, alpha]
|
||||
*/
|
||||
var colorToOklab = createColorConverter("colorToOklab", "oklab", convertColorToOklab);
|
||||
/**
|
||||
* convert color to oklch
|
||||
* @param value - CSS color value
|
||||
* @param [opt] - options
|
||||
* @returns ColorChannels - [l, c, h, alpha]
|
||||
*/
|
||||
var colorToOklch = createColorConverter("colorToOklch", "oklch", convertColorToOklch);
|
||||
/**
|
||||
* convert color to rgb
|
||||
* @param value - CSS color value
|
||||
* @param [opt] - options
|
||||
* @returns ColorChannels - [r, g, b, alpha]
|
||||
*/
|
||||
var colorToRgb = createColorConverter("colorToRgb", "rgb", convertColorToRgb);
|
||||
/**
|
||||
* convert color to xyz
|
||||
* @param value - CSS color value
|
||||
* @param [opt] - options
|
||||
* @returns ColorChannels - [x, y, z, alpha]
|
||||
*/
|
||||
var colorToXyz = (value, opt = {}) => {
|
||||
if (!isString(value)) throw new TypeError(`${value} is not a string.`);
|
||||
const resolved = preProcess(value, opt);
|
||||
if (resolved instanceof NullObject) return [
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0
|
||||
];
|
||||
const val = resolved.toLowerCase();
|
||||
const cacheKey = createCacheKey({
|
||||
namespace: NAMESPACE,
|
||||
name: "colorToXyz",
|
||||
value: val
|
||||
}, opt);
|
||||
const cached = getCache(cacheKey);
|
||||
if (cached instanceof CacheItem) return cached.item;
|
||||
let parsed;
|
||||
if (val.startsWith("color(")) parsed = parseColorFunc(val, opt);
|
||||
else parsed = parseColorValue(val, opt);
|
||||
const [, ...xyz] = parsed;
|
||||
setCache(cacheKey, xyz);
|
||||
return xyz;
|
||||
};
|
||||
/**
|
||||
* convert color to xyz-d50
|
||||
* @param value - CSS color value
|
||||
* @param [opt] - options
|
||||
* @returns ColorChannels - [x, y, z, alpha]
|
||||
*/
|
||||
var colorToXyzD50 = (value, opt = {}) => {
|
||||
opt.d50 = true;
|
||||
return colorToXyz(value, opt);
|
||||
};
|
||||
var convert = {
|
||||
colorToHex,
|
||||
colorToHsl,
|
||||
colorToHwb,
|
||||
colorToLab,
|
||||
colorToLch,
|
||||
colorToOklab,
|
||||
colorToOklch,
|
||||
colorToRgb,
|
||||
colorToXyz,
|
||||
colorToXyzD50,
|
||||
numberToHex
|
||||
};
|
||||
//#endregion
|
||||
export { convert };
|
||||
|
||||
//# sourceMappingURL=convert.js.map
|
||||
1
node_modules/@asamuzakjp/css-color/dist/esm/js/convert.js.map
generated
vendored
Normal file
1
node_modules/@asamuzakjp/css-color/dist/esm/js/convert.js.map
generated
vendored
Normal file
File diff suppressed because one or more lines are too long
89
node_modules/@asamuzakjp/css-color/dist/esm/js/css-calc.d.ts
generated
vendored
Normal file
89
node_modules/@asamuzakjp/css-color/dist/esm/js/css-calc.d.ts
generated
vendored
Normal file
@@ -0,0 +1,89 @@
|
||||
import { CSSToken } from '@csstools/css-tokenizer';
|
||||
import { NullObject } from './cache.js';
|
||||
import { Options } from './typedef.js';
|
||||
/**
|
||||
* Calclator
|
||||
*/
|
||||
export declare class Calculator {
|
||||
#private;
|
||||
/**
|
||||
* constructor
|
||||
*/
|
||||
constructor();
|
||||
get hasNum(): boolean;
|
||||
set hasNum(value: boolean);
|
||||
get numSum(): number[];
|
||||
get numMul(): number[];
|
||||
get hasPct(): boolean;
|
||||
set hasPct(value: boolean);
|
||||
get pctSum(): number[];
|
||||
get pctMul(): number[];
|
||||
get hasDim(): boolean;
|
||||
set hasDim(value: boolean);
|
||||
get dimSum(): string[];
|
||||
get dimSub(): string[];
|
||||
get dimMul(): string[];
|
||||
get dimDiv(): string[];
|
||||
get hasEtc(): boolean;
|
||||
set hasEtc(value: boolean);
|
||||
get etcSum(): string[];
|
||||
get etcSub(): string[];
|
||||
get etcMul(): string[];
|
||||
get etcDiv(): string[];
|
||||
/**
|
||||
* clear values
|
||||
* @returns void
|
||||
*/
|
||||
clear(): void;
|
||||
/**
|
||||
* sort values
|
||||
* @param values - values
|
||||
* @returns sorted values
|
||||
*/
|
||||
sort(values?: string[]): string[];
|
||||
/**
|
||||
* multiply values
|
||||
* @returns resolved value
|
||||
*/
|
||||
multiply(): string;
|
||||
/**
|
||||
* sum values
|
||||
* @returns resolved value
|
||||
*/
|
||||
sum(): string;
|
||||
}
|
||||
/**
|
||||
* sort calc values
|
||||
* @param values - values to sort
|
||||
* @param [finalize] - finalize values
|
||||
* @returns sorted values
|
||||
*/
|
||||
export declare const sortCalcValues: (values?: (number | string)[], finalize?: boolean) => string;
|
||||
/**
|
||||
* serialize calc
|
||||
* @param value - CSS value
|
||||
* @param [opt] - options
|
||||
* @returns serialized value
|
||||
*/
|
||||
export declare const serializeCalc: (value: string, opt?: Options) => string;
|
||||
/**
|
||||
* resolve dimension
|
||||
* @param token - CSS token
|
||||
* @param [opt] - options
|
||||
* @returns resolved value
|
||||
*/
|
||||
export declare const resolveDimension: (token: CSSToken, opt?: Options) => string | NullObject;
|
||||
/**
|
||||
* parse tokens
|
||||
* @param tokens - CSS tokens
|
||||
* @param [opt] - options
|
||||
* @returns parsed tokens
|
||||
*/
|
||||
export declare const parseTokens: (tokens: CSSToken[], opt?: Options) => string[];
|
||||
/**
|
||||
* CSS calc()
|
||||
* @param value - CSS value including calc()
|
||||
* @param [opt] - options
|
||||
* @returns resolved value
|
||||
*/
|
||||
export declare const cssCalc: (value: string, opt?: Options) => string;
|
||||
636
node_modules/@asamuzakjp/css-color/dist/esm/js/css-calc.js
generated
vendored
Normal file
636
node_modules/@asamuzakjp/css-color/dist/esm/js/css-calc.js
generated
vendored
Normal file
@@ -0,0 +1,636 @@
|
||||
import { CacheItem, NullObject, createCacheKey, getCache, setCache } from "./cache.js";
|
||||
import { isString, isStringOrNumber } from "./common.js";
|
||||
import { ANGLE, LENGTH, NUM, SYN_FN_CALC, SYN_FN_MATH_START, SYN_FN_VAR, SYN_FN_VAR_START } from "./constant.js";
|
||||
import { resolveLengthInPixels, roundToPrecision } from "./util.js";
|
||||
import { resolveVar } from "./css-var.js";
|
||||
import { calc } from "@csstools/css-calc";
|
||||
import { TokenType, tokenize } from "@csstools/css-tokenizer";
|
||||
//#region src/js/css-calc.ts
|
||||
/**
|
||||
* css-calc
|
||||
*/
|
||||
var { CloseParen: PAREN_CLOSE, Comment: COMMENT, Dimension: DIM, EOF, Function: FUNC, OpenParen: PAREN_OPEN, Whitespace: W_SPACE } = TokenType;
|
||||
var NAMESPACE = "css-calc";
|
||||
var TRIA = 3;
|
||||
var HEX = 16;
|
||||
var MAX_PCT = 100;
|
||||
var REG_FN_CALC = new RegExp(SYN_FN_CALC);
|
||||
var REG_FN_CALC_NUM = new RegExp(`^calc\\((${NUM})\\)$`);
|
||||
var REG_FN_MATH_START = new RegExp(SYN_FN_MATH_START);
|
||||
var REG_FN_VAR = new RegExp(SYN_FN_VAR);
|
||||
var REG_FN_VAR_START = new RegExp(SYN_FN_VAR_START);
|
||||
var REG_OPERATOR = /\s[*+/-]\s/;
|
||||
var REG_PAREN_OPEN = /\($/;
|
||||
var REG_TYPE_DIM = new RegExp(`^(${NUM})(${ANGLE}|${LENGTH})$`);
|
||||
var REG_TYPE_DIM_PCT = new RegExp(`^(${NUM})(${ANGLE}|${LENGTH}|%)$`);
|
||||
var REG_TYPE_PCT = new RegExp(`^(${NUM})%$`);
|
||||
/**
|
||||
* Calclator
|
||||
*/
|
||||
var Calculator = class {
|
||||
#hasNum;
|
||||
#numSum;
|
||||
#numMul;
|
||||
#hasPct;
|
||||
#pctSum;
|
||||
#pctMul;
|
||||
#hasDim;
|
||||
#dimSum;
|
||||
#dimSub;
|
||||
#dimMul;
|
||||
#dimDiv;
|
||||
#hasEtc;
|
||||
#etcSum;
|
||||
#etcSub;
|
||||
#etcMul;
|
||||
#etcDiv;
|
||||
#calcOpts;
|
||||
/**
|
||||
* constructor
|
||||
*/
|
||||
constructor() {
|
||||
this.#hasNum = false;
|
||||
this.#numSum = [];
|
||||
this.#numMul = [];
|
||||
this.#hasPct = false;
|
||||
this.#pctSum = [];
|
||||
this.#pctMul = [];
|
||||
this.#hasDim = false;
|
||||
this.#dimSum = [];
|
||||
this.#dimSub = [];
|
||||
this.#dimMul = [];
|
||||
this.#dimDiv = [];
|
||||
this.#hasEtc = false;
|
||||
this.#etcSum = [];
|
||||
this.#etcSub = [];
|
||||
this.#etcMul = [];
|
||||
this.#etcDiv = [];
|
||||
this.#calcOpts = { toCanonicalUnits: true };
|
||||
}
|
||||
get hasNum() {
|
||||
return this.#hasNum;
|
||||
}
|
||||
set hasNum(value) {
|
||||
this.#hasNum = !!value;
|
||||
}
|
||||
get numSum() {
|
||||
return this.#numSum;
|
||||
}
|
||||
get numMul() {
|
||||
return this.#numMul;
|
||||
}
|
||||
get hasPct() {
|
||||
return this.#hasPct;
|
||||
}
|
||||
set hasPct(value) {
|
||||
this.#hasPct = !!value;
|
||||
}
|
||||
get pctSum() {
|
||||
return this.#pctSum;
|
||||
}
|
||||
get pctMul() {
|
||||
return this.#pctMul;
|
||||
}
|
||||
get hasDim() {
|
||||
return this.#hasDim;
|
||||
}
|
||||
set hasDim(value) {
|
||||
this.#hasDim = !!value;
|
||||
}
|
||||
get dimSum() {
|
||||
return this.#dimSum;
|
||||
}
|
||||
get dimSub() {
|
||||
return this.#dimSub;
|
||||
}
|
||||
get dimMul() {
|
||||
return this.#dimMul;
|
||||
}
|
||||
get dimDiv() {
|
||||
return this.#dimDiv;
|
||||
}
|
||||
get hasEtc() {
|
||||
return this.#hasEtc;
|
||||
}
|
||||
set hasEtc(value) {
|
||||
this.#hasEtc = !!value;
|
||||
}
|
||||
get etcSum() {
|
||||
return this.#etcSum;
|
||||
}
|
||||
get etcSub() {
|
||||
return this.#etcSub;
|
||||
}
|
||||
get etcMul() {
|
||||
return this.#etcMul;
|
||||
}
|
||||
get etcDiv() {
|
||||
return this.#etcDiv;
|
||||
}
|
||||
/**
|
||||
* clear values
|
||||
* @returns void
|
||||
*/
|
||||
clear() {
|
||||
this.#hasNum = false;
|
||||
this.#numSum.length = 0;
|
||||
this.#numMul.length = 0;
|
||||
this.#hasPct = false;
|
||||
this.#pctSum.length = 0;
|
||||
this.#pctMul.length = 0;
|
||||
this.#hasDim = false;
|
||||
this.#dimSum.length = 0;
|
||||
this.#dimSub.length = 0;
|
||||
this.#dimMul.length = 0;
|
||||
this.#dimDiv.length = 0;
|
||||
this.#hasEtc = false;
|
||||
this.#etcSum.length = 0;
|
||||
this.#etcSub.length = 0;
|
||||
this.#etcMul.length = 0;
|
||||
this.#etcDiv.length = 0;
|
||||
}
|
||||
/**
|
||||
* sort values
|
||||
* @param values - values
|
||||
* @returns sorted values
|
||||
*/
|
||||
sort(values = []) {
|
||||
const arr = [...values];
|
||||
if (arr.length > 1) arr.sort((a, b) => {
|
||||
let res;
|
||||
if (REG_TYPE_DIM_PCT.test(a) && REG_TYPE_DIM_PCT.test(b)) {
|
||||
const [, valA, unitA] = a.match(REG_TYPE_DIM_PCT);
|
||||
const [, valB, unitB] = b.match(REG_TYPE_DIM_PCT);
|
||||
if (unitA === unitB) if (Number(valA) === Number(valB)) res = 0;
|
||||
else if (Number(valA) > Number(valB)) res = 1;
|
||||
else res = -1;
|
||||
else if (unitA > unitB) res = 1;
|
||||
else res = -1;
|
||||
} else if (a === b) res = 0;
|
||||
else if (a > b) res = 1;
|
||||
else res = -1;
|
||||
return res;
|
||||
});
|
||||
return arr;
|
||||
}
|
||||
/**
|
||||
* multiply values
|
||||
* @returns resolved value
|
||||
*/
|
||||
multiply() {
|
||||
const value = [];
|
||||
let num;
|
||||
if (this.#hasNum) {
|
||||
num = 1;
|
||||
for (const i of this.#numMul) {
|
||||
num *= i;
|
||||
if (num === 0 || !Number.isFinite(num) || Number.isNaN(num)) break;
|
||||
}
|
||||
if (!this.#hasPct && !this.#hasDim && !this.hasEtc) {
|
||||
if (Number.isFinite(num)) num = roundToPrecision(num, HEX);
|
||||
value.push(num);
|
||||
}
|
||||
}
|
||||
if (this.#hasPct) {
|
||||
if (typeof num !== "number") num = 1;
|
||||
for (const i of this.#pctMul) {
|
||||
num *= i;
|
||||
if (num === 0 || !Number.isFinite(num) || Number.isNaN(num)) break;
|
||||
}
|
||||
if (Number.isFinite(num)) num = `${roundToPrecision(num, HEX)}%`;
|
||||
if (!this.#hasDim && !this.hasEtc) value.push(num);
|
||||
}
|
||||
if (this.#hasDim) {
|
||||
let dim = "";
|
||||
let mul = "";
|
||||
let div = "";
|
||||
if (this.#dimMul.length) if (this.#dimMul.length === 1) [mul] = this.#dimMul;
|
||||
else mul = `${this.sort(this.#dimMul).join(" * ")}`;
|
||||
if (this.#dimDiv.length) if (this.#dimDiv.length === 1) [div] = this.#dimDiv;
|
||||
else div = `${this.sort(this.#dimDiv).join(" * ")}`;
|
||||
if (Number.isFinite(num)) {
|
||||
if (mul) if (div) if (div.includes("*")) dim = calc(`calc(${num} * ${mul} / (${div}))`, this.#calcOpts);
|
||||
else dim = calc(`calc(${num} * ${mul} / ${div})`, this.#calcOpts);
|
||||
else dim = calc(`calc(${num} * ${mul})`, this.#calcOpts);
|
||||
else if (div.includes("*")) dim = calc(`calc(${num} / (${div}))`, this.#calcOpts);
|
||||
else dim = calc(`calc(${num} / ${div})`, this.#calcOpts);
|
||||
value.push(dim.replace(/^calc/, ""));
|
||||
} else {
|
||||
if (!value.length && num !== void 0) value.push(num);
|
||||
if (mul) {
|
||||
if (div) if (div.includes("*")) dim = calc(`calc(${mul} / (${div}))`, this.#calcOpts);
|
||||
else dim = calc(`calc(${mul} / ${div})`, this.#calcOpts);
|
||||
else dim = calc(`calc(${mul})`, this.#calcOpts);
|
||||
if (value.length) value.push("*", dim.replace(/^calc/, ""));
|
||||
else value.push(dim.replace(/^calc/, ""));
|
||||
} else {
|
||||
dim = calc(`calc(${div})`, this.#calcOpts);
|
||||
if (value.length) value.push("/", dim.replace(/^calc/, ""));
|
||||
else value.push("1", "/", dim.replace(/^calc/, ""));
|
||||
}
|
||||
}
|
||||
}
|
||||
if (this.#hasEtc) {
|
||||
if (this.#etcMul.length) {
|
||||
if (!value.length && num !== void 0) value.push(num);
|
||||
const mul = this.sort(this.#etcMul).join(" * ");
|
||||
if (value.length) value.push(`* ${mul}`);
|
||||
else value.push(`${mul}`);
|
||||
}
|
||||
if (this.#etcDiv.length) {
|
||||
const div = this.sort(this.#etcDiv).join(" * ");
|
||||
if (div.includes("*")) if (value.length) value.push(`/ (${div})`);
|
||||
else value.push(`1 / (${div})`);
|
||||
else if (value.length) value.push(`/ ${div}`);
|
||||
else value.push(`1 / ${div}`);
|
||||
}
|
||||
}
|
||||
if (value.length) return value.join(" ");
|
||||
return "";
|
||||
}
|
||||
/**
|
||||
* sum values
|
||||
* @returns resolved value
|
||||
*/
|
||||
sum() {
|
||||
const value = [];
|
||||
if (this.#hasNum) {
|
||||
let num = 0;
|
||||
for (const i of this.#numSum) {
|
||||
num += i;
|
||||
if (!Number.isFinite(num) || Number.isNaN(num)) break;
|
||||
}
|
||||
value.push(num);
|
||||
}
|
||||
if (this.#hasPct) {
|
||||
let num = 0;
|
||||
for (const i of this.#pctSum) {
|
||||
num += i;
|
||||
if (!Number.isFinite(num)) break;
|
||||
}
|
||||
if (Number.isFinite(num)) num = `${num}%`;
|
||||
if (value.length) value.push(`+ ${num}`);
|
||||
else value.push(num);
|
||||
}
|
||||
if (this.#hasDim) {
|
||||
let dim, sum, sub;
|
||||
if (this.#dimSum.length) sum = this.sort(this.#dimSum).join(" + ");
|
||||
if (this.#dimSub.length) sub = this.sort(this.#dimSub).join(" + ");
|
||||
if (sum) if (sub) if (sub.includes("-")) dim = calc(`calc(${sum} - (${sub}))`, this.#calcOpts);
|
||||
else dim = calc(`calc(${sum} - ${sub})`, this.#calcOpts);
|
||||
else dim = calc(`calc(${sum})`, this.#calcOpts);
|
||||
else dim = calc(`calc(-1 * (${sub}))`, this.#calcOpts);
|
||||
if (value.length) value.push("+", dim.replace(/^calc/, ""));
|
||||
else value.push(dim.replace(/^calc/, ""));
|
||||
}
|
||||
if (this.#hasEtc) {
|
||||
if (this.#etcSum.length) {
|
||||
const sum = this.sort(this.#etcSum).map((item) => {
|
||||
let res;
|
||||
if (REG_OPERATOR.test(item) && !item.startsWith("(") && !item.endsWith(")")) res = `(${item})`;
|
||||
else res = item;
|
||||
return res;
|
||||
}).join(" + ");
|
||||
if (value.length) if (this.#etcSum.length > 1) value.push(`+ (${sum})`);
|
||||
else value.push(`+ ${sum}`);
|
||||
else value.push(`${sum}`);
|
||||
}
|
||||
if (this.#etcSub.length) {
|
||||
const sub = this.sort(this.#etcSub).map((item) => {
|
||||
let res;
|
||||
if (REG_OPERATOR.test(item) && !item.startsWith("(") && !item.endsWith(")")) res = `(${item})`;
|
||||
else res = item;
|
||||
return res;
|
||||
}).join(" + ");
|
||||
if (value.length) if (this.#etcSub.length > 1) value.push(`- (${sub})`);
|
||||
else value.push(`- ${sub}`);
|
||||
else if (this.#etcSub.length > 1) value.push(`-1 * (${sub})`);
|
||||
else value.push(`-1 * ${sub}`);
|
||||
}
|
||||
}
|
||||
if (value.length) return value.join(" ");
|
||||
return "";
|
||||
}
|
||||
};
|
||||
/**
|
||||
* sort calc values
|
||||
* @param values - values to sort
|
||||
* @param [finalize] - finalize values
|
||||
* @returns sorted values
|
||||
*/
|
||||
var sortCalcValues = (values = [], finalize = false) => {
|
||||
if (values.length < TRIA) throw new Error(`Unexpected array length ${values.length}.`);
|
||||
const start = values.shift();
|
||||
if (!isString(start) || !start.endsWith("(")) throw new Error(`Unexpected token ${start}.`);
|
||||
const end = values.pop();
|
||||
if (end !== ")") throw new Error(`Unexpected token ${end}.`);
|
||||
if (values.length === 1) {
|
||||
const [value] = values;
|
||||
if (!isStringOrNumber(value)) throw new Error(`Unexpected token ${value}.`);
|
||||
return `${start}${value}${end}`;
|
||||
}
|
||||
const sortedValues = [];
|
||||
const cal = new Calculator();
|
||||
let operator = "";
|
||||
const l = values.length;
|
||||
let hasAddSub = false;
|
||||
for (let i = 0; i < l; i++) {
|
||||
const value = values[i];
|
||||
if (!isStringOrNumber(value)) throw new Error(`Unexpected token ${value}.`);
|
||||
if (value === "*" || value === "/") operator = value;
|
||||
else if (value === "+" || value === "-") {
|
||||
const sortedValue = cal.multiply();
|
||||
if (sortedValue) sortedValues.push(sortedValue, value);
|
||||
hasAddSub = true;
|
||||
cal.clear();
|
||||
operator = "";
|
||||
} else {
|
||||
const numValue = Number(value);
|
||||
const strValue = `${value}`;
|
||||
switch (operator) {
|
||||
case "/":
|
||||
if (Number.isFinite(numValue)) {
|
||||
cal.hasNum = true;
|
||||
cal.numMul.push(1 / numValue);
|
||||
} else if (REG_TYPE_PCT.test(strValue)) {
|
||||
const [, val] = strValue.match(REG_TYPE_PCT);
|
||||
cal.hasPct = true;
|
||||
cal.pctMul.push(MAX_PCT * MAX_PCT / Number(val));
|
||||
} else if (REG_TYPE_DIM.test(strValue)) {
|
||||
cal.hasDim = true;
|
||||
cal.dimDiv.push(strValue);
|
||||
} else {
|
||||
cal.hasEtc = true;
|
||||
cal.etcDiv.push(strValue);
|
||||
}
|
||||
break;
|
||||
default: if (Number.isFinite(numValue)) {
|
||||
cal.hasNum = true;
|
||||
cal.numMul.push(numValue);
|
||||
} else if (REG_TYPE_PCT.test(strValue)) {
|
||||
const [, val] = strValue.match(REG_TYPE_PCT);
|
||||
cal.hasPct = true;
|
||||
cal.pctMul.push(Number(val));
|
||||
} else if (REG_TYPE_DIM.test(strValue)) {
|
||||
cal.hasDim = true;
|
||||
cal.dimMul.push(strValue);
|
||||
} else {
|
||||
cal.hasEtc = true;
|
||||
cal.etcMul.push(strValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (i === l - 1) {
|
||||
const sortedValue = cal.multiply();
|
||||
if (sortedValue) sortedValues.push(sortedValue);
|
||||
cal.clear();
|
||||
operator = "";
|
||||
}
|
||||
}
|
||||
let resolvedValue = "";
|
||||
if (finalize && hasAddSub) {
|
||||
const finalizedValues = [];
|
||||
cal.clear();
|
||||
operator = "";
|
||||
const l = sortedValues.length;
|
||||
for (let i = 0; i < l; i++) {
|
||||
const value = sortedValues[i];
|
||||
if (isStringOrNumber(value)) if (value === "+" || value === "-") operator = value;
|
||||
else {
|
||||
const numValue = Number(value);
|
||||
const strValue = `${value}`;
|
||||
switch (operator) {
|
||||
case "-":
|
||||
if (Number.isFinite(numValue)) {
|
||||
cal.hasNum = true;
|
||||
cal.numSum.push(-1 * numValue);
|
||||
} else if (REG_TYPE_PCT.test(strValue)) {
|
||||
const [, val] = strValue.match(REG_TYPE_PCT);
|
||||
cal.hasPct = true;
|
||||
cal.pctSum.push(-1 * Number(val));
|
||||
} else if (REG_TYPE_DIM.test(strValue)) {
|
||||
cal.hasDim = true;
|
||||
cal.dimSub.push(strValue);
|
||||
} else {
|
||||
cal.hasEtc = true;
|
||||
cal.etcSub.push(strValue);
|
||||
}
|
||||
break;
|
||||
default: if (Number.isFinite(numValue)) {
|
||||
cal.hasNum = true;
|
||||
cal.numSum.push(numValue);
|
||||
} else if (REG_TYPE_PCT.test(strValue)) {
|
||||
const [, val] = strValue.match(REG_TYPE_PCT);
|
||||
cal.hasPct = true;
|
||||
cal.pctSum.push(Number(val));
|
||||
} else if (REG_TYPE_DIM.test(strValue)) {
|
||||
cal.hasDim = true;
|
||||
cal.dimSum.push(strValue);
|
||||
} else {
|
||||
cal.hasEtc = true;
|
||||
cal.etcSum.push(strValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (i === l - 1) {
|
||||
const sortedValue = cal.sum();
|
||||
if (sortedValue) finalizedValues.push(sortedValue);
|
||||
cal.clear();
|
||||
operator = "";
|
||||
}
|
||||
}
|
||||
resolvedValue = finalizedValues.join(" ").replace(/\+\s-/g, "- ");
|
||||
} else resolvedValue = sortedValues.join(" ").replace(/\+\s-/g, "- ");
|
||||
if (resolvedValue.startsWith("(") && resolvedValue.endsWith(")") && resolvedValue.lastIndexOf("(") === 0 && resolvedValue.indexOf(")") === resolvedValue.length - 1) resolvedValue = resolvedValue.substring(1, resolvedValue.length - 1);
|
||||
return `${start}${resolvedValue}${end}`;
|
||||
};
|
||||
/**
|
||||
* resolve AST node
|
||||
* @param node - AST node
|
||||
* @param isRoot - is root node
|
||||
* @returns resolved value
|
||||
*/
|
||||
var resolveNode = (node, isRoot) => {
|
||||
const flatItems = [];
|
||||
for (const item of node) if (Array.isArray(item)) flatItems.push(resolveNode(item, false));
|
||||
else flatItems.push(item);
|
||||
if (isRoot) {
|
||||
if (flatItems.length >= TRIA) return sortCalcValues(flatItems, true);
|
||||
const joined = flatItems.join("");
|
||||
return joined.startsWith("calc(") ? joined : `calc(${joined})`;
|
||||
}
|
||||
if (flatItems.length >= TRIA) {
|
||||
let serialized = sortCalcValues(flatItems, false);
|
||||
if (REG_FN_VAR_START.test(serialized)) serialized = calc(serialized, { toCanonicalUnits: true });
|
||||
return serialized;
|
||||
}
|
||||
return flatItems.join("");
|
||||
};
|
||||
/**
|
||||
* serialize calc
|
||||
* @param value - CSS value
|
||||
* @param [opt] - options
|
||||
* @returns serialized value
|
||||
*/
|
||||
var serializeCalc = (value, opt = {}) => {
|
||||
const { format = "" } = opt;
|
||||
if (isString(value)) {
|
||||
if (!REG_FN_VAR_START.test(value) || format !== "specifiedValue") return value;
|
||||
value = value.toLowerCase().trim();
|
||||
} else throw new TypeError(`${value} is not a string.`);
|
||||
const cacheKey = createCacheKey({
|
||||
namespace: NAMESPACE,
|
||||
name: "serializeCalc",
|
||||
value
|
||||
}, opt);
|
||||
const cachedResult = getCache(cacheKey);
|
||||
if (cachedResult instanceof CacheItem) return cachedResult.item;
|
||||
const items = tokenize({ css: value }).map((token) => {
|
||||
const [type, val] = token;
|
||||
let res = "";
|
||||
if (type !== W_SPACE && type !== COMMENT) res = val;
|
||||
return res;
|
||||
}).filter((v) => v);
|
||||
const stack = [[]];
|
||||
for (const item of items) if (REG_PAREN_OPEN.test(item)) {
|
||||
const newNode = [item];
|
||||
const parent = stack[stack.length - 1];
|
||||
if (parent) parent.push(newNode);
|
||||
stack.push(newNode);
|
||||
} else if (item === ")") if (stack.length > 1) {
|
||||
const currentLevel = stack.pop();
|
||||
if (currentLevel) currentLevel.push(item);
|
||||
} else {
|
||||
const root = stack[0];
|
||||
if (root) root.push(item);
|
||||
}
|
||||
else {
|
||||
const parent = stack[stack.length - 1];
|
||||
if (parent) parent.push(item);
|
||||
}
|
||||
let serializedCalc = "";
|
||||
const rootItems = stack[0];
|
||||
if (rootItems) if (rootItems.length === 1 && Array.isArray(rootItems[0])) serializedCalc = resolveNode(rootItems[0], true);
|
||||
else {
|
||||
const flatItems = [];
|
||||
for (const item of rootItems) if (Array.isArray(item)) flatItems.push(resolveNode(item, false));
|
||||
else flatItems.push(item);
|
||||
if (flatItems.length >= TRIA) serializedCalc = sortCalcValues(flatItems, true);
|
||||
else {
|
||||
const firstItem = flatItems[0] || "";
|
||||
serializedCalc = isString(firstItem) && firstItem.startsWith("calc(") ? firstItem : `calc(${firstItem})`;
|
||||
}
|
||||
}
|
||||
setCache(cacheKey, serializedCalc);
|
||||
return serializedCalc;
|
||||
};
|
||||
/**
|
||||
* resolve dimension
|
||||
* @param token - CSS token
|
||||
* @param [opt] - options
|
||||
* @returns resolved value
|
||||
*/
|
||||
var resolveDimension = (token, opt = {}) => {
|
||||
if (!Array.isArray(token)) throw new TypeError(`${token} is not an array.`);
|
||||
const [, , , , detail = {}] = token;
|
||||
const { unit, value } = detail;
|
||||
if (unit === "px") return `${value}${unit}`;
|
||||
const pixelValue = resolveLengthInPixels(Number(value), unit, opt);
|
||||
if (Number.isFinite(pixelValue)) return `${roundToPrecision(pixelValue, HEX)}px`;
|
||||
return new NullObject();
|
||||
};
|
||||
/**
|
||||
* parse tokens
|
||||
* @param tokens - CSS tokens
|
||||
* @param [opt] - options
|
||||
* @returns parsed tokens
|
||||
*/
|
||||
var parseTokens = (tokens, opt = {}) => {
|
||||
if (!Array.isArray(tokens)) throw new TypeError(`${tokens} is not an array.`);
|
||||
const { format = "" } = opt;
|
||||
const mathFunc = /* @__PURE__ */ new Set();
|
||||
let nest = 0;
|
||||
const res = [];
|
||||
for (const token of tokens) {
|
||||
if (!Array.isArray(token)) throw new TypeError(`${token} is not an array.`);
|
||||
const [type = "", value = ""] = token;
|
||||
switch (type) {
|
||||
case DIM:
|
||||
if (format === "specifiedValue" && !mathFunc.has(nest)) res.push(value);
|
||||
else {
|
||||
const resolvedValue = resolveDimension(token, opt);
|
||||
if (isString(resolvedValue)) res.push(resolvedValue);
|
||||
else res.push(value);
|
||||
}
|
||||
break;
|
||||
case FUNC:
|
||||
case PAREN_OPEN:
|
||||
res.push(value);
|
||||
nest++;
|
||||
if (REG_FN_MATH_START.test(value)) mathFunc.add(nest);
|
||||
break;
|
||||
case PAREN_CLOSE:
|
||||
if (res.length) if (res[res.length - 1] === " ") res.splice(-1, 1, value);
|
||||
else res.push(value);
|
||||
else res.push(value);
|
||||
if (mathFunc.has(nest)) mathFunc.delete(nest);
|
||||
nest--;
|
||||
break;
|
||||
case W_SPACE:
|
||||
if (res.length) {
|
||||
const lastValue = res[res.length - 1];
|
||||
if (isString(lastValue) && !lastValue.endsWith("(") && lastValue !== " ") res.push(value);
|
||||
}
|
||||
break;
|
||||
default: if (type !== COMMENT && type !== EOF) res.push(value);
|
||||
}
|
||||
}
|
||||
return res;
|
||||
};
|
||||
/**
|
||||
* CSS calc()
|
||||
* @param value - CSS value including calc()
|
||||
* @param [opt] - options
|
||||
* @returns resolved value
|
||||
*/
|
||||
var cssCalc = (value, opt = {}) => {
|
||||
const { format = "" } = opt;
|
||||
if (isString(value)) {
|
||||
if (REG_FN_VAR.test(value)) if (format === "specifiedValue") return value;
|
||||
else {
|
||||
const resolvedValue = resolveVar(value, opt);
|
||||
if (isString(resolvedValue)) return resolvedValue;
|
||||
else return "";
|
||||
}
|
||||
else if (!REG_FN_CALC.test(value)) return value;
|
||||
value = value.toLowerCase().trim();
|
||||
} else throw new TypeError(`${value} is not a string.`);
|
||||
const cacheKey = createCacheKey({
|
||||
namespace: NAMESPACE,
|
||||
name: "cssCalc",
|
||||
value
|
||||
}, opt);
|
||||
const cachedResult = getCache(cacheKey);
|
||||
if (cachedResult instanceof CacheItem) return cachedResult.item;
|
||||
let resolvedValue = calc(parseTokens(tokenize({ css: value }), opt).join(""), { toCanonicalUnits: true });
|
||||
if (REG_FN_VAR_START.test(value)) {
|
||||
if (REG_TYPE_DIM_PCT.test(resolvedValue)) {
|
||||
const [, val, unit] = resolvedValue.match(REG_TYPE_DIM_PCT);
|
||||
resolvedValue = `${roundToPrecision(Number(val), HEX)}${unit}`;
|
||||
}
|
||||
if (resolvedValue && !REG_FN_VAR_START.test(resolvedValue) && format === "specifiedValue") resolvedValue = `calc(${resolvedValue})`;
|
||||
}
|
||||
if (format === "specifiedValue") {
|
||||
if (/\s[-+*/]\s/.test(resolvedValue) && !resolvedValue.includes("NaN")) resolvedValue = serializeCalc(resolvedValue, opt);
|
||||
else if (REG_FN_CALC_NUM.test(resolvedValue)) {
|
||||
const [, val] = resolvedValue.match(REG_FN_CALC_NUM);
|
||||
resolvedValue = `calc(${roundToPrecision(Number(val), HEX)})`;
|
||||
}
|
||||
}
|
||||
setCache(cacheKey, resolvedValue);
|
||||
return resolvedValue;
|
||||
};
|
||||
//#endregion
|
||||
export { cssCalc, resolveDimension, serializeCalc };
|
||||
|
||||
//# sourceMappingURL=css-calc.js.map
|
||||
1
node_modules/@asamuzakjp/css-color/dist/esm/js/css-calc.js.map
generated
vendored
Normal file
1
node_modules/@asamuzakjp/css-color/dist/esm/js/css-calc.js.map
generated
vendored
Normal file
File diff suppressed because one or more lines are too long
79
node_modules/@asamuzakjp/css-color/dist/esm/js/css-gradient.d.ts
generated
vendored
Normal file
79
node_modules/@asamuzakjp/css-color/dist/esm/js/css-gradient.d.ts
generated
vendored
Normal file
@@ -0,0 +1,79 @@
|
||||
import { Options } from './typedef.js';
|
||||
/**
|
||||
* @type ColorStopList - list of color stops
|
||||
*/
|
||||
type ColorStopList = [string, string, ...string[]];
|
||||
/**
|
||||
* @typedef ValidateGradientLine - validate gradient line
|
||||
* @property line - gradient line
|
||||
* @property valid - result
|
||||
*/
|
||||
interface ValidateGradientLine {
|
||||
line: string;
|
||||
valid: boolean;
|
||||
}
|
||||
/**
|
||||
* @typedef ValidateColorStops - validate color stops
|
||||
* @property colorStops - list of color stops
|
||||
* @property valid - result
|
||||
*/
|
||||
interface ValidateColorStops {
|
||||
colorStops: string[];
|
||||
valid: boolean;
|
||||
}
|
||||
/**
|
||||
* @typedef Gradient - parsed CSS gradient
|
||||
* @property value - input value
|
||||
* @property type - gradient type
|
||||
* @property [gradientLine] - gradient line
|
||||
* @property colorStopList - list of color stops
|
||||
*/
|
||||
interface Gradient {
|
||||
value: string;
|
||||
type: string;
|
||||
gradientLine?: string;
|
||||
colorStopList: ColorStopList;
|
||||
}
|
||||
/**
|
||||
* get gradient type
|
||||
* @param value - gradient value
|
||||
* @returns gradient type
|
||||
*/
|
||||
export declare const getGradientType: (value: string) => string;
|
||||
/**
|
||||
* validate gradient line
|
||||
* @param value - gradient line value
|
||||
* @param type - gradient type
|
||||
* @returns result
|
||||
*/
|
||||
export declare const validateGradientLine: (value: string, type: string) => ValidateGradientLine;
|
||||
/**
|
||||
* validate color stop list
|
||||
* @param list
|
||||
* @param type
|
||||
* @param [opt]
|
||||
* @returns result
|
||||
*/
|
||||
export declare const validateColorStopList: (list: string[], type: string, opt?: Options) => ValidateColorStops;
|
||||
/**
|
||||
* parse CSS gradient
|
||||
* @param value - gradient value
|
||||
* @param [opt] - options
|
||||
* @returns parsed result
|
||||
*/
|
||||
export declare const parseGradient: (value: string, opt?: Options) => Gradient | null;
|
||||
/**
|
||||
* resolve CSS gradient
|
||||
* @param value - CSS value
|
||||
* @param [opt] - options
|
||||
* @returns result
|
||||
*/
|
||||
export declare const resolveGradient: (value: string, opt?: Options) => string;
|
||||
/**
|
||||
* is CSS gradient
|
||||
* @param value - CSS value
|
||||
* @param [opt] - options
|
||||
* @returns result
|
||||
*/
|
||||
export declare const isGradient: (value: string, opt?: Options) => boolean;
|
||||
export {};
|
||||
291
node_modules/@asamuzakjp/css-color/dist/esm/js/css-gradient.js
generated
vendored
Normal file
291
node_modules/@asamuzakjp/css-color/dist/esm/js/css-gradient.js
generated
vendored
Normal file
@@ -0,0 +1,291 @@
|
||||
import { CacheItem, createCacheKey, getCache, setCache } from "./cache.js";
|
||||
import { isString } from "./common.js";
|
||||
import { ANGLE, CS_HUE, CS_RECT, LENGTH, NUM, NUM_POSITIVE, PCT, VAL_COMP } from "./constant.js";
|
||||
import { resolveColor } from "./resolve.js";
|
||||
import { isColor, splitValue } from "./util.js";
|
||||
//#region src/js/css-gradient.ts
|
||||
/**
|
||||
* css-gradient
|
||||
*/
|
||||
var NAMESPACE = "css-gradient";
|
||||
var DIM_ANGLE = `${NUM}(?:${ANGLE})`;
|
||||
var DIM_ANGLE_PCT = `${DIM_ANGLE}|${PCT}`;
|
||||
var DIM_LEN_PCT = `${`${NUM}(?:${LENGTH})|0`}|${PCT}`;
|
||||
var DIM_LEN_PCT_POSI = `${NUM_POSITIVE}(?:${LENGTH}|%)|0`;
|
||||
var DIM_LEN_POSI = `${NUM_POSITIVE}(?:${LENGTH})|0`;
|
||||
var CTR = "center";
|
||||
var L_R = "left|right";
|
||||
var T_B = "top|bottom";
|
||||
var S_E = "start|end";
|
||||
var AXIS_X = `${L_R}|x-(?:${S_E})`;
|
||||
var AXIS_Y = `${T_B}|y-(?:${S_E})`;
|
||||
var BLOCK = `block-(?:${S_E})`;
|
||||
var INLINE = `inline-(?:${S_E})`;
|
||||
var POS_1 = `${CTR}|${AXIS_X}|${AXIS_Y}|${BLOCK}|${INLINE}|${DIM_LEN_PCT}`;
|
||||
var POS_2 = [
|
||||
`(?:${CTR}|${AXIS_X})\\s+(?:${CTR}|${AXIS_Y})`,
|
||||
`(?:${CTR}|${AXIS_Y})\\s+(?:${CTR}|${AXIS_X})`,
|
||||
`(?:${CTR}|${AXIS_X}|${DIM_LEN_PCT})\\s+(?:${CTR}|${AXIS_Y}|${DIM_LEN_PCT})`,
|
||||
`(?:${CTR}|${BLOCK})\\s+(?:${CTR}|${INLINE})`,
|
||||
`(?:${CTR}|${INLINE})\\s+(?:${CTR}|${BLOCK})`,
|
||||
`(?:${CTR}|${S_E})\\s+(?:${CTR}|${S_E})`
|
||||
].join("|");
|
||||
var POS_4 = [
|
||||
`(?:${AXIS_X})\\s+(?:${DIM_LEN_PCT})\\s+(?:${AXIS_Y})\\s+(?:${DIM_LEN_PCT})`,
|
||||
`(?:${AXIS_Y})\\s+(?:${DIM_LEN_PCT})\\s+(?:${AXIS_X})\\s+(?:${DIM_LEN_PCT})`,
|
||||
`(?:${BLOCK})\\s+(?:${DIM_LEN_PCT})\\s+(?:${INLINE})\\s+(?:${DIM_LEN_PCT})`,
|
||||
`(?:${INLINE})\\s+(?:${DIM_LEN_PCT})\\s+(?:${BLOCK})\\s+(?:${DIM_LEN_PCT})`,
|
||||
`(?:${S_E})\\s+(?:${DIM_LEN_PCT})\\s+(?:${S_E})\\s+(?:${DIM_LEN_PCT})`
|
||||
].join("|");
|
||||
var RAD_EXTENT = "(?:clos|farth)est-(?:corner|side)";
|
||||
var RAD_SIZE = [
|
||||
`${RAD_EXTENT}(?:\\s+${RAD_EXTENT})?`,
|
||||
`${DIM_LEN_POSI}`,
|
||||
`(?:${DIM_LEN_PCT_POSI})\\s+(?:${DIM_LEN_PCT_POSI})`
|
||||
].join("|");
|
||||
var RAD_SHAPE = "circle|ellipse";
|
||||
var FROM_ANGLE = `from\\s+${DIM_ANGLE}`;
|
||||
var AT_POSITION = `at\\s+(?:${POS_1}|${POS_2}|${POS_4})`;
|
||||
var TO_SIDE_CORNER = `to\\s+(?:(?:${L_R})(?:\\s(?:${T_B}))?|(?:${T_B})(?:\\s(?:${L_R}))?)`;
|
||||
var IN_COLOR_SPACE = `in\\s+(?:${CS_RECT}|${CS_HUE})`;
|
||||
var LINE_SYNTAX_LINEAR = [`(?:${DIM_ANGLE}|${TO_SIDE_CORNER})(?:\\s+${IN_COLOR_SPACE})?`, `${IN_COLOR_SPACE}(?:\\s+(?:${DIM_ANGLE}|${TO_SIDE_CORNER}))?`].join("|");
|
||||
var LINE_SYNTAX_RADIAL = [
|
||||
`(?:${RAD_SHAPE})(?:\\s+(?:${RAD_SIZE}))?(?:\\s+${AT_POSITION})?(?:\\s+${IN_COLOR_SPACE})?`,
|
||||
`(?:${RAD_SIZE})(?:\\s+(?:${RAD_SHAPE}))?(?:\\s+${AT_POSITION})?(?:\\s+${IN_COLOR_SPACE})?`,
|
||||
`${AT_POSITION}(?:\\s+${IN_COLOR_SPACE})?`,
|
||||
`${IN_COLOR_SPACE}(?:\\s+${RAD_SHAPE})(?:\\s+(?:${RAD_SIZE}))?(?:\\s+${AT_POSITION})?`,
|
||||
`${IN_COLOR_SPACE}(?:\\s+${RAD_SIZE})(?:\\s+(?:${RAD_SHAPE}))?(?:\\s+${AT_POSITION})?`,
|
||||
`${IN_COLOR_SPACE}(?:\\s+${AT_POSITION})?`
|
||||
].join("|");
|
||||
var LINE_SYNTAX_CONIC = [
|
||||
`${FROM_ANGLE}(?:\\s+${AT_POSITION})?(?:\\s+${IN_COLOR_SPACE})?`,
|
||||
`${AT_POSITION}(?:\\s+${IN_COLOR_SPACE})?`,
|
||||
`${IN_COLOR_SPACE}(?:\\s+${FROM_ANGLE})?(?:\\s+${AT_POSITION})?`
|
||||
].join("|");
|
||||
var DEFAULT_LINEAR = [/to\s+bottom/];
|
||||
var DEFAULT_RADIAL = [
|
||||
/ellipse/,
|
||||
/farthest-corner/,
|
||||
/at\s+center/
|
||||
];
|
||||
var DEFAULT_CONIC = [/at\s+center/];
|
||||
var IS_CONIC = /^(?:repeating-)?conic-gradient$/;
|
||||
var IS_LINEAR = /^(?:repeating-)?linear-gradient$/;
|
||||
var IS_RADIAL = /^(?:repeating-)?radial-gradient$/;
|
||||
var REG_COLOR_HINT_CONIC = new RegExp(`^(?:${DIM_ANGLE_PCT})$`);
|
||||
var REG_COLOR_HINT_NON_CONIC = new RegExp(`^(?:${DIM_LEN_PCT})$`);
|
||||
var REG_DIM_CONIC = new RegExp(`(?:\\s+(?:${DIM_ANGLE_PCT})){1,2}$`);
|
||||
var REG_DIM_NON_CONIC = new RegExp(`(?:\\s+(?:${DIM_LEN_PCT})){1,2}$`);
|
||||
var REG_GRAD = /^(?:repeating-)?(?:conic|linear|radial)-gradient\(/;
|
||||
var REG_GRAD_CAPT = /^((?:repeating-)?(?:conic|linear|radial)-gradient)\(/;
|
||||
var REG_LINE_CONIC = new RegExp(`^(?:${LINE_SYNTAX_CONIC})$`);
|
||||
var REG_LINE_LINEAR = new RegExp(`^(?:${LINE_SYNTAX_LINEAR})$`);
|
||||
var REG_LINE_RADIAL = new RegExp(`^(?:${LINE_SYNTAX_RADIAL})$`);
|
||||
/**
|
||||
* get gradient type
|
||||
* @param value - gradient value
|
||||
* @returns gradient type
|
||||
*/
|
||||
var getGradientType = (value) => {
|
||||
if (isString(value)) {
|
||||
value = value.trim();
|
||||
if (REG_GRAD.test(value)) {
|
||||
const [, type] = value.match(REG_GRAD_CAPT);
|
||||
return type;
|
||||
}
|
||||
}
|
||||
return "";
|
||||
};
|
||||
/**
|
||||
* validate gradient line
|
||||
* @param value - gradient line value
|
||||
* @param type - gradient type
|
||||
* @returns result
|
||||
*/
|
||||
var validateGradientLine = (value, type) => {
|
||||
if (isString(value) && isString(type)) {
|
||||
value = value.trim();
|
||||
type = type.trim();
|
||||
let reg = null;
|
||||
let defaultValues = [];
|
||||
if (IS_LINEAR.test(type)) {
|
||||
reg = REG_LINE_LINEAR;
|
||||
defaultValues = DEFAULT_LINEAR;
|
||||
} else if (IS_RADIAL.test(type)) {
|
||||
reg = REG_LINE_RADIAL;
|
||||
defaultValues = DEFAULT_RADIAL;
|
||||
} else if (IS_CONIC.test(type)) {
|
||||
reg = REG_LINE_CONIC;
|
||||
defaultValues = DEFAULT_CONIC;
|
||||
}
|
||||
if (reg) {
|
||||
const valid = reg.test(value);
|
||||
if (valid) {
|
||||
let line = value;
|
||||
for (const defaultValue of defaultValues) line = line.replace(defaultValue, "");
|
||||
line = line.replace(/\s{2,}/g, " ").trim();
|
||||
return {
|
||||
line,
|
||||
valid
|
||||
};
|
||||
}
|
||||
return {
|
||||
valid,
|
||||
line: value
|
||||
};
|
||||
}
|
||||
}
|
||||
return {
|
||||
line: value,
|
||||
valid: false
|
||||
};
|
||||
};
|
||||
/**
|
||||
* validate color stop list
|
||||
* @param list
|
||||
* @param type
|
||||
* @param [opt]
|
||||
* @returns result
|
||||
*/
|
||||
var validateColorStopList = (list, type, opt = {}) => {
|
||||
if (Array.isArray(list) && list.length > 1) {
|
||||
const isConic = IS_CONIC.test(type);
|
||||
const regColorHint = isConic ? REG_COLOR_HINT_CONIC : REG_COLOR_HINT_NON_CONIC;
|
||||
const regDimension = isConic ? REG_DIM_CONIC : REG_DIM_NON_CONIC;
|
||||
const valueList = [];
|
||||
let prevType = "";
|
||||
for (let i = 0; i < list.length; i++) {
|
||||
const item = list[i];
|
||||
if (isString(item)) if (regColorHint.test(item)) {
|
||||
if (i === 0 || prevType === "hint") return {
|
||||
colorStops: list,
|
||||
valid: false
|
||||
};
|
||||
prevType = "hint";
|
||||
valueList.push(item);
|
||||
} else {
|
||||
const itemColor = item.replace(regDimension, "");
|
||||
if (isColor(itemColor, { format: "specifiedValue" })) {
|
||||
const resolvedColor = resolveColor(itemColor, opt);
|
||||
prevType = "color";
|
||||
valueList.push(item.replace(itemColor, resolvedColor));
|
||||
} else return {
|
||||
colorStops: list,
|
||||
valid: false
|
||||
};
|
||||
}
|
||||
else return {
|
||||
colorStops: list,
|
||||
valid: false
|
||||
};
|
||||
}
|
||||
if (prevType !== "color") return {
|
||||
colorStops: list,
|
||||
valid: false
|
||||
};
|
||||
return {
|
||||
valid: true,
|
||||
colorStops: valueList
|
||||
};
|
||||
}
|
||||
return {
|
||||
colorStops: list,
|
||||
valid: false
|
||||
};
|
||||
};
|
||||
/**
|
||||
* parse CSS gradient
|
||||
* @param value - gradient value
|
||||
* @param [opt] - options
|
||||
* @returns parsed result
|
||||
*/
|
||||
var parseGradient = (value, opt = {}) => {
|
||||
if (isString(value)) {
|
||||
value = value.trim();
|
||||
const cacheKey = createCacheKey({
|
||||
namespace: NAMESPACE,
|
||||
name: "parseGradient",
|
||||
value
|
||||
}, opt);
|
||||
const cachedResult = getCache(cacheKey);
|
||||
if (cachedResult instanceof CacheItem) {
|
||||
if (cachedResult.isNull) return null;
|
||||
return cachedResult.item;
|
||||
}
|
||||
const type = getGradientType(value);
|
||||
const gradValue = value.replace(REG_GRAD, "").replace(/\)$/, "");
|
||||
if (type && gradValue) {
|
||||
const [lineOrColorStop = "", ...itemList] = splitValue(gradValue, { delimiter: "," });
|
||||
const regDimension = IS_CONIC.test(type) ? REG_DIM_CONIC : REG_DIM_NON_CONIC;
|
||||
let colorStop = "";
|
||||
if (regDimension.test(lineOrColorStop)) {
|
||||
const itemColor = lineOrColorStop.replace(regDimension, "");
|
||||
if (isColor(itemColor, { format: "specifiedValue" })) {
|
||||
const resolvedColor = resolveColor(itemColor, opt);
|
||||
colorStop = lineOrColorStop.replace(itemColor, resolvedColor);
|
||||
}
|
||||
} else if (isColor(lineOrColorStop, { format: "specifiedValue" })) colorStop = resolveColor(lineOrColorStop, opt);
|
||||
if (colorStop) {
|
||||
itemList.unshift(colorStop);
|
||||
const { colorStops, valid } = validateColorStopList(itemList, type, opt);
|
||||
if (valid) {
|
||||
const res = {
|
||||
value,
|
||||
type,
|
||||
colorStopList: colorStops
|
||||
};
|
||||
setCache(cacheKey, res);
|
||||
return res;
|
||||
}
|
||||
} else if (itemList.length > 1) {
|
||||
const { line: gradientLine, valid: validLine } = validateGradientLine(lineOrColorStop, type);
|
||||
const { colorStops, valid: validColorStops } = validateColorStopList(itemList, type, opt);
|
||||
if (validLine && validColorStops) {
|
||||
const res = {
|
||||
value,
|
||||
type,
|
||||
gradientLine,
|
||||
colorStopList: colorStops
|
||||
};
|
||||
setCache(cacheKey, res);
|
||||
return res;
|
||||
}
|
||||
}
|
||||
}
|
||||
setCache(cacheKey, null);
|
||||
return null;
|
||||
}
|
||||
return null;
|
||||
};
|
||||
/**
|
||||
* resolve CSS gradient
|
||||
* @param value - CSS value
|
||||
* @param [opt] - options
|
||||
* @returns result
|
||||
*/
|
||||
var resolveGradient = (value, opt = {}) => {
|
||||
const { format = VAL_COMP } = opt;
|
||||
const gradient = parseGradient(value, opt);
|
||||
if (gradient) {
|
||||
const { type = "", gradientLine = "", colorStopList = [] } = gradient;
|
||||
if (type && Array.isArray(colorStopList) && colorStopList.length > 1) {
|
||||
if (gradientLine) return `${type}(${gradientLine}, ${colorStopList.join(", ")})`;
|
||||
return `${type}(${colorStopList.join(", ")})`;
|
||||
}
|
||||
}
|
||||
if (format === "specifiedValue") return "";
|
||||
return "none";
|
||||
};
|
||||
/**
|
||||
* is CSS gradient
|
||||
* @param value - CSS value
|
||||
* @param [opt] - options
|
||||
* @returns result
|
||||
*/
|
||||
var isGradient = (value, opt = {}) => {
|
||||
return parseGradient(value, opt) !== null;
|
||||
};
|
||||
//#endregion
|
||||
export { isGradient, resolveGradient };
|
||||
|
||||
//# sourceMappingURL=css-gradient.js.map
|
||||
1
node_modules/@asamuzakjp/css-color/dist/esm/js/css-gradient.js.map
generated
vendored
Normal file
1
node_modules/@asamuzakjp/css-color/dist/esm/js/css-gradient.js.map
generated
vendored
Normal file
File diff suppressed because one or more lines are too long
31
node_modules/@asamuzakjp/css-color/dist/esm/js/css-var.d.ts
generated
vendored
Normal file
31
node_modules/@asamuzakjp/css-color/dist/esm/js/css-var.d.ts
generated
vendored
Normal file
@@ -0,0 +1,31 @@
|
||||
import { CSSToken } from '@csstools/css-tokenizer';
|
||||
import { NullObject } from './cache.js';
|
||||
import { Options } from './typedef.js';
|
||||
/**
|
||||
* resolve custom property
|
||||
* @param tokens - CSS tokens
|
||||
* @param [opt] - options
|
||||
* @returns result - [tokens, resolvedValue]
|
||||
*/
|
||||
export declare function resolveCustomProperty(tokens: CSSToken[], opt?: Options): [CSSToken[], string];
|
||||
/**
|
||||
* parse tokens
|
||||
* @param tokens - CSS tokens
|
||||
* @param [opt] - options
|
||||
* @returns parsed tokens
|
||||
*/
|
||||
export declare function parseTokens(tokens: CSSToken[], opt?: Options): string[] | NullObject;
|
||||
/**
|
||||
* resolve CSS var()
|
||||
* @param value - CSS value including var()
|
||||
* @param [opt] - options
|
||||
* @returns resolved value
|
||||
*/
|
||||
export declare function resolveVar(value: string, opt?: Options): string | NullObject;
|
||||
/**
|
||||
* CSS var()
|
||||
* @param value - CSS value including var()
|
||||
* @param [opt] - options
|
||||
* @returns resolved value
|
||||
*/
|
||||
export declare const cssVar: (value: string, opt?: Options) => string;
|
||||
144
node_modules/@asamuzakjp/css-color/dist/esm/js/css-var.js
generated
vendored
Normal file
144
node_modules/@asamuzakjp/css-color/dist/esm/js/css-var.js
generated
vendored
Normal file
@@ -0,0 +1,144 @@
|
||||
import { CacheItem, NullObject, createCacheKey, getCache, setCache } from "./cache.js";
|
||||
import { isString } from "./common.js";
|
||||
import { SYN_FN_CALC, SYN_FN_VAR } from "./constant.js";
|
||||
import { isColor } from "./util.js";
|
||||
import { cssCalc } from "./css-calc.js";
|
||||
import { TokenType, tokenize } from "@csstools/css-tokenizer";
|
||||
//#region src/js/css-var.ts
|
||||
/**
|
||||
* css-var
|
||||
*/
|
||||
var { CloseParen: PAREN_CLOSE, Comment: COMMENT, EOF, Ident: IDENT, Whitespace: W_SPACE } = TokenType;
|
||||
var NAMESPACE = "css-var";
|
||||
var REG_FN_CALC = new RegExp(SYN_FN_CALC);
|
||||
var REG_FN_VAR = new RegExp(SYN_FN_VAR);
|
||||
var REG_CSS_WIDE_KEYWORD = /^(?:inherit|initial|revert(?:-layer)?|unset)$/;
|
||||
/**
|
||||
* resolve custom property
|
||||
* @param tokens - CSS tokens
|
||||
* @param [opt] - options
|
||||
* @returns result - [tokens, resolvedValue]
|
||||
*/
|
||||
function resolveCustomProperty(tokens, opt = {}) {
|
||||
if (!Array.isArray(tokens)) throw new TypeError(`${tokens} is not an array.`);
|
||||
const { customProperty = {} } = opt;
|
||||
const items = [];
|
||||
while (tokens.length) {
|
||||
const token = tokens.shift();
|
||||
if (!token) break;
|
||||
if (!Array.isArray(token)) throw new TypeError(`${token} is not an array.`);
|
||||
const [type, value] = token;
|
||||
if (type === PAREN_CLOSE) break;
|
||||
if (value === "var(") {
|
||||
const [, item] = resolveCustomProperty(tokens, opt);
|
||||
if (item) items.push(item);
|
||||
} else if (type === IDENT) {
|
||||
if (value.startsWith("--")) {
|
||||
let item;
|
||||
if (Object.hasOwn(customProperty, value)) item = customProperty[value];
|
||||
else if (typeof customProperty.callback === "function") item = customProperty.callback(value);
|
||||
if (item) items.push(item);
|
||||
} else if (value) items.push(value);
|
||||
}
|
||||
}
|
||||
let resolveAsColor = false;
|
||||
if (items.length > 1) resolveAsColor = isColor(items[items.length - 1]);
|
||||
let resolvedValue = "";
|
||||
for (let item of items) {
|
||||
item = item.trim();
|
||||
if (REG_FN_VAR.test(item)) {
|
||||
const resolvedItem = resolveVar(item, opt);
|
||||
if (isString(resolvedItem)) {
|
||||
if (!resolveAsColor || isColor(resolvedItem)) resolvedValue = resolvedItem;
|
||||
}
|
||||
} else if (REG_FN_CALC.test(item)) {
|
||||
item = cssCalc(item, opt);
|
||||
if (!resolveAsColor || isColor(item)) resolvedValue = item;
|
||||
} else if (item && !REG_CSS_WIDE_KEYWORD.test(item)) {
|
||||
if (!resolveAsColor || isColor(item)) resolvedValue = item;
|
||||
}
|
||||
if (resolvedValue) break;
|
||||
}
|
||||
return [tokens, resolvedValue];
|
||||
}
|
||||
/**
|
||||
* parse tokens
|
||||
* @param tokens - CSS tokens
|
||||
* @param [opt] - options
|
||||
* @returns parsed tokens
|
||||
*/
|
||||
function parseTokens(tokens, opt = {}) {
|
||||
const res = [];
|
||||
while (tokens.length) {
|
||||
const token = tokens.shift();
|
||||
if (!token) break;
|
||||
const [type = "", value = ""] = token;
|
||||
if (value === "var(") {
|
||||
const [, resolvedValue] = resolveCustomProperty(tokens, opt);
|
||||
if (!resolvedValue) return new NullObject();
|
||||
res.push(resolvedValue);
|
||||
} else switch (type) {
|
||||
case PAREN_CLOSE:
|
||||
if (res.length) if (res[res.length - 1] === " ") res[res.length - 1] = value;
|
||||
else res.push(value);
|
||||
else res.push(value);
|
||||
break;
|
||||
case W_SPACE:
|
||||
if (res.length) {
|
||||
const lastValue = res[res.length - 1];
|
||||
if (isString(lastValue) && !lastValue.endsWith("(") && lastValue !== " ") res.push(value);
|
||||
}
|
||||
break;
|
||||
default: if (type !== COMMENT && type !== EOF) res.push(value);
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
/**
|
||||
* resolve CSS var()
|
||||
* @param value - CSS value including var()
|
||||
* @param [opt] - options
|
||||
* @returns resolved value
|
||||
*/
|
||||
function resolveVar(value, opt = {}) {
|
||||
const { format = "" } = opt;
|
||||
if (isString(value)) {
|
||||
if (!REG_FN_VAR.test(value) || format === "specifiedValue") return value;
|
||||
value = value.trim();
|
||||
} else throw new TypeError(`${value} is not a string.`);
|
||||
const cacheKey = createCacheKey({
|
||||
namespace: NAMESPACE,
|
||||
name: "resolveVar",
|
||||
value
|
||||
}, opt);
|
||||
const cachedResult = getCache(cacheKey);
|
||||
if (cachedResult instanceof CacheItem) {
|
||||
if (cachedResult.isNull) return cachedResult;
|
||||
return cachedResult.item;
|
||||
}
|
||||
const values = parseTokens(tokenize({ css: value }), opt);
|
||||
if (Array.isArray(values)) {
|
||||
let color = values.join("");
|
||||
if (REG_FN_CALC.test(color)) color = cssCalc(color, opt);
|
||||
setCache(cacheKey, color);
|
||||
return color;
|
||||
} else {
|
||||
setCache(cacheKey, null);
|
||||
return new NullObject();
|
||||
}
|
||||
}
|
||||
/**
|
||||
* CSS var()
|
||||
* @param value - CSS value including var()
|
||||
* @param [opt] - options
|
||||
* @returns resolved value
|
||||
*/
|
||||
var cssVar = (value, opt = {}) => {
|
||||
const resolvedValue = resolveVar(value, opt);
|
||||
if (isString(resolvedValue)) return resolvedValue;
|
||||
return "";
|
||||
};
|
||||
//#endregion
|
||||
export { cssVar, resolveVar };
|
||||
|
||||
//# sourceMappingURL=css-var.js.map
|
||||
1
node_modules/@asamuzakjp/css-color/dist/esm/js/css-var.js.map
generated
vendored
Normal file
1
node_modules/@asamuzakjp/css-color/dist/esm/js/css-var.js.map
generated
vendored
Normal file
File diff suppressed because one or more lines are too long
65
node_modules/@asamuzakjp/css-color/dist/esm/js/relative-color.d.ts
generated
vendored
Normal file
65
node_modules/@asamuzakjp/css-color/dist/esm/js/relative-color.d.ts
generated
vendored
Normal file
@@ -0,0 +1,65 @@
|
||||
import { CSSToken } from '@csstools/css-tokenizer';
|
||||
import { NullObject } from './cache.js';
|
||||
import { ColorChannels, Options, StringColorChannels } from './typedef.js';
|
||||
/**
|
||||
* @type NumberOrStringColorChannels - color channel
|
||||
*/
|
||||
type NumberOrStringColorChannels = ColorChannels & StringColorChannels;
|
||||
/**
|
||||
* resolve relative color channels
|
||||
* @param value
|
||||
* - CSS color value
|
||||
* - system colors are not supported
|
||||
* @param [opt] - options
|
||||
* @param [opt.currentColor]
|
||||
* - color to use for `currentcolor` keyword
|
||||
* - if omitted, it will be treated as a missing color
|
||||
* i.e. `rgb(none none none / none)`
|
||||
* @param [opt.customProperty]
|
||||
* - custom properties
|
||||
* - pair of `--` prefixed property name and value,
|
||||
* e.g. `customProperty: { '--some-color': '#0000ff' }`
|
||||
* - and/or `callback` function to get the value of the custom property,
|
||||
* e.g. `customProperty: { callback: someDeclaration.getPropertyValue }`
|
||||
* @param [opt.dimension]
|
||||
* - dimension, convert relative length to pixels
|
||||
* - pair of unit and it's value as a number in pixels,
|
||||
* e.g. `dimension: { em: 12, rem: 16, vw: 10.26 }`
|
||||
* - and/or `callback` function to get the value as a number in pixels,
|
||||
* e.g. `dimension: { callback: convertUnitToPixel }`
|
||||
* @param [opt.format]
|
||||
* - output format, one of below
|
||||
* - `computedValue` (default), [computed value][139] of the color
|
||||
* - `specifiedValue`, [specified value][140] of the color
|
||||
* - `hex`, hex color notation, i.e. `rrggbb`
|
||||
* - `hexAlpha`, hex color notation with alpha channel, i.e. `#rrggbbaa`
|
||||
* @returns
|
||||
* - one of rgba?(), #rrggbb(aa)?, color-name, '(empty-string)',
|
||||
* color(color-space r g b / alpha), color(color-space x y z / alpha),
|
||||
* lab(l a b / alpha), lch(l c h / alpha), oklab(l a b / alpha),
|
||||
* oklch(l c h / alpha), null
|
||||
* - in `computedValue`, values are numbers, however `rgb()` values are
|
||||
* integers
|
||||
* - in `specifiedValue`, returns `empty string` for unknown and/or invalid
|
||||
* color
|
||||
* - in `hex`, returns `null` for `transparent`, and also returns `null` if
|
||||
* any of `r`, `g`, `b`, `alpha` is not a number
|
||||
* - in `hexAlpha`, returns `#00000000` for `transparent`,
|
||||
* however returns `null` if any of `r`, `g`, `b`, `alpha` is not a number
|
||||
*/
|
||||
export declare function resolveColorChannels(tokens: CSSToken[], opt?: Options): NumberOrStringColorChannels | NullObject;
|
||||
/**
|
||||
* extract origin color
|
||||
* @param value - CSS color value
|
||||
* @param [opt] - options
|
||||
* @returns origin color value
|
||||
*/
|
||||
export declare function extractOriginColor(value: string, opt?: Options): string | NullObject;
|
||||
/**
|
||||
* resolve relative color
|
||||
* @param value - CSS relative color value
|
||||
* @param [opt] - options
|
||||
* @returns resolved value
|
||||
*/
|
||||
export declare function resolveRelativeColor(value: string, opt?: Options): string | NullObject;
|
||||
export {};
|
||||
451
node_modules/@asamuzakjp/css-color/dist/esm/js/relative-color.js
generated
vendored
Normal file
451
node_modules/@asamuzakjp/css-color/dist/esm/js/relative-color.js
generated
vendored
Normal file
@@ -0,0 +1,451 @@
|
||||
import { CacheItem, NullObject, createCacheKey, getCache, setCache } from "./cache.js";
|
||||
import { isString, isStringOrNumber } from "./common.js";
|
||||
import { CS_LAB, CS_LCH, FN_REL, FN_REL_CAPT, FN_VAR, NONE, SYN_COLOR_TYPE, SYN_FN_MATH_START, SYN_FN_VAR, SYN_MIX, VAL_SPEC } from "./constant.js";
|
||||
import { NAMED_COLORS, convertColorToRgb } from "./color.js";
|
||||
import { resolveColor } from "./resolve.js";
|
||||
import { roundToPrecision, splitValue } from "./util.js";
|
||||
import { resolveDimension, serializeCalc } from "./css-calc.js";
|
||||
import { TokenType, tokenize } from "@csstools/css-tokenizer";
|
||||
import { SyntaxFlag, color } from "@csstools/css-color-parser";
|
||||
import { parseComponentValue } from "@csstools/css-parser-algorithms";
|
||||
//#region src/js/relative-color.ts
|
||||
/**
|
||||
* relative-color
|
||||
*/
|
||||
var { CloseParen: PAREN_CLOSE, Comment: COMMENT, Delim: DELIM, Dimension: DIM, EOF, Function: FUNC, Ident: IDENT, Number: NUM, OpenParen: PAREN_OPEN, Percentage: PCT, Whitespace: W_SPACE } = TokenType;
|
||||
var { HasNoneKeywords: KEY_NONE } = SyntaxFlag;
|
||||
var NAMESPACE = "relative-color";
|
||||
var OCT = 8;
|
||||
var DEC = 10;
|
||||
var HEX = 16;
|
||||
var MAX_PCT = 100;
|
||||
var MAX_RGB = 255;
|
||||
var COLOR_CHANNELS = new Map([
|
||||
["color", [
|
||||
"r",
|
||||
"g",
|
||||
"b",
|
||||
"alpha"
|
||||
]],
|
||||
["hsl", [
|
||||
"h",
|
||||
"s",
|
||||
"l",
|
||||
"alpha"
|
||||
]],
|
||||
["hsla", [
|
||||
"h",
|
||||
"s",
|
||||
"l",
|
||||
"alpha"
|
||||
]],
|
||||
["hwb", [
|
||||
"h",
|
||||
"w",
|
||||
"b",
|
||||
"alpha"
|
||||
]],
|
||||
["lab", [
|
||||
"l",
|
||||
"a",
|
||||
"b",
|
||||
"alpha"
|
||||
]],
|
||||
["lch", [
|
||||
"l",
|
||||
"c",
|
||||
"h",
|
||||
"alpha"
|
||||
]],
|
||||
["oklab", [
|
||||
"l",
|
||||
"a",
|
||||
"b",
|
||||
"alpha"
|
||||
]],
|
||||
["oklch", [
|
||||
"l",
|
||||
"c",
|
||||
"h",
|
||||
"alpha"
|
||||
]],
|
||||
["rgb", [
|
||||
"r",
|
||||
"g",
|
||||
"b",
|
||||
"alpha"
|
||||
]],
|
||||
["rgba", [
|
||||
"r",
|
||||
"g",
|
||||
"b",
|
||||
"alpha"
|
||||
]]
|
||||
]);
|
||||
var REG_COLOR_CAPT = new RegExp(`^${FN_REL}(${SYN_COLOR_TYPE}|${SYN_MIX})\\s+`);
|
||||
var REG_CS_HSL = /(?:hsla?|hwb)$/;
|
||||
var REG_CS_CIE = new RegExp(`^(?:${CS_LAB}|${CS_LCH})$`);
|
||||
var REG_FN_CALC_SUM = /^(?:abs|sig?n|cos|tan)\(/;
|
||||
var REG_FN_MATH_START = new RegExp(SYN_FN_MATH_START);
|
||||
var REG_FN_REL = new RegExp(FN_REL);
|
||||
var REG_FN_REL_CAPT = new RegExp(`^${FN_REL_CAPT}`);
|
||||
var REG_FN_REL_START = new RegExp(`^${FN_REL}`);
|
||||
var REG_FN_VAR = new RegExp(SYN_FN_VAR);
|
||||
/**
|
||||
* resolve relative color channels
|
||||
* @param value
|
||||
* - CSS color value
|
||||
* - system colors are not supported
|
||||
* @param [opt] - options
|
||||
* @param [opt.currentColor]
|
||||
* - color to use for `currentcolor` keyword
|
||||
* - if omitted, it will be treated as a missing color
|
||||
* i.e. `rgb(none none none / none)`
|
||||
* @param [opt.customProperty]
|
||||
* - custom properties
|
||||
* - pair of `--` prefixed property name and value,
|
||||
* e.g. `customProperty: { '--some-color': '#0000ff' }`
|
||||
* - and/or `callback` function to get the value of the custom property,
|
||||
* e.g. `customProperty: { callback: someDeclaration.getPropertyValue }`
|
||||
* @param [opt.dimension]
|
||||
* - dimension, convert relative length to pixels
|
||||
* - pair of unit and it's value as a number in pixels,
|
||||
* e.g. `dimension: { em: 12, rem: 16, vw: 10.26 }`
|
||||
* - and/or `callback` function to get the value as a number in pixels,
|
||||
* e.g. `dimension: { callback: convertUnitToPixel }`
|
||||
* @param [opt.format]
|
||||
* - output format, one of below
|
||||
* - `computedValue` (default), [computed value][139] of the color
|
||||
* - `specifiedValue`, [specified value][140] of the color
|
||||
* - `hex`, hex color notation, i.e. `rrggbb`
|
||||
* - `hexAlpha`, hex color notation with alpha channel, i.e. `#rrggbbaa`
|
||||
* @returns
|
||||
* - one of rgba?(), #rrggbb(aa)?, color-name, '(empty-string)',
|
||||
* color(color-space r g b / alpha), color(color-space x y z / alpha),
|
||||
* lab(l a b / alpha), lch(l c h / alpha), oklab(l a b / alpha),
|
||||
* oklch(l c h / alpha), null
|
||||
* - in `computedValue`, values are numbers, however `rgb()` values are
|
||||
* integers
|
||||
* - in `specifiedValue`, returns `empty string` for unknown and/or invalid
|
||||
* color
|
||||
* - in `hex`, returns `null` for `transparent`, and also returns `null` if
|
||||
* any of `r`, `g`, `b`, `alpha` is not a number
|
||||
* - in `hexAlpha`, returns `#00000000` for `transparent`,
|
||||
* however returns `null` if any of `r`, `g`, `b`, `alpha` is not a number
|
||||
*/
|
||||
function resolveColorChannels(tokens, opt = {}) {
|
||||
if (!Array.isArray(tokens)) throw new TypeError(`${tokens} is not an array.`);
|
||||
const { colorSpace = "", format = "" } = opt;
|
||||
const colorChannel = COLOR_CHANNELS.get(colorSpace);
|
||||
if (!colorChannel) return new NullObject();
|
||||
const mathFunc = /* @__PURE__ */ new Set();
|
||||
const channels = [
|
||||
[],
|
||||
[],
|
||||
[],
|
||||
[]
|
||||
];
|
||||
let i = 0;
|
||||
let nest = 0;
|
||||
let func = "";
|
||||
let precededPct = false;
|
||||
for (const token of tokens) {
|
||||
if (!Array.isArray(token)) throw new TypeError(`${token} is not an array.`);
|
||||
const [type, value, , , detail] = token;
|
||||
const channel = channels[i];
|
||||
if (Array.isArray(channel)) switch (type) {
|
||||
case DELIM:
|
||||
if (func) {
|
||||
if ((value === "+" || value === "-") && precededPct && !REG_FN_CALC_SUM.test(func)) return new NullObject();
|
||||
precededPct = false;
|
||||
channel.push(value);
|
||||
}
|
||||
break;
|
||||
case DIM: {
|
||||
if (!func || !REG_FN_CALC_SUM.test(func)) return new NullObject();
|
||||
const resolvedValue = resolveDimension(token, opt);
|
||||
if (isString(resolvedValue)) channel.push(resolvedValue);
|
||||
else channel.push(value);
|
||||
break;
|
||||
}
|
||||
case FUNC:
|
||||
channel.push(value);
|
||||
func = value;
|
||||
nest++;
|
||||
if (REG_FN_MATH_START.test(value)) mathFunc.add(nest);
|
||||
break;
|
||||
case IDENT:
|
||||
if (!colorChannel.includes(value)) return new NullObject();
|
||||
channel.push(value);
|
||||
if (!func) i++;
|
||||
break;
|
||||
case NUM:
|
||||
channel.push(Number(detail?.value));
|
||||
if (!func) i++;
|
||||
break;
|
||||
case PAREN_OPEN:
|
||||
channel.push(value);
|
||||
nest++;
|
||||
break;
|
||||
case PAREN_CLOSE:
|
||||
if (func) {
|
||||
if (channel[channel.length - 1] === " ") channel[channel.length - 1] = value;
|
||||
else channel.push(value);
|
||||
if (mathFunc.has(nest)) mathFunc.delete(nest);
|
||||
nest--;
|
||||
if (nest === 0) {
|
||||
func = "";
|
||||
i++;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case PCT:
|
||||
if (!func) return new NullObject();
|
||||
else if (!REG_FN_CALC_SUM.test(func)) {
|
||||
let lastValue;
|
||||
for (let j = channel.length - 1; j >= 0; j--) if (channel[j] !== " ") {
|
||||
lastValue = channel[j];
|
||||
break;
|
||||
}
|
||||
if (lastValue === "+" || lastValue === "-") return new NullObject();
|
||||
else if (lastValue === "*" || lastValue === "/") precededPct = false;
|
||||
else precededPct = true;
|
||||
}
|
||||
channel.push(Number(detail?.value) / MAX_PCT);
|
||||
break;
|
||||
case W_SPACE:
|
||||
if (channel.length && func) {
|
||||
const lastValue = channel[channel.length - 1];
|
||||
if (typeof lastValue === "number") channel.push(value);
|
||||
else if (isString(lastValue) && !lastValue.endsWith("(") && lastValue !== " ") channel.push(value);
|
||||
}
|
||||
break;
|
||||
default: if (type !== COMMENT && type !== EOF && func) channel.push(value);
|
||||
}
|
||||
}
|
||||
const channelValues = [];
|
||||
for (const channel of channels) if (channel.length === 1) {
|
||||
const [resolvedValue] = channel;
|
||||
if (isStringOrNumber(resolvedValue)) channelValues.push(resolvedValue);
|
||||
} else if (channel.length) {
|
||||
const resolvedValue = serializeCalc(channel.join(""), { format });
|
||||
channelValues.push(resolvedValue);
|
||||
}
|
||||
return channelValues;
|
||||
}
|
||||
/**
|
||||
* extract origin color
|
||||
* @param value - CSS color value
|
||||
* @param [opt] - options
|
||||
* @returns origin color value
|
||||
*/
|
||||
function extractOriginColor(value, opt = {}) {
|
||||
const { colorScheme = "normal", currentColor = "", format = "" } = opt;
|
||||
if (isString(value)) {
|
||||
value = value.toLowerCase().trim();
|
||||
if (!value) return new NullObject();
|
||||
if (!REG_FN_REL_START.test(value)) return value;
|
||||
} else return new NullObject();
|
||||
const cacheKey = createCacheKey({
|
||||
namespace: NAMESPACE,
|
||||
name: "extractOriginColor",
|
||||
value
|
||||
}, opt);
|
||||
const cachedResult = getCache(cacheKey);
|
||||
if (cachedResult instanceof CacheItem) {
|
||||
if (cachedResult.isNull) return cachedResult;
|
||||
return cachedResult.item;
|
||||
}
|
||||
if (/currentcolor/.test(value)) if (currentColor) value = value.replace(/currentcolor/g, currentColor);
|
||||
else {
|
||||
setCache(cacheKey, null);
|
||||
return new NullObject();
|
||||
}
|
||||
let colorSpace = "";
|
||||
if (REG_FN_REL_CAPT.test(value)) [, colorSpace] = value.match(REG_FN_REL_CAPT);
|
||||
opt.colorSpace = colorSpace;
|
||||
if (value.includes("light-dark(")) {
|
||||
const [, originColor = ""] = splitValue(value.replace(new RegExp(`^${colorSpace}\\(`), "").replace(/\)$/, ""));
|
||||
const specifiedOriginColor = resolveColor(originColor, {
|
||||
colorScheme,
|
||||
format: VAL_SPEC
|
||||
});
|
||||
if (specifiedOriginColor === "") {
|
||||
setCache(cacheKey, null);
|
||||
return new NullObject();
|
||||
}
|
||||
if (format === "specifiedValue") value = value.replace(originColor, specifiedOriginColor);
|
||||
else {
|
||||
const resolvedOriginColor = resolveColor(specifiedOriginColor, opt);
|
||||
if (isString(resolvedOriginColor)) value = value.replace(originColor, resolvedOriginColor);
|
||||
}
|
||||
}
|
||||
if (REG_COLOR_CAPT.test(value)) {
|
||||
const [, originColor] = value.match(REG_COLOR_CAPT);
|
||||
const [, restValue] = value.split(originColor);
|
||||
if (/^[a-z]+$/.test(originColor)) {
|
||||
if (!/^transparent$/.test(originColor) && !Object.hasOwn(NAMED_COLORS, originColor)) {
|
||||
setCache(cacheKey, null);
|
||||
return new NullObject();
|
||||
}
|
||||
} else if (format === "specifiedValue") {
|
||||
const resolvedOriginColor = resolveColor(originColor, opt);
|
||||
if (isString(resolvedOriginColor)) value = value.replace(originColor, resolvedOriginColor);
|
||||
}
|
||||
if (format === "specifiedValue") {
|
||||
const channelValues = resolveColorChannels(tokenize({ css: restValue }), opt);
|
||||
if (channelValues instanceof NullObject) {
|
||||
setCache(cacheKey, null);
|
||||
return channelValues;
|
||||
}
|
||||
const [v1, v2, v3, v4] = channelValues;
|
||||
let channelValue = "";
|
||||
if (isStringOrNumber(v4)) channelValue = ` ${v1} ${v2} ${v3} / ${v4})`;
|
||||
else channelValue = ` ${channelValues.join(" ")})`;
|
||||
if (restValue !== channelValue) value = value.replace(restValue, channelValue);
|
||||
}
|
||||
} else {
|
||||
const [, restValue] = value.split(REG_FN_REL_START);
|
||||
const tokens = tokenize({ css: restValue });
|
||||
const originColor = [];
|
||||
let nest = 0;
|
||||
let tokenIndex = 0;
|
||||
for (const [type, tokenValue] of tokens) {
|
||||
tokenIndex++;
|
||||
switch (type) {
|
||||
case FUNC:
|
||||
case PAREN_OPEN:
|
||||
originColor.push(tokenValue);
|
||||
nest++;
|
||||
break;
|
||||
case PAREN_CLOSE: {
|
||||
const lastValue = originColor[originColor.length - 1];
|
||||
if (lastValue === " ") originColor[originColor.length - 1] = tokenValue;
|
||||
else if (isString(lastValue)) originColor.push(tokenValue);
|
||||
nest--;
|
||||
break;
|
||||
}
|
||||
case W_SPACE: {
|
||||
const lastValue = originColor[originColor.length - 1];
|
||||
if (isString(lastValue) && !lastValue.endsWith("(") && lastValue !== " ") originColor.push(tokenValue);
|
||||
break;
|
||||
}
|
||||
default: if (type !== COMMENT && type !== EOF) originColor.push(tokenValue);
|
||||
}
|
||||
if (nest === 0) break;
|
||||
}
|
||||
const resolvedOriginColor = resolveRelativeColor(originColor.join("").trim(), opt);
|
||||
if (resolvedOriginColor instanceof NullObject) {
|
||||
setCache(cacheKey, null);
|
||||
return resolvedOriginColor;
|
||||
}
|
||||
const channelValues = resolveColorChannels(tokens.slice(tokenIndex), opt);
|
||||
if (channelValues instanceof NullObject) {
|
||||
setCache(cacheKey, null);
|
||||
return channelValues;
|
||||
}
|
||||
const [v1, v2, v3, v4] = channelValues;
|
||||
let channelValue = "";
|
||||
if (isStringOrNumber(v4)) channelValue = ` ${v1} ${v2} ${v3} / ${v4})`;
|
||||
else channelValue = ` ${channelValues.join(" ")})`;
|
||||
value = value.replace(restValue, `${resolvedOriginColor}${channelValue}`);
|
||||
}
|
||||
setCache(cacheKey, value);
|
||||
return value;
|
||||
}
|
||||
/**
|
||||
* resolve relative color
|
||||
* @param value - CSS relative color value
|
||||
* @param [opt] - options
|
||||
* @returns resolved value
|
||||
*/
|
||||
function resolveRelativeColor(value, opt = {}) {
|
||||
const { format = "" } = opt;
|
||||
if (isString(value)) {
|
||||
if (REG_FN_VAR.test(value)) {
|
||||
if (format !== "specifiedValue") throw new SyntaxError(`Unexpected token ${FN_VAR} found.`);
|
||||
return value;
|
||||
} else if (!REG_FN_REL.test(value)) return value;
|
||||
value = value.toLowerCase().trim();
|
||||
} else throw new TypeError(`${value} is not a string.`);
|
||||
const cacheKey = createCacheKey({
|
||||
namespace: NAMESPACE,
|
||||
name: "resolveRelativeColor",
|
||||
value
|
||||
}, opt);
|
||||
const cachedResult = getCache(cacheKey);
|
||||
if (cachedResult instanceof CacheItem) {
|
||||
if (cachedResult.isNull) return cachedResult;
|
||||
return cachedResult.item;
|
||||
}
|
||||
const originColor = extractOriginColor(value, opt);
|
||||
if (originColor instanceof NullObject) {
|
||||
setCache(cacheKey, null);
|
||||
return originColor;
|
||||
}
|
||||
value = originColor;
|
||||
if (format === "specifiedValue") {
|
||||
if (value.startsWith("rgba(")) value = value.replace("rgba(", "rgb(");
|
||||
else if (value.startsWith("hsla(")) value = value.replace("hsla(", "hsl(");
|
||||
return value;
|
||||
}
|
||||
const parsedComponents = color(parseComponentValue(tokenize({ css: value })));
|
||||
if (!parsedComponents) {
|
||||
setCache(cacheKey, null);
|
||||
return new NullObject();
|
||||
}
|
||||
const { alpha: alphaComponent, channels: channelsComponent, colorNotation, syntaxFlags } = parsedComponents;
|
||||
let alpha;
|
||||
if (Number.isNaN(Number(alphaComponent))) if (syntaxFlags instanceof Set && syntaxFlags.has(KEY_NONE)) alpha = NONE;
|
||||
else alpha = 0;
|
||||
else alpha = roundToPrecision(Number(alphaComponent), OCT);
|
||||
let v1;
|
||||
let v2;
|
||||
let v3;
|
||||
[v1, v2, v3] = channelsComponent;
|
||||
let resolvedValue;
|
||||
if (REG_CS_CIE.test(colorNotation)) {
|
||||
const hasNone = syntaxFlags instanceof Set && syntaxFlags.has(KEY_NONE);
|
||||
if (Number.isNaN(v1)) if (hasNone) v1 = NONE;
|
||||
else v1 = 0;
|
||||
else v1 = roundToPrecision(v1, HEX);
|
||||
if (Number.isNaN(v2)) if (hasNone) v2 = NONE;
|
||||
else v2 = 0;
|
||||
else v2 = roundToPrecision(v2, HEX);
|
||||
if (Number.isNaN(v3)) if (hasNone) v3 = NONE;
|
||||
else v3 = 0;
|
||||
else v3 = roundToPrecision(v3, HEX);
|
||||
if (alpha === 1) resolvedValue = `${colorNotation}(${v1} ${v2} ${v3})`;
|
||||
else resolvedValue = `${colorNotation}(${v1} ${v2} ${v3} / ${alpha})`;
|
||||
} else if (REG_CS_HSL.test(colorNotation)) {
|
||||
if (Number.isNaN(v1)) v1 = 0;
|
||||
if (Number.isNaN(v2)) v2 = 0;
|
||||
if (Number.isNaN(v3)) v3 = 0;
|
||||
let [r, g, b] = convertColorToRgb(`${colorNotation}(${v1} ${v2} ${v3} / ${alpha})`);
|
||||
r = roundToPrecision(r / MAX_RGB, DEC);
|
||||
g = roundToPrecision(g / MAX_RGB, DEC);
|
||||
b = roundToPrecision(b / MAX_RGB, DEC);
|
||||
if (alpha === 1) resolvedValue = `color(srgb ${r} ${g} ${b})`;
|
||||
else resolvedValue = `color(srgb ${r} ${g} ${b} / ${alpha})`;
|
||||
} else {
|
||||
const cs = colorNotation === "rgb" ? "srgb" : colorNotation;
|
||||
const hasNone = syntaxFlags instanceof Set && syntaxFlags.has(KEY_NONE);
|
||||
if (Number.isNaN(v1)) if (hasNone) v1 = NONE;
|
||||
else v1 = 0;
|
||||
else v1 = roundToPrecision(v1, DEC);
|
||||
if (Number.isNaN(v2)) if (hasNone) v2 = NONE;
|
||||
else v2 = 0;
|
||||
else v2 = roundToPrecision(v2, DEC);
|
||||
if (Number.isNaN(v3)) if (hasNone) v3 = NONE;
|
||||
else v3 = 0;
|
||||
else v3 = roundToPrecision(v3, DEC);
|
||||
if (alpha === 1) resolvedValue = `color(${cs} ${v1} ${v2} ${v3})`;
|
||||
else resolvedValue = `color(${cs} ${v1} ${v2} ${v3} / ${alpha})`;
|
||||
}
|
||||
setCache(cacheKey, resolvedValue);
|
||||
return resolvedValue;
|
||||
}
|
||||
//#endregion
|
||||
export { resolveRelativeColor };
|
||||
|
||||
//# sourceMappingURL=relative-color.js.map
|
||||
1
node_modules/@asamuzakjp/css-color/dist/esm/js/relative-color.js.map
generated
vendored
Normal file
1
node_modules/@asamuzakjp/css-color/dist/esm/js/relative-color.js.map
generated
vendored
Normal file
File diff suppressed because one or more lines are too long
15
node_modules/@asamuzakjp/css-color/dist/esm/js/resolve.d.ts
generated
vendored
Normal file
15
node_modules/@asamuzakjp/css-color/dist/esm/js/resolve.d.ts
generated
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
import { NullObject } from './cache.js';
|
||||
import { Options } from './typedef.js';
|
||||
/**
|
||||
* resolve color
|
||||
* @param value - CSS color value
|
||||
* @param [opt] - options
|
||||
* @returns resolved color
|
||||
*/
|
||||
export declare const resolveColor: (value: string, opt?: Options) => string | NullObject;
|
||||
/**
|
||||
* resolve CSS color
|
||||
* @param value - CSS color value. system colors are not supported
|
||||
* @param [opt] - options
|
||||
*/
|
||||
export declare const resolve: (value: string, opt?: Options) => string | null;
|
||||
210
node_modules/@asamuzakjp/css-color/dist/esm/js/resolve.js
generated
vendored
Normal file
210
node_modules/@asamuzakjp/css-color/dist/esm/js/resolve.js
generated
vendored
Normal file
@@ -0,0 +1,210 @@
|
||||
import { CacheItem, NullObject, createCacheKey, getCache, setCache } from "./cache.js";
|
||||
import { isString } from "./common.js";
|
||||
import { SYN_FN_CALC, SYN_FN_LIGHT_DARK, SYN_FN_REL, SYN_FN_VAR, VAL_COMP, VAL_SPEC } from "./constant.js";
|
||||
import { convertRgbToHex, resolveColorFunc, resolveColorMix, resolveColorValue } from "./color.js";
|
||||
import { resolveRelativeColor } from "./relative-color.js";
|
||||
import { splitValue } from "./util.js";
|
||||
import { resolveVar } from "./css-var.js";
|
||||
import { cssCalc } from "./css-calc.js";
|
||||
//#region src/js/resolve.ts
|
||||
/**
|
||||
* resolve
|
||||
*/
|
||||
var NAMESPACE = "resolve";
|
||||
var RGB_TRANSPARENT = "rgba(0, 0, 0, 0)";
|
||||
var REG_FN_CALC = new RegExp(SYN_FN_CALC);
|
||||
var REG_FN_LIGHT_DARK = new RegExp(SYN_FN_LIGHT_DARK);
|
||||
var REG_FN_REL = new RegExp(SYN_FN_REL);
|
||||
var REG_FN_VAR = new RegExp(SYN_FN_VAR);
|
||||
/**
|
||||
* resolve color
|
||||
* @param value - CSS color value
|
||||
* @param [opt] - options
|
||||
* @returns resolved color
|
||||
*/
|
||||
var resolveColor = (value, opt = {}) => {
|
||||
if (!isString(value)) throw new TypeError(`${value} is not a string.`);
|
||||
value = value.trim();
|
||||
const { colorScheme = "normal", currentColor = "", format = VAL_COMP, nullable = false } = opt;
|
||||
const cacheKey = createCacheKey({
|
||||
namespace: NAMESPACE,
|
||||
name: "resolve",
|
||||
value
|
||||
}, opt);
|
||||
const cachedResult = getCache(cacheKey);
|
||||
if (cachedResult instanceof CacheItem) {
|
||||
if (cachedResult.isNull) return cachedResult;
|
||||
return cachedResult.item;
|
||||
}
|
||||
if (REG_FN_VAR.test(value)) {
|
||||
if (format === "specifiedValue") {
|
||||
setCache(cacheKey, value);
|
||||
return value;
|
||||
}
|
||||
const resolvedVar = resolveVar(value, opt);
|
||||
if (resolvedVar instanceof NullObject) {
|
||||
const res = format === "hex" || format === "hexAlpha" || nullable ? resolvedVar : RGB_TRANSPARENT;
|
||||
setCache(cacheKey, res);
|
||||
return res;
|
||||
}
|
||||
value = resolvedVar;
|
||||
}
|
||||
if (opt.format !== format) opt.format = format;
|
||||
value = value.toLowerCase();
|
||||
if (REG_FN_LIGHT_DARK.test(value) && value.endsWith(")")) {
|
||||
const [light = "", dark = ""] = splitValue(value.replace(REG_FN_LIGHT_DARK, "").replace(/\)$/, ""), { delimiter: "," });
|
||||
if (light && dark) {
|
||||
if (format === "specifiedValue") {
|
||||
const lightColor = resolveColor(light, opt);
|
||||
const darkColor = resolveColor(dark, opt);
|
||||
const res = lightColor && darkColor ? `light-dark(${lightColor}, ${darkColor})` : "";
|
||||
setCache(cacheKey, res);
|
||||
return res;
|
||||
}
|
||||
const resolved = resolveColor(colorScheme === "dark" ? dark : light, opt);
|
||||
const res = resolved instanceof NullObject && !nullable ? RGB_TRANSPARENT : resolved;
|
||||
setCache(cacheKey, res);
|
||||
return res;
|
||||
}
|
||||
const invalidRes = format === "specifiedValue" ? "" : format === "hex" || format === "hexAlpha" ? new NullObject() : RGB_TRANSPARENT;
|
||||
setCache(cacheKey, invalidRes);
|
||||
return invalidRes;
|
||||
}
|
||||
if (REG_FN_REL.test(value)) {
|
||||
const resolvedRel = resolveRelativeColor(value, opt);
|
||||
if (format === "computedValue") {
|
||||
const res = resolvedRel instanceof NullObject && !nullable ? RGB_TRANSPARENT : resolvedRel;
|
||||
setCache(cacheKey, res);
|
||||
return res;
|
||||
}
|
||||
if (format === "specifiedValue") {
|
||||
const res = resolvedRel instanceof NullObject ? "" : resolvedRel;
|
||||
setCache(cacheKey, res);
|
||||
return res;
|
||||
}
|
||||
value = resolvedRel instanceof NullObject ? "" : resolvedRel;
|
||||
}
|
||||
if (REG_FN_CALC.test(value)) value = cssCalc(value, opt);
|
||||
let cs = "";
|
||||
let r = NaN;
|
||||
let g = NaN;
|
||||
let b = NaN;
|
||||
let alpha = NaN;
|
||||
if (value === "transparent") {
|
||||
let res;
|
||||
switch (format) {
|
||||
case VAL_SPEC:
|
||||
res = value;
|
||||
break;
|
||||
case "hex":
|
||||
res = new NullObject();
|
||||
break;
|
||||
case "hexAlpha":
|
||||
res = "#00000000";
|
||||
break;
|
||||
default: res = RGB_TRANSPARENT;
|
||||
}
|
||||
setCache(cacheKey, res);
|
||||
return res;
|
||||
}
|
||||
if (value === "currentcolor") {
|
||||
if (format === "specifiedValue") {
|
||||
setCache(cacheKey, value);
|
||||
return value;
|
||||
}
|
||||
if (currentColor) {
|
||||
let resolvedCurrent;
|
||||
if (currentColor.startsWith("color-mix(")) resolvedCurrent = resolveColorMix(currentColor, opt);
|
||||
else if (currentColor.startsWith("color(")) resolvedCurrent = resolveColorFunc(currentColor, opt);
|
||||
else resolvedCurrent = resolveColorValue(currentColor, opt);
|
||||
if (resolvedCurrent instanceof NullObject) {
|
||||
setCache(cacheKey, resolvedCurrent);
|
||||
return resolvedCurrent;
|
||||
}
|
||||
[cs, r, g, b, alpha] = resolvedCurrent;
|
||||
} else {
|
||||
const res = format === "computedValue" ? RGB_TRANSPARENT : value;
|
||||
if (format === "computedValue") {
|
||||
setCache(cacheKey, res);
|
||||
return res;
|
||||
}
|
||||
}
|
||||
} else if (format === "specifiedValue") {
|
||||
let res = "";
|
||||
if (value.startsWith("color-mix(")) res = resolveColorMix(value, opt);
|
||||
else if (value.startsWith("color(")) {
|
||||
const [scs, rr, gg, bb, aa] = resolveColorFunc(value, opt);
|
||||
res = aa === 1 ? `color(${scs} ${rr} ${gg} ${bb})` : `color(${scs} ${rr} ${gg} ${bb} / ${aa})`;
|
||||
} else {
|
||||
const rgb = resolveColorValue(value, opt);
|
||||
if (isString(rgb)) res = rgb;
|
||||
else {
|
||||
const [scs, rr, gg, bb, aa] = rgb;
|
||||
if (scs === "rgb") res = aa === 1 ? `${scs}(${rr}, ${gg}, ${bb})` : `${scs}a(${rr}, ${gg}, ${bb}, ${aa})`;
|
||||
else res = aa === 1 ? `${scs}(${rr} ${gg} ${bb})` : `${scs}(${rr} ${gg} ${bb} / ${aa})`;
|
||||
}
|
||||
}
|
||||
setCache(cacheKey, res);
|
||||
return res;
|
||||
} else if (value.startsWith("color-mix(")) {
|
||||
if (currentColor) value = value.replace(/currentcolor/g, currentColor);
|
||||
value = value.replace(/transparent/g, RGB_TRANSPARENT);
|
||||
const resolvedMix = resolveColorMix(value, opt);
|
||||
if (resolvedMix instanceof NullObject) {
|
||||
setCache(cacheKey, resolvedMix);
|
||||
return resolvedMix;
|
||||
}
|
||||
[cs, r, g, b, alpha] = resolvedMix;
|
||||
} else if (value.startsWith("color(")) {
|
||||
const resolvedFunc = resolveColorFunc(value, opt);
|
||||
if (resolvedFunc instanceof NullObject) {
|
||||
setCache(cacheKey, resolvedFunc);
|
||||
return resolvedFunc;
|
||||
}
|
||||
[cs, r, g, b, alpha] = resolvedFunc;
|
||||
} else if (value) {
|
||||
const resolvedVal = resolveColorValue(value, opt);
|
||||
if (resolvedVal instanceof NullObject) {
|
||||
setCache(cacheKey, resolvedVal);
|
||||
return resolvedVal;
|
||||
}
|
||||
[cs, r, g, b, alpha] = resolvedVal;
|
||||
}
|
||||
let finalRes = "";
|
||||
switch (format) {
|
||||
case "hex":
|
||||
case "hexAlpha":
|
||||
if (Number.isNaN(r) || Number.isNaN(g) || Number.isNaN(b) || Number.isNaN(alpha) || format === "hex" && alpha === 0) finalRes = new NullObject();
|
||||
else finalRes = convertRgbToHex([
|
||||
r,
|
||||
g,
|
||||
b,
|
||||
format === "hex" ? 1 : alpha
|
||||
]);
|
||||
break;
|
||||
default: if (cs === "rgb") finalRes = alpha === 1 ? `${cs}(${r}, ${g}, ${b})` : `${cs}a(${r}, ${g}, ${b}, ${alpha})`;
|
||||
else if ([
|
||||
"lab",
|
||||
"lch",
|
||||
"oklab",
|
||||
"oklch"
|
||||
].includes(cs)) finalRes = alpha === 1 ? `${cs}(${r} ${g} ${b})` : `${cs}(${r} ${g} ${b} / ${alpha})`;
|
||||
else finalRes = alpha === 1 ? `color(${cs} ${r} ${g} ${b})` : `color(${cs} ${r} ${g} ${b} / ${alpha})`;
|
||||
}
|
||||
setCache(cacheKey, finalRes);
|
||||
return finalRes;
|
||||
};
|
||||
/**
|
||||
* resolve CSS color
|
||||
* @param value - CSS color value. system colors are not supported
|
||||
* @param [opt] - options
|
||||
*/
|
||||
var resolve = (value, opt = {}) => {
|
||||
opt.nullable = false;
|
||||
const resolvedValue = resolveColor(value, opt);
|
||||
return resolvedValue instanceof NullObject ? null : resolvedValue;
|
||||
};
|
||||
//#endregion
|
||||
export { resolve, resolveColor };
|
||||
|
||||
//# sourceMappingURL=resolve.js.map
|
||||
1
node_modules/@asamuzakjp/css-color/dist/esm/js/resolve.js.map
generated
vendored
Normal file
1
node_modules/@asamuzakjp/css-color/dist/esm/js/resolve.js.map
generated
vendored
Normal file
File diff suppressed because one or more lines are too long
80
node_modules/@asamuzakjp/css-color/dist/esm/js/typedef.d.ts
generated
vendored
Normal file
80
node_modules/@asamuzakjp/css-color/dist/esm/js/typedef.d.ts
generated
vendored
Normal file
@@ -0,0 +1,80 @@
|
||||
/**
|
||||
* typedef
|
||||
*/
|
||||
/**
|
||||
* @typedef Options - options
|
||||
* @property [alpha] - enable alpha
|
||||
* @property [colorSpace] - color space
|
||||
* @property [currentColor] - color for currentcolor
|
||||
* @property [customProperty] - custom properties
|
||||
* @property [d50] - white point in d50
|
||||
* @property [dimension] - dimension
|
||||
* @property [format] - output format
|
||||
* @property [key] - key
|
||||
*/
|
||||
export interface Options {
|
||||
alpha?: boolean;
|
||||
colorScheme?: string;
|
||||
colorSpace?: string;
|
||||
currentColor?: string;
|
||||
customProperty?: Record<string, string | ((K: string) => string)>;
|
||||
d50?: boolean;
|
||||
delimiter?: string | string[];
|
||||
dimension?: Record<string, number | ((K: string) => number)>;
|
||||
format?: string;
|
||||
nullable?: boolean;
|
||||
preserveComment?: boolean;
|
||||
}
|
||||
/**
|
||||
* @type ColorChannels - color channels
|
||||
*/
|
||||
export type ColorChannels = [x: number, y: number, z: number, alpha: number];
|
||||
/**
|
||||
* @type StringColorChannels - color channels
|
||||
*/
|
||||
export type StringColorChannels = [
|
||||
x: string,
|
||||
y: string,
|
||||
z: string,
|
||||
alpha: string | undefined
|
||||
];
|
||||
/**
|
||||
* @type StringColorSpacedChannels - specified value
|
||||
*/
|
||||
export type StringColorSpacedChannels = [
|
||||
cs: string,
|
||||
x: string,
|
||||
y: string,
|
||||
z: string,
|
||||
alpha: string | undefined
|
||||
];
|
||||
/**
|
||||
* @type ComputedColorChannels - computed value
|
||||
*/
|
||||
export type ComputedColorChannels = [
|
||||
cs: string,
|
||||
x: number,
|
||||
y: number,
|
||||
z: number,
|
||||
alpha: number
|
||||
];
|
||||
/**
|
||||
* @type SpecifiedColorChannels - specified value
|
||||
*/
|
||||
export type SpecifiedColorChannels = [
|
||||
cs: string,
|
||||
x: number | string,
|
||||
y: number | string,
|
||||
z: number | string,
|
||||
alpha: number | string
|
||||
];
|
||||
/**
|
||||
* @type MatchedRegExp - matched regexp array
|
||||
*/
|
||||
export type MatchedRegExp = [
|
||||
match: string,
|
||||
gr1: string,
|
||||
gr2: string,
|
||||
gr3: string,
|
||||
gr4: string
|
||||
];
|
||||
59
node_modules/@asamuzakjp/css-color/dist/esm/js/util.d.ts
generated
vendored
Normal file
59
node_modules/@asamuzakjp/css-color/dist/esm/js/util.d.ts
generated
vendored
Normal file
@@ -0,0 +1,59 @@
|
||||
import { Options } from './typedef.js';
|
||||
/**
|
||||
* split value
|
||||
* NOTE: comments are stripped, it can be preserved if, in the options param,
|
||||
* `delimiter` is either ',' or '/' and with `preserveComment` set to `true`
|
||||
* @param value - CSS value
|
||||
* @param [opt] - options
|
||||
* @returns array of values
|
||||
*/
|
||||
export declare const splitValue: (value: string, opt?: Options) => string[];
|
||||
/**
|
||||
* extract dashed-ident tokens
|
||||
* @param value - CSS value
|
||||
* @returns array of dashed-ident tokens
|
||||
*/
|
||||
export declare const extractDashedIdent: (value: string) => string[];
|
||||
/**
|
||||
* is color
|
||||
* @param value - CSS value
|
||||
* @param [opt] - options
|
||||
* @returns result
|
||||
*/
|
||||
export declare const isColor: (value: unknown, opt?: Options) => boolean;
|
||||
/**
|
||||
* round to specified precision
|
||||
* @param value - numeric value
|
||||
* @param bit - minimum bits
|
||||
* @returns rounded value
|
||||
*/
|
||||
export declare const roundToPrecision: (value: number, bit?: number) => number;
|
||||
/**
|
||||
* interpolate hue
|
||||
* @param hueA - hue value
|
||||
* @param hueB - hue value
|
||||
* @param arc - shorter | longer | increasing | decreasing
|
||||
* @returns result - [hueA, hueB]
|
||||
*/
|
||||
export declare const interpolateHue: (hueA: number, hueB: number, arc?: string) => [number, number];
|
||||
/**
|
||||
* resolve length in pixels
|
||||
* @param value - value
|
||||
* @param unit - unit
|
||||
* @param [opt] - options
|
||||
* @returns pixelated value
|
||||
*/
|
||||
export declare const resolveLengthInPixels: (value: number | string, unit: string | undefined, opt?: Options) => number;
|
||||
/**
|
||||
* is absolute size or length
|
||||
* @param value - value
|
||||
* @param unit - unit
|
||||
* @returns result
|
||||
*/
|
||||
export declare const isAbsoluteSizeOrLength: (value: number | string, unit: string | undefined) => boolean;
|
||||
/**
|
||||
* is absolute font size
|
||||
* @param css - css
|
||||
* @returns result
|
||||
*/
|
||||
export declare const isAbsoluteFontSize: (css: unknown) => boolean;
|
||||
274
node_modules/@asamuzakjp/css-color/dist/esm/js/util.js
generated
vendored
Normal file
274
node_modules/@asamuzakjp/css-color/dist/esm/js/util.js
generated
vendored
Normal file
@@ -0,0 +1,274 @@
|
||||
import { CacheItem, createCacheKey, getCache, setCache } from "./cache.js";
|
||||
import { isString } from "./common.js";
|
||||
import { SYN_COLOR_TYPE, SYN_MIX, VAL_SPEC } from "./constant.js";
|
||||
import { NAMED_COLORS } from "./color.js";
|
||||
import { resolveColor } from "./resolve.js";
|
||||
import { TokenType, tokenize } from "@csstools/css-tokenizer";
|
||||
//#region src/js/util.ts
|
||||
/**
|
||||
* util
|
||||
*/
|
||||
var { CloseParen: PAREN_CLOSE, Comma: COMMA, Comment: COMMENT, Delim: DELIM, EOF, Function: FUNC, OpenParen: PAREN_OPEN, Whitespace: W_SPACE } = TokenType;
|
||||
var NAMESPACE = "util";
|
||||
var DEC = 10;
|
||||
var HEX = 16;
|
||||
var DEG = 360;
|
||||
var DEG_HALF = 180;
|
||||
var REG_COLOR = new RegExp(`^(?:${SYN_COLOR_TYPE})$`);
|
||||
var REG_DIMENSION = /^([+-]?(?:\d+(?:\.\d+)?|\.\d+)(?:e[+-]?\d+)?)([a-z]*)$/i;
|
||||
var REG_FN_COLOR = /^(?:(?:ok)?l(?:ab|ch)|color(?:-mix)?|hsla?|hwb|rgba?|var)\(/;
|
||||
var REG_MIX = new RegExp(SYN_MIX);
|
||||
var REG_DASHED_IDENT = /--[\w-]+/g;
|
||||
var REG_COMMA = /^,$/;
|
||||
var REG_SLASH = /^\/$/;
|
||||
var REG_WHITESPACE = /^\s+$/;
|
||||
/**
|
||||
* split value
|
||||
* NOTE: comments are stripped, it can be preserved if, in the options param,
|
||||
* `delimiter` is either ',' or '/' and with `preserveComment` set to `true`
|
||||
* @param value - CSS value
|
||||
* @param [opt] - options
|
||||
* @returns array of values
|
||||
*/
|
||||
var splitValue = (value, opt = {}) => {
|
||||
if (!isString(value)) throw new TypeError(`${value} is not a string.`);
|
||||
const strValue = value.trim();
|
||||
const { delimiter = " ", preserveComment = false } = opt;
|
||||
const cacheKey = createCacheKey({
|
||||
namespace: NAMESPACE,
|
||||
name: "splitValue",
|
||||
value: strValue
|
||||
}, {
|
||||
delimiter,
|
||||
preserveComment
|
||||
});
|
||||
const cachedResult = getCache(cacheKey);
|
||||
if (cachedResult instanceof CacheItem) return cachedResult.item;
|
||||
let regDelimiter;
|
||||
switch (delimiter) {
|
||||
case ",":
|
||||
regDelimiter = REG_COMMA;
|
||||
break;
|
||||
case "/":
|
||||
regDelimiter = REG_SLASH;
|
||||
break;
|
||||
default: regDelimiter = REG_WHITESPACE;
|
||||
}
|
||||
const tokens = tokenize({ css: strValue });
|
||||
let nest = 0;
|
||||
let currentStr = "";
|
||||
const res = [];
|
||||
for (const [type, val] of tokens) switch (type) {
|
||||
case COMMA:
|
||||
case DELIM:
|
||||
if (nest === 0 && regDelimiter.test(val)) {
|
||||
res.push(currentStr.trim());
|
||||
currentStr = "";
|
||||
} else currentStr += val;
|
||||
break;
|
||||
case COMMENT:
|
||||
if (preserveComment && (delimiter === "," || delimiter === "/")) currentStr += val;
|
||||
break;
|
||||
case FUNC:
|
||||
case PAREN_OPEN:
|
||||
currentStr += val;
|
||||
nest++;
|
||||
break;
|
||||
case PAREN_CLOSE:
|
||||
currentStr += val;
|
||||
nest--;
|
||||
break;
|
||||
case W_SPACE:
|
||||
if (regDelimiter.test(val)) if (nest === 0) {
|
||||
if (currentStr) {
|
||||
res.push(currentStr.trim());
|
||||
currentStr = "";
|
||||
}
|
||||
} else currentStr += " ";
|
||||
else if (!currentStr.endsWith(" ")) currentStr += " ";
|
||||
break;
|
||||
default: if (type === EOF) {
|
||||
res.push(currentStr.trim());
|
||||
currentStr = "";
|
||||
} else currentStr += val;
|
||||
}
|
||||
setCache(cacheKey, res);
|
||||
return res;
|
||||
};
|
||||
/**
|
||||
* extract dashed-ident tokens
|
||||
* @param value - CSS value
|
||||
* @returns array of dashed-ident tokens
|
||||
*/
|
||||
var extractDashedIdent = (value) => {
|
||||
if (!isString(value)) throw new TypeError(`${value} is not a string.`);
|
||||
const strValue = value.trim();
|
||||
const cacheKey = createCacheKey({
|
||||
namespace: NAMESPACE,
|
||||
name: "extractDashedIdent",
|
||||
value: strValue
|
||||
});
|
||||
const cachedResult = getCache(cacheKey);
|
||||
if (cachedResult instanceof CacheItem) return cachedResult.item;
|
||||
const matches = strValue.match(REG_DASHED_IDENT);
|
||||
const res = matches ? [...new Set(matches)] : [];
|
||||
setCache(cacheKey, res);
|
||||
return res;
|
||||
};
|
||||
/**
|
||||
* is color
|
||||
* @param value - CSS value
|
||||
* @param [opt] - options
|
||||
* @returns result
|
||||
*/
|
||||
var isColor = (value, opt = {}) => {
|
||||
if (!isString(value)) return false;
|
||||
const str = value.toLowerCase().trim();
|
||||
if (!str) return false;
|
||||
if (/^[a-z]+$/.test(str)) return str === "currentcolor" || str === "transparent" || Object.hasOwn(NAMED_COLORS, str);
|
||||
if (REG_COLOR.test(str) || REG_MIX.test(str)) return true;
|
||||
if (REG_FN_COLOR.test(str)) {
|
||||
const colorOpt = {
|
||||
...opt,
|
||||
nullable: true
|
||||
};
|
||||
if (!colorOpt.format) colorOpt.format = VAL_SPEC;
|
||||
return !!resolveColor(str, colorOpt);
|
||||
}
|
||||
return false;
|
||||
};
|
||||
/**
|
||||
* round to specified precision
|
||||
* @param value - numeric value
|
||||
* @param bit - minimum bits
|
||||
* @returns rounded value
|
||||
*/
|
||||
var roundToPrecision = (value, bit = 0) => {
|
||||
if (!Number.isFinite(value)) throw new TypeError(`${value} is not a finite number.`);
|
||||
if (!Number.isFinite(bit)) throw new TypeError(`${bit} is not a finite number.`);
|
||||
if (bit < 0 || bit > HEX) throw new RangeError(`${bit} is not between 0 and ${HEX}.`);
|
||||
if (bit === 0) return Math.round(value);
|
||||
const precision = bit === HEX ? 6 : bit < DEC ? 4 : 5;
|
||||
return parseFloat(value.toPrecision(precision));
|
||||
};
|
||||
/**
|
||||
* interpolate hue
|
||||
* @param hueA - hue value
|
||||
* @param hueB - hue value
|
||||
* @param arc - shorter | longer | increasing | decreasing
|
||||
* @returns result - [hueA, hueB]
|
||||
*/
|
||||
var interpolateHue = (hueA, hueB, arc = "shorter") => {
|
||||
if (!Number.isFinite(hueA)) throw new TypeError(`${hueA} is not a finite number.`);
|
||||
if (!Number.isFinite(hueB)) throw new TypeError(`${hueB} is not a finite number.`);
|
||||
let a = hueA;
|
||||
let b = hueB;
|
||||
switch (arc) {
|
||||
case "decreasing":
|
||||
if (b > a) a += DEG;
|
||||
break;
|
||||
case "increasing":
|
||||
if (b < a) b += DEG;
|
||||
break;
|
||||
case "longer":
|
||||
if (b > a && b < a + DEG_HALF) a += DEG;
|
||||
else if (b > a - DEG_HALF && b <= a) b += DEG;
|
||||
break;
|
||||
default: if (b > a + DEG_HALF) a += DEG;
|
||||
else if (b < a - DEG_HALF) b += DEG;
|
||||
}
|
||||
return [a, b];
|
||||
};
|
||||
var absoluteFontSize = new Map([
|
||||
["xx-small", 9 / 16],
|
||||
["x-small", 5 / 8],
|
||||
["small", 13 / 16],
|
||||
["medium", 1],
|
||||
["large", 9 / 8],
|
||||
["x-large", 3 / 2],
|
||||
["xx-large", 2],
|
||||
["xxx-large", 3]
|
||||
]);
|
||||
var relativeFontSize = new Map([["smaller", 1 / 1.2], ["larger", 1.2]]);
|
||||
var absoluteLength = new Map([
|
||||
["cm", 96 / 2.54],
|
||||
["mm", 96 / 25.4],
|
||||
["q", 96 / 101.6],
|
||||
["in", 96],
|
||||
["pc", 16],
|
||||
["pt", 96 / 72],
|
||||
["px", 1]
|
||||
]);
|
||||
var relativeLength = new Map([
|
||||
["rcap", 1],
|
||||
["rch", .5],
|
||||
["rem", 1],
|
||||
["rex", .5],
|
||||
["ric", 1],
|
||||
["rlh", 1.2]
|
||||
]);
|
||||
/**
|
||||
* resolve length in pixels
|
||||
* @param value - value
|
||||
* @param unit - unit
|
||||
* @param [opt] - options
|
||||
* @returns pixelated value
|
||||
*/
|
||||
var resolveLengthInPixels = (value, unit, opt = {}) => {
|
||||
const { dimension = {} } = opt;
|
||||
const { callback, em, rem, vh, vw } = dimension;
|
||||
if (isString(value)) {
|
||||
const str = value.toLowerCase().trim();
|
||||
const ratio = absoluteFontSize.get(str);
|
||||
if (ratio !== void 0) return ratio * rem;
|
||||
const relRatio = relativeFontSize.get(str);
|
||||
if (relRatio !== void 0) return relRatio * em;
|
||||
return NaN;
|
||||
}
|
||||
if (Number.isFinite(value) && unit) {
|
||||
const u = unit.toLowerCase();
|
||||
if (Object.hasOwn(dimension, u)) return value * Number(dimension[u]);
|
||||
if (typeof callback === "function") return value * (callback(u) ?? NaN);
|
||||
const absRatio = absoluteLength.get(u);
|
||||
if (absRatio !== void 0) return value * absRatio;
|
||||
const relRatio = relativeLength.get(u);
|
||||
if (relRatio !== void 0) return value * relRatio * rem;
|
||||
const rUnitRatio = relativeLength.get(`r${u}`);
|
||||
if (rUnitRatio !== void 0) return value * rUnitRatio * em;
|
||||
switch (u) {
|
||||
case "vb":
|
||||
case "vi": return value * vw;
|
||||
case "vmax": return value * Math.max(vh, vw);
|
||||
case "vmin": return value * Math.min(vh, vw);
|
||||
default:
|
||||
}
|
||||
}
|
||||
return NaN;
|
||||
};
|
||||
/**
|
||||
* is absolute size or length
|
||||
* @param value - value
|
||||
* @param unit - unit
|
||||
* @returns result
|
||||
*/
|
||||
var isAbsoluteSizeOrLength = (value, unit) => {
|
||||
if (isString(value)) return absoluteFontSize.has(value.toLowerCase().trim());
|
||||
if (isString(unit)) return absoluteLength.has(unit.toLowerCase().trim());
|
||||
return value === 0;
|
||||
};
|
||||
/**
|
||||
* is absolute font size
|
||||
* @param css - css
|
||||
* @returns result
|
||||
*/
|
||||
var isAbsoluteFontSize = (css) => {
|
||||
if (!isString(css)) return false;
|
||||
const str = css.trim();
|
||||
if (isAbsoluteSizeOrLength(str, void 0)) return true;
|
||||
const match = str.match(REG_DIMENSION);
|
||||
return match ? isAbsoluteSizeOrLength(Number(match[1]), match[2] || void 0) : false;
|
||||
};
|
||||
//#endregion
|
||||
export { extractDashedIdent, interpolateHue, isAbsoluteFontSize, isAbsoluteSizeOrLength, isColor, resolveLengthInPixels, roundToPrecision, splitValue };
|
||||
|
||||
//# sourceMappingURL=util.js.map
|
||||
1
node_modules/@asamuzakjp/css-color/dist/esm/js/util.js.map
generated
vendored
Normal file
1
node_modules/@asamuzakjp/css-color/dist/esm/js/util.js.map
generated
vendored
Normal file
File diff suppressed because one or more lines are too long
84
node_modules/@asamuzakjp/css-color/package.json
generated
vendored
Normal file
84
node_modules/@asamuzakjp/css-color/package.json
generated
vendored
Normal file
@@ -0,0 +1,84 @@
|
||||
{
|
||||
"name": "@asamuzakjp/css-color",
|
||||
"description": "CSS color - Resolve and convert CSS colors.",
|
||||
"author": "asamuzaK",
|
||||
"license": "MIT",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/asamuzaK/cssColor.git"
|
||||
},
|
||||
"homepage": "https://github.com/asamuzaK/cssColor#readme",
|
||||
"bugs": {
|
||||
"url": "https://github.com/asamuzaK/cssColor/issues"
|
||||
},
|
||||
"files": [
|
||||
"dist",
|
||||
"src"
|
||||
],
|
||||
"type": "module",
|
||||
"exports": {
|
||||
".": {
|
||||
"types": "./dist/esm/index.d.ts",
|
||||
"default": "./dist/esm/index.js"
|
||||
},
|
||||
"./package.json": "./package.json"
|
||||
},
|
||||
"dependencies": {
|
||||
"@csstools/css-calc": "^3.1.1",
|
||||
"@csstools/css-color-parser": "^4.0.2",
|
||||
"@csstools/css-parser-algorithms": "^4.0.0",
|
||||
"@csstools/css-tokenizer": "^4.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tanstack/vite-config": "^0.5.2",
|
||||
"@vitest/coverage-istanbul": "^4.1.4",
|
||||
"esbuild": "^0.27.7",
|
||||
"eslint": "^9.39.4",
|
||||
"eslint-plugin-regexp": "^3.1.0",
|
||||
"globals": "^17.4.0",
|
||||
"knip": "^6.4.0",
|
||||
"neostandard": "^0.13.0",
|
||||
"prettier": "^3.8.2",
|
||||
"publint": "^0.3.18",
|
||||
"rimraf": "^6.1.3",
|
||||
"typescript": "^5.9.3",
|
||||
"vite": "^8.0.8",
|
||||
"vitest": "^4.1.4"
|
||||
},
|
||||
"packageManager": "pnpm@10.33.0",
|
||||
"pnpm": {
|
||||
"onlyBuiltDependencies": [
|
||||
"esbuild",
|
||||
"oxc-resolver",
|
||||
"unrs-resolver"
|
||||
],
|
||||
"overrides": {
|
||||
"ajv": "^8.18.0",
|
||||
"@eslint/eslintrc>ajv": "^6.14.0",
|
||||
"eslint>ajv": "^6.14.0",
|
||||
"brace-expansion": "^5.0.4",
|
||||
"minimatch": "^3.1.5",
|
||||
"@typescript-eslint/typescript-estree>minimatch": "^9.0.9",
|
||||
"@vue/language-core>minimatch": "^9.0.9",
|
||||
"eslint-plugin-import-x>minimatch": "^10.2.4",
|
||||
"glob>minimatch": "^10.2.4",
|
||||
"lodash@>=4.0.0 <=4.17.23": ">=4.18.0",
|
||||
"lodash@<=4.17.23": ">=4.18.0"
|
||||
}
|
||||
},
|
||||
"scripts": {
|
||||
"build": "pnpm run clean && pnpm run test && pnpm run knip && vite build && pnpm run publint",
|
||||
"clean": "rimraf ./coverage ./dist",
|
||||
"knip": "knip",
|
||||
"prettier": "prettier . --ignore-unknown --write",
|
||||
"publint": "publint --strict",
|
||||
"test": "pnpm run prettier && pnpm run --stream \"/^test:.*/\"",
|
||||
"test:eslint": "eslint ./src ./test --fix",
|
||||
"test:types": "tsc",
|
||||
"test:unit": "vitest"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^20.19.0 || ^22.12.0 || >=24.0.0"
|
||||
},
|
||||
"version": "5.1.10"
|
||||
}
|
||||
34
node_modules/@asamuzakjp/css-color/src/index.ts
generated
vendored
Normal file
34
node_modules/@asamuzakjp/css-color/src/index.ts
generated
vendored
Normal file
@@ -0,0 +1,34 @@
|
||||
/*!
|
||||
* CSS color - Resolve, parse, convert CSS color.
|
||||
* @license MIT
|
||||
* @copyright asamuzaK (Kazz)
|
||||
* @see {@link https://github.com/asamuzaK/cssColor/blob/main/LICENSE}
|
||||
*/
|
||||
|
||||
import { cssCalc } from './js/css-calc';
|
||||
import { isGradient, resolveGradient } from './js/css-gradient';
|
||||
import { cssVar } from './js/css-var';
|
||||
import {
|
||||
extractDashedIdent,
|
||||
isAbsoluteFontSize,
|
||||
isAbsoluteSizeOrLength,
|
||||
isColor,
|
||||
resolveLengthInPixels,
|
||||
splitValue
|
||||
} from './js/util';
|
||||
|
||||
export { convert } from './js/convert';
|
||||
export { resolve } from './js/resolve';
|
||||
/* utils */
|
||||
export const utils = {
|
||||
cssCalc,
|
||||
cssVar,
|
||||
extractDashedIdent,
|
||||
isAbsoluteFontSize,
|
||||
isAbsoluteSizeOrLength,
|
||||
isColor,
|
||||
isGradient,
|
||||
resolveGradient,
|
||||
resolveLengthInPixels,
|
||||
splitValue
|
||||
};
|
||||
213
node_modules/@asamuzakjp/css-color/src/js/cache.ts
generated
vendored
Normal file
213
node_modules/@asamuzakjp/css-color/src/js/cache.ts
generated
vendored
Normal file
@@ -0,0 +1,213 @@
|
||||
/**
|
||||
* cache
|
||||
*/
|
||||
|
||||
import { Options } from './typedef';
|
||||
|
||||
/* constants */
|
||||
const MAX_CACHE = 1024;
|
||||
|
||||
/**
|
||||
* CacheItem
|
||||
*/
|
||||
export class CacheItem {
|
||||
/* private */
|
||||
#isNull: boolean;
|
||||
#item: unknown;
|
||||
|
||||
constructor(item: unknown, isNull: boolean = false) {
|
||||
this.#item = item;
|
||||
this.#isNull = !!isNull;
|
||||
}
|
||||
|
||||
get item() {
|
||||
return this.#item;
|
||||
}
|
||||
|
||||
get isNull() {
|
||||
return this.#isNull;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* NullObject
|
||||
*/
|
||||
export class NullObject extends CacheItem {
|
||||
constructor() {
|
||||
super(Symbol('null'), true);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generational Cache implementation
|
||||
*/
|
||||
export class GenerationalCache<K, V> {
|
||||
#max: number;
|
||||
#boundary: number;
|
||||
#current: Map<K, V>;
|
||||
#old: Map<K, V>;
|
||||
|
||||
constructor(max: number) {
|
||||
this.#current = new Map<K, V>();
|
||||
this.#old = new Map<K, V>();
|
||||
if (Number.isFinite(max) && max > 4) {
|
||||
this.#max = max;
|
||||
this.#boundary = Math.ceil(max / 2);
|
||||
} else {
|
||||
this.#max = 4;
|
||||
this.#boundary = 2;
|
||||
}
|
||||
}
|
||||
|
||||
get size(): number {
|
||||
return this.#current.size + this.#old.size;
|
||||
}
|
||||
|
||||
get max(): number {
|
||||
return this.#max;
|
||||
}
|
||||
|
||||
set max(value: number) {
|
||||
if (Number.isFinite(value) && value > 4) {
|
||||
this.#max = value;
|
||||
this.#boundary = Math.ceil(value / 2);
|
||||
} else {
|
||||
this.#max = 4;
|
||||
this.#boundary = 2;
|
||||
}
|
||||
this.#current.clear();
|
||||
this.#old.clear();
|
||||
}
|
||||
|
||||
get(key: K): V | undefined {
|
||||
let value = this.#current.get(key);
|
||||
if (value !== undefined) {
|
||||
return value;
|
||||
}
|
||||
value = this.#old.get(key);
|
||||
if (value !== undefined) {
|
||||
this.set(key, value);
|
||||
return value;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
set(key: K, value: V): void {
|
||||
this.#current.set(key, value);
|
||||
if (this.#current.size >= this.#boundary) {
|
||||
this.#old = this.#current;
|
||||
this.#current = new Map<K, V>();
|
||||
}
|
||||
}
|
||||
|
||||
has(key: K): boolean {
|
||||
return this.#current.has(key) || this.#old.has(key);
|
||||
}
|
||||
|
||||
delete(key: K): void {
|
||||
this.#current.delete(key);
|
||||
this.#old.delete(key);
|
||||
}
|
||||
|
||||
clear(): void {
|
||||
this.#current.clear();
|
||||
this.#old.clear();
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* generational cache instance
|
||||
*/
|
||||
export const genCache = new GenerationalCache<string, CacheItem>(MAX_CACHE);
|
||||
|
||||
/**
|
||||
* shared null object
|
||||
*/
|
||||
const sharedNullObject = new NullObject();
|
||||
|
||||
/**
|
||||
* set cache
|
||||
* @param key - cache key
|
||||
* @param value - value to cache
|
||||
* @returns void
|
||||
*/
|
||||
export const setCache = (key: string, value: unknown): void => {
|
||||
if (!key) {
|
||||
return;
|
||||
}
|
||||
if (value === null) {
|
||||
genCache.set(key, sharedNullObject);
|
||||
} else if (value instanceof CacheItem) {
|
||||
genCache.set(key, value);
|
||||
} else {
|
||||
genCache.set(key, new CacheItem(value));
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* get cache
|
||||
* @param key - cache key
|
||||
* @returns cached item or false otherwise
|
||||
*/
|
||||
export const getCache = (key: string): CacheItem | false => {
|
||||
if (!key) {
|
||||
return false;
|
||||
}
|
||||
const item = genCache.get(key);
|
||||
if (item !== undefined) {
|
||||
return item as CacheItem;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
/**
|
||||
* helper function to sort object keys alphabetically
|
||||
* @param obj - Object
|
||||
* @returns stringified JSON
|
||||
*/
|
||||
const stringifySorted = (obj: Record<string, unknown>): string => {
|
||||
const keys = Object.keys(obj);
|
||||
if (keys.length === 0) {
|
||||
return '';
|
||||
}
|
||||
keys.sort();
|
||||
let result = '';
|
||||
for (const key of keys) {
|
||||
result += `${key}:${JSON.stringify(obj[key])};`;
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
/**
|
||||
* create cache key
|
||||
* @param keyData - key data
|
||||
* @param [opt] - options
|
||||
* @returns cache key
|
||||
*/
|
||||
export const createCacheKey = (
|
||||
keyData: Record<string, string>,
|
||||
opt: Options = {}
|
||||
): string => {
|
||||
if (
|
||||
!keyData ||
|
||||
(opt.customProperty && typeof opt.customProperty.callback === 'function') ||
|
||||
(opt.dimension && typeof opt.dimension.callback === 'function')
|
||||
) {
|
||||
return '';
|
||||
}
|
||||
const namespace = keyData.namespace || '';
|
||||
const name = keyData.name || '';
|
||||
const value = keyData.value || '';
|
||||
if (!namespace && !name && !value) {
|
||||
return '';
|
||||
}
|
||||
const baseKey = `${namespace}:${name}:${value}`;
|
||||
const optStr = `${opt.format || ''}|${opt.colorSpace || ''}|${opt.colorScheme || ''}|${opt.currentColor || ''}|${opt.d50 ? '1' : '0'}|${opt.nullable ? '1' : '0'}|${opt.preserveComment ? '1' : '0'}|${opt.delimiter || ''}`;
|
||||
const customPropStr = opt.customProperty
|
||||
? stringifySorted(opt.customProperty as Record<string, unknown>)
|
||||
: '';
|
||||
const dimStr = opt.dimension
|
||||
? stringifySorted(opt.dimension as Record<string, unknown>)
|
||||
: '';
|
||||
return `${baseKey}::${optStr}::${customPropStr}::${dimStr}`;
|
||||
};
|
||||
3496
node_modules/@asamuzakjp/css-color/src/js/color.ts
generated
vendored
Normal file
3496
node_modules/@asamuzakjp/css-color/src/js/color.ts
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
31
node_modules/@asamuzakjp/css-color/src/js/common.ts
generated
vendored
Normal file
31
node_modules/@asamuzakjp/css-color/src/js/common.ts
generated
vendored
Normal file
@@ -0,0 +1,31 @@
|
||||
/**
|
||||
* common
|
||||
*/
|
||||
|
||||
/* numeric constants */
|
||||
const TYPE_FROM = 8;
|
||||
const TYPE_TO = -1;
|
||||
|
||||
/**
|
||||
* get type
|
||||
* @param o - object to check
|
||||
* @returns type of object
|
||||
*/
|
||||
export const getType = (o: unknown): string =>
|
||||
Object.prototype.toString.call(o).slice(TYPE_FROM, TYPE_TO);
|
||||
|
||||
/**
|
||||
* is string
|
||||
* @param o - object to check
|
||||
* @returns result
|
||||
*/
|
||||
export const isString = (o: unknown): o is string =>
|
||||
typeof o === 'string' || o instanceof String;
|
||||
|
||||
/**
|
||||
* is string or number
|
||||
* @param o - object to check
|
||||
* @returns result
|
||||
*/
|
||||
export const isStringOrNumber = (o: unknown): boolean =>
|
||||
isString(o) || typeof o === 'number';
|
||||
68
node_modules/@asamuzakjp/css-color/src/js/constant.ts
generated
vendored
Normal file
68
node_modules/@asamuzakjp/css-color/src/js/constant.ts
generated
vendored
Normal file
@@ -0,0 +1,68 @@
|
||||
/**
|
||||
* constant
|
||||
*/
|
||||
|
||||
/* values and units */
|
||||
const _DIGIT = '(?:0|[1-9]\\d*)';
|
||||
const _COMPARE = 'clamp|max|min';
|
||||
const _EXPO = 'exp|hypot|log|pow|sqrt';
|
||||
const _SIGN = 'abs|sign';
|
||||
const _STEP = 'mod|rem|round';
|
||||
const _TRIG = 'a?(?:cos|sin|tan)|atan2';
|
||||
const _MATH = `${_COMPARE}|${_EXPO}|${_SIGN}|${_STEP}|${_TRIG}`;
|
||||
const _CALC = `calc|${_MATH}`;
|
||||
const _VAR = `var|${_CALC}`;
|
||||
export const ANGLE = 'deg|g?rad|turn';
|
||||
export const LENGTH =
|
||||
'[cm]m|[dls]?v(?:[bhiw]|max|min)|in|p[ctx]|q|r?(?:[cl]h|cap|e[mx]|ic)';
|
||||
export const NUM = `[+-]?(?:${_DIGIT}(?:\\.\\d*)?|\\.\\d+)(?:e-?${_DIGIT})?`;
|
||||
export const NUM_POSITIVE = `\\+?(?:${_DIGIT}(?:\\.\\d*)?|\\.\\d+)(?:e-?${_DIGIT})?`;
|
||||
export const NONE = 'none';
|
||||
export const PCT = `${NUM}%`;
|
||||
export const SYN_FN_CALC = `^(?:${_CALC})\\(|(?<=[*\\/\\s\\(])(?:${_CALC})\\(`;
|
||||
export const SYN_FN_MATH_START = `^(?:${_MATH})\\($`;
|
||||
export const SYN_FN_VAR = '^var\\(|(?<=[*\\/\\s\\(])var\\(';
|
||||
export const SYN_FN_VAR_START = `^(?:${_VAR})\\(`;
|
||||
|
||||
/* colors */
|
||||
const _ALPHA = `(?:\\s*\\/\\s*(?:${NUM}|${PCT}|${NONE}))?`;
|
||||
const _ALPHA_LV3 = `(?:\\s*,\\s*(?:${NUM}|${PCT}))?`;
|
||||
const _COLOR_FUNC = '(?:ok)?l(?:ab|ch)|color|hsla?|hwb|rgba?';
|
||||
const _COLOR_KEY = '[a-z]+|#[\\da-f]{3}|#[\\da-f]{4}|#[\\da-f]{6}|#[\\da-f]{8}';
|
||||
const _CS_HUE = '(?:ok)?lch|hsl|hwb';
|
||||
const _CS_HUE_ARC = '(?:de|in)creasing|longer|shorter';
|
||||
const _NUM_ANGLE = `${NUM}(?:${ANGLE})?`;
|
||||
const _NUM_ANGLE_NONE = `(?:${NUM}(?:${ANGLE})?|${NONE})`;
|
||||
const _NUM_PCT_NONE = `(?:${NUM}|${PCT}|${NONE})`;
|
||||
export const CS_HUE = `(?:${_CS_HUE})(?:\\s(?:${_CS_HUE_ARC})\\shue)?`;
|
||||
export const CS_HUE_CAPT = `(${_CS_HUE})(?:\\s(${_CS_HUE_ARC})\\shue)?`;
|
||||
export const CS_LAB = '(?:ok)?lab';
|
||||
export const CS_LCH = '(?:ok)?lch';
|
||||
export const CS_SRGB = 'srgb(?:-linear)?';
|
||||
export const CS_RGB = `(?:a98|prophoto)-rgb|display-p3|rec2020|${CS_SRGB}`;
|
||||
export const CS_XYZ = 'xyz(?:-d(?:50|65))?';
|
||||
export const CS_RECT = `${CS_LAB}|${CS_RGB}|${CS_XYZ}`;
|
||||
export const CS_MIX = `${CS_HUE}|${CS_RECT}`;
|
||||
export const FN_COLOR = 'color(';
|
||||
export const FN_LIGHT_DARK = 'light-dark(';
|
||||
export const FN_MIX = 'color-mix(';
|
||||
export const FN_REL = `(?:${_COLOR_FUNC})\\(\\s*from\\s+`;
|
||||
export const FN_REL_CAPT = `(${_COLOR_FUNC})\\(\\s*from\\s+`;
|
||||
export const FN_VAR = 'var(';
|
||||
export const SYN_FN_COLOR = `(?:${CS_RGB}|${CS_XYZ})(?:\\s+${_NUM_PCT_NONE}){3}${_ALPHA}`;
|
||||
export const SYN_FN_LIGHT_DARK = '^light-dark\\(';
|
||||
export const SYN_FN_REL = `^${FN_REL}|(?<=[\\s])${FN_REL}`;
|
||||
export const SYN_HSL = `${_NUM_ANGLE_NONE}(?:\\s+${_NUM_PCT_NONE}){2}${_ALPHA}`;
|
||||
export const SYN_HSL_LV3 = `${_NUM_ANGLE}(?:\\s*,\\s*${PCT}){2}${_ALPHA_LV3}`;
|
||||
export const SYN_LCH = `(?:${_NUM_PCT_NONE}\\s+){2}${_NUM_ANGLE_NONE}${_ALPHA}`;
|
||||
export const SYN_MOD = `${_NUM_PCT_NONE}(?:\\s+${_NUM_PCT_NONE}){2}${_ALPHA}`;
|
||||
export const SYN_RGB_LV3 = `(?:${NUM}(?:\\s*,\\s*${NUM}){2}|${PCT}(?:\\s*,\\s*${PCT}){2})${_ALPHA_LV3}`;
|
||||
export const SYN_COLOR_TYPE = `${_COLOR_KEY}|hsla?\\(\\s*${SYN_HSL_LV3}\\s*\\)|rgba?\\(\\s*${SYN_RGB_LV3}\\s*\\)|(?:hsla?|hwb)\\(\\s*${SYN_HSL}\\s*\\)|(?:(?:ok)?lab|rgba?)\\(\\s*${SYN_MOD}\\s*\\)|(?:ok)?lch\\(\\s*${SYN_LCH}\\s*\\)|color\\(\\s*${SYN_FN_COLOR}\\s*\\)`;
|
||||
export const SYN_MIX_PART = `(?:${SYN_COLOR_TYPE})(?:\\s+${PCT})?`;
|
||||
export const SYN_MIX = `color-mix\\(\\s*in\\s+(?:${CS_MIX})\\s*,\\s*${SYN_MIX_PART}\\s*,\\s*${SYN_MIX_PART}\\s*\\)`;
|
||||
export const SYN_MIX_CAPT = `color-mix\\(\\s*in\\s+(${CS_MIX})\\s*,\\s*(${SYN_MIX_PART})\\s*,\\s*(${SYN_MIX_PART})\\s*\\)`;
|
||||
|
||||
/* formats */
|
||||
export const VAL_COMP = 'computedValue';
|
||||
export const VAL_MIX = 'mixValue';
|
||||
export const VAL_SPEC = 'specifiedValue';
|
||||
331
node_modules/@asamuzakjp/css-color/src/js/convert.ts
generated
vendored
Normal file
331
node_modules/@asamuzakjp/css-color/src/js/convert.ts
generated
vendored
Normal file
@@ -0,0 +1,331 @@
|
||||
/**
|
||||
* convert
|
||||
*/
|
||||
|
||||
import {
|
||||
CacheItem,
|
||||
NullObject,
|
||||
createCacheKey,
|
||||
getCache,
|
||||
setCache
|
||||
} from './cache';
|
||||
import {
|
||||
convertColorToHsl,
|
||||
convertColorToHwb,
|
||||
convertColorToLab,
|
||||
convertColorToLch,
|
||||
convertColorToOklab,
|
||||
convertColorToOklch,
|
||||
convertColorToRgb,
|
||||
numberToHexString,
|
||||
parseColorFunc,
|
||||
parseColorValue
|
||||
} from './color';
|
||||
import { isString } from './common';
|
||||
import { cssCalc } from './css-calc';
|
||||
import { resolveVar } from './css-var';
|
||||
import { resolveRelativeColor } from './relative-color';
|
||||
import { resolveColor } from './resolve';
|
||||
import { ColorChannels, ComputedColorChannels, Options } from './typedef';
|
||||
|
||||
/* constants */
|
||||
import { SYN_FN_CALC, SYN_FN_REL, SYN_FN_VAR, VAL_COMP } from './constant';
|
||||
const NAMESPACE = 'convert';
|
||||
|
||||
/* regexp */
|
||||
const REG_FN_CALC = new RegExp(SYN_FN_CALC);
|
||||
const REG_FN_REL = new RegExp(SYN_FN_REL);
|
||||
const REG_FN_VAR = new RegExp(SYN_FN_VAR);
|
||||
|
||||
/**
|
||||
* pre process
|
||||
* @param value - CSS color value
|
||||
* @param [opt] - options
|
||||
* @returns value
|
||||
*/
|
||||
export const preProcess = (
|
||||
value: string,
|
||||
opt: Options = {}
|
||||
): string | NullObject => {
|
||||
if (!isString(value)) {
|
||||
return new NullObject();
|
||||
}
|
||||
value = value.trim();
|
||||
if (!value) {
|
||||
return new NullObject();
|
||||
}
|
||||
const cacheKey: string = createCacheKey(
|
||||
{ namespace: NAMESPACE, name: 'preProcess', value },
|
||||
opt
|
||||
);
|
||||
const cachedResult = getCache(cacheKey);
|
||||
if (cachedResult instanceof CacheItem) {
|
||||
if (cachedResult.isNull) {
|
||||
return cachedResult as NullObject;
|
||||
}
|
||||
return cachedResult.item as string;
|
||||
}
|
||||
let res: string | NullObject = value;
|
||||
if (REG_FN_VAR.test(value)) {
|
||||
const resolved = resolveVar(value, opt);
|
||||
if (isString(resolved)) {
|
||||
res = resolved;
|
||||
} else {
|
||||
setCache(cacheKey, null);
|
||||
return new NullObject();
|
||||
}
|
||||
}
|
||||
if (isString(res)) {
|
||||
if (REG_FN_REL.test(res)) {
|
||||
const resolved = resolveRelativeColor(res, opt);
|
||||
if (isString(resolved)) {
|
||||
res = resolved;
|
||||
} else {
|
||||
setCache(cacheKey, null);
|
||||
return new NullObject();
|
||||
}
|
||||
} else if (REG_FN_CALC.test(res)) {
|
||||
res = cssCalc(res, opt);
|
||||
}
|
||||
}
|
||||
if (isString(res)) {
|
||||
if (res.startsWith('color-mix')) {
|
||||
res = resolveColor(res, { ...opt, format: VAL_COMP, nullable: true });
|
||||
}
|
||||
}
|
||||
setCache(cacheKey, res);
|
||||
return res;
|
||||
};
|
||||
|
||||
/**
|
||||
* converter factory to reduce boilerplate
|
||||
* @param name - function name for cache
|
||||
* @param format - color format
|
||||
* @param convertFn - conversion function
|
||||
* @returns color converter function
|
||||
*/
|
||||
const createColorConverter = (
|
||||
name: string,
|
||||
format: string,
|
||||
convertFn: Function
|
||||
) => {
|
||||
const colorConverterFn = (
|
||||
value: string,
|
||||
opt: Options = {}
|
||||
): ColorChannels => {
|
||||
if (!isString(value)) {
|
||||
throw new TypeError(`${value} is not a string.`);
|
||||
}
|
||||
const resolved = preProcess(value, opt);
|
||||
if (resolved instanceof NullObject) {
|
||||
return [0, 0, 0, 0];
|
||||
}
|
||||
const val = resolved.toLowerCase();
|
||||
const cacheKey = createCacheKey(
|
||||
{ namespace: NAMESPACE, name, value: val },
|
||||
opt
|
||||
);
|
||||
const cached = getCache(cacheKey);
|
||||
if (cached instanceof CacheItem) {
|
||||
return cached.item as ColorChannels;
|
||||
}
|
||||
const result = convertFn(val, { ...opt, format }) as ColorChannels;
|
||||
setCache(cacheKey, result);
|
||||
return result;
|
||||
};
|
||||
return colorConverterFn;
|
||||
};
|
||||
|
||||
/**
|
||||
* convert number to hex string
|
||||
* @param value - numeric value
|
||||
* @returns hex string: 00..ff
|
||||
*/
|
||||
export const numberToHex = (value: number): string => numberToHexString(value);
|
||||
|
||||
/**
|
||||
* convert color to hex
|
||||
* @param value - CSS color value
|
||||
* @param [opt] - options
|
||||
* @param [opt.alpha] - enable alpha channel
|
||||
* @returns #rrggbb | #rrggbbaa | null
|
||||
*/
|
||||
export const colorToHex = (value: string, opt: Options = {}): string | null => {
|
||||
if (!isString(value)) {
|
||||
throw new TypeError(`${value} is not a string.`);
|
||||
}
|
||||
const resolved = preProcess(value, opt);
|
||||
if (resolved instanceof NullObject) {
|
||||
return null;
|
||||
}
|
||||
const val = resolved.toLowerCase();
|
||||
const cacheKey = createCacheKey(
|
||||
{ namespace: NAMESPACE, name: 'colorToHex', value: val },
|
||||
opt
|
||||
);
|
||||
const cached = getCache(cacheKey);
|
||||
if (cached instanceof CacheItem) {
|
||||
if (cached.isNull) {
|
||||
return null;
|
||||
}
|
||||
return cached.item as string;
|
||||
}
|
||||
const hex = resolveColor(val, {
|
||||
...opt,
|
||||
nullable: true,
|
||||
format: opt.alpha ? 'hexAlpha' : 'hex'
|
||||
});
|
||||
if (isString(hex)) {
|
||||
setCache(cacheKey, hex);
|
||||
return hex;
|
||||
}
|
||||
setCache(cacheKey, null);
|
||||
return null;
|
||||
};
|
||||
|
||||
/**
|
||||
* convert color to hsl
|
||||
* @param value - CSS color value
|
||||
* @param [opt] - options
|
||||
* @returns ColorChannels - [h, s, l, alpha]
|
||||
*/
|
||||
export const colorToHsl = createColorConverter(
|
||||
'colorToHsl',
|
||||
'hsl',
|
||||
convertColorToHsl
|
||||
);
|
||||
|
||||
/**
|
||||
* convert color to hwb
|
||||
* @param value - CSS color value
|
||||
* @param [opt] - options
|
||||
* @returns ColorChannels - [h, w, b, alpha]
|
||||
*/
|
||||
export const colorToHwb = createColorConverter(
|
||||
'colorToHwb',
|
||||
'hwb',
|
||||
convertColorToHwb
|
||||
);
|
||||
|
||||
/**
|
||||
* convert color to lab
|
||||
* @param value - CSS color value
|
||||
* @param [opt] - options
|
||||
* @returns ColorChannels - [l, a, b, alpha]
|
||||
*/
|
||||
export const colorToLab = createColorConverter(
|
||||
'colorToLab',
|
||||
'lab',
|
||||
convertColorToLab
|
||||
);
|
||||
|
||||
/**
|
||||
* convert color to lch
|
||||
* @param value - CSS color value
|
||||
* @param [opt] - options
|
||||
* @returns ColorChannels - [l, c, h, alpha]
|
||||
*/
|
||||
export const colorToLch = createColorConverter(
|
||||
'colorToLch',
|
||||
'lch',
|
||||
convertColorToLch
|
||||
);
|
||||
|
||||
/**
|
||||
* convert color to oklab
|
||||
* @param value - CSS color value
|
||||
* @param [opt] - options
|
||||
* @returns ColorChannels - [l, a, b, alpha]
|
||||
*/
|
||||
export const colorToOklab = createColorConverter(
|
||||
'colorToOklab',
|
||||
'oklab',
|
||||
convertColorToOklab
|
||||
);
|
||||
|
||||
/**
|
||||
* convert color to oklch
|
||||
* @param value - CSS color value
|
||||
* @param [opt] - options
|
||||
* @returns ColorChannels - [l, c, h, alpha]
|
||||
*/
|
||||
export const colorToOklch = createColorConverter(
|
||||
'colorToOklch',
|
||||
'oklch',
|
||||
convertColorToOklch
|
||||
);
|
||||
|
||||
/**
|
||||
* convert color to rgb
|
||||
* @param value - CSS color value
|
||||
* @param [opt] - options
|
||||
* @returns ColorChannels - [r, g, b, alpha]
|
||||
*/
|
||||
export const colorToRgb = createColorConverter(
|
||||
'colorToRgb',
|
||||
'rgb',
|
||||
convertColorToRgb
|
||||
);
|
||||
|
||||
/**
|
||||
* convert color to xyz
|
||||
* @param value - CSS color value
|
||||
* @param [opt] - options
|
||||
* @returns ColorChannels - [x, y, z, alpha]
|
||||
*/
|
||||
export const colorToXyz = (value: string, opt: Options = {}): ColorChannels => {
|
||||
if (!isString(value)) {
|
||||
throw new TypeError(`${value} is not a string.`);
|
||||
}
|
||||
const resolved = preProcess(value, opt);
|
||||
if (resolved instanceof NullObject) {
|
||||
return [0, 0, 0, 0];
|
||||
}
|
||||
const val = resolved.toLowerCase();
|
||||
const cacheKey = createCacheKey(
|
||||
{ namespace: NAMESPACE, name: 'colorToXyz', value: val },
|
||||
opt
|
||||
);
|
||||
const cached = getCache(cacheKey);
|
||||
if (cached instanceof CacheItem) {
|
||||
return cached.item as ColorChannels;
|
||||
}
|
||||
let parsed;
|
||||
if (val.startsWith('color(')) {
|
||||
parsed = parseColorFunc(val, opt);
|
||||
} else {
|
||||
parsed = parseColorValue(val, opt);
|
||||
}
|
||||
const [, ...xyz] = parsed as ComputedColorChannels;
|
||||
setCache(cacheKey, xyz);
|
||||
return xyz as ColorChannels;
|
||||
};
|
||||
|
||||
/**
|
||||
* convert color to xyz-d50
|
||||
* @param value - CSS color value
|
||||
* @param [opt] - options
|
||||
* @returns ColorChannels - [x, y, z, alpha]
|
||||
*/
|
||||
export const colorToXyzD50 = (
|
||||
value: string,
|
||||
opt: Options = {}
|
||||
): ColorChannels => {
|
||||
opt.d50 = true;
|
||||
return colorToXyz(value, opt);
|
||||
};
|
||||
|
||||
/* convert */
|
||||
export const convert = {
|
||||
colorToHex,
|
||||
colorToHsl,
|
||||
colorToHwb,
|
||||
colorToLab,
|
||||
colorToLch,
|
||||
colorToOklab,
|
||||
colorToOklch,
|
||||
colorToRgb,
|
||||
colorToXyz,
|
||||
colorToXyzD50,
|
||||
numberToHex
|
||||
};
|
||||
1008
node_modules/@asamuzakjp/css-color/src/js/css-calc.ts
generated
vendored
Normal file
1008
node_modules/@asamuzakjp/css-color/src/js/css-calc.ts
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
374
node_modules/@asamuzakjp/css-color/src/js/css-gradient.ts
generated
vendored
Normal file
374
node_modules/@asamuzakjp/css-color/src/js/css-gradient.ts
generated
vendored
Normal file
@@ -0,0 +1,374 @@
|
||||
/**
|
||||
* css-gradient
|
||||
*/
|
||||
|
||||
import { CacheItem, createCacheKey, getCache, setCache } from './cache';
|
||||
import { resolveColor } from './resolve';
|
||||
import { isString } from './common';
|
||||
import { MatchedRegExp, Options } from './typedef';
|
||||
import { isColor, splitValue } from './util';
|
||||
|
||||
/* constants */
|
||||
import {
|
||||
ANGLE,
|
||||
CS_HUE,
|
||||
CS_RECT,
|
||||
LENGTH,
|
||||
NUM,
|
||||
NUM_POSITIVE,
|
||||
PCT,
|
||||
VAL_COMP,
|
||||
VAL_SPEC
|
||||
} from './constant';
|
||||
const NAMESPACE = 'css-gradient';
|
||||
const DIM_ANGLE = `${NUM}(?:${ANGLE})`;
|
||||
const DIM_ANGLE_PCT = `${DIM_ANGLE}|${PCT}`;
|
||||
const DIM_LEN = `${NUM}(?:${LENGTH})|0`;
|
||||
const DIM_LEN_PCT = `${DIM_LEN}|${PCT}`;
|
||||
const DIM_LEN_PCT_POSI = `${NUM_POSITIVE}(?:${LENGTH}|%)|0`;
|
||||
const DIM_LEN_POSI = `${NUM_POSITIVE}(?:${LENGTH})|0`;
|
||||
const CTR = 'center';
|
||||
const L_R = 'left|right';
|
||||
const T_B = 'top|bottom';
|
||||
const S_E = 'start|end';
|
||||
const AXIS_X = `${L_R}|x-(?:${S_E})`;
|
||||
const AXIS_Y = `${T_B}|y-(?:${S_E})`;
|
||||
const BLOCK = `block-(?:${S_E})`;
|
||||
const INLINE = `inline-(?:${S_E})`;
|
||||
const POS_1 = `${CTR}|${AXIS_X}|${AXIS_Y}|${BLOCK}|${INLINE}|${DIM_LEN_PCT}`;
|
||||
const POS_2 = [
|
||||
`(?:${CTR}|${AXIS_X})\\s+(?:${CTR}|${AXIS_Y})`,
|
||||
`(?:${CTR}|${AXIS_Y})\\s+(?:${CTR}|${AXIS_X})`,
|
||||
`(?:${CTR}|${AXIS_X}|${DIM_LEN_PCT})\\s+(?:${CTR}|${AXIS_Y}|${DIM_LEN_PCT})`,
|
||||
`(?:${CTR}|${BLOCK})\\s+(?:${CTR}|${INLINE})`,
|
||||
`(?:${CTR}|${INLINE})\\s+(?:${CTR}|${BLOCK})`,
|
||||
`(?:${CTR}|${S_E})\\s+(?:${CTR}|${S_E})`
|
||||
].join('|');
|
||||
const POS_4 = [
|
||||
`(?:${AXIS_X})\\s+(?:${DIM_LEN_PCT})\\s+(?:${AXIS_Y})\\s+(?:${DIM_LEN_PCT})`,
|
||||
`(?:${AXIS_Y})\\s+(?:${DIM_LEN_PCT})\\s+(?:${AXIS_X})\\s+(?:${DIM_LEN_PCT})`,
|
||||
`(?:${BLOCK})\\s+(?:${DIM_LEN_PCT})\\s+(?:${INLINE})\\s+(?:${DIM_LEN_PCT})`,
|
||||
`(?:${INLINE})\\s+(?:${DIM_LEN_PCT})\\s+(?:${BLOCK})\\s+(?:${DIM_LEN_PCT})`,
|
||||
`(?:${S_E})\\s+(?:${DIM_LEN_PCT})\\s+(?:${S_E})\\s+(?:${DIM_LEN_PCT})`
|
||||
].join('|');
|
||||
const RAD_EXTENT = '(?:clos|farth)est-(?:corner|side)';
|
||||
const RAD_SIZE = [
|
||||
`${RAD_EXTENT}(?:\\s+${RAD_EXTENT})?`,
|
||||
`${DIM_LEN_POSI}`,
|
||||
`(?:${DIM_LEN_PCT_POSI})\\s+(?:${DIM_LEN_PCT_POSI})`
|
||||
].join('|');
|
||||
const RAD_SHAPE = 'circle|ellipse';
|
||||
const FROM_ANGLE = `from\\s+${DIM_ANGLE}`;
|
||||
const AT_POSITION = `at\\s+(?:${POS_1}|${POS_2}|${POS_4})`;
|
||||
const TO_SIDE_CORNER = `to\\s+(?:(?:${L_R})(?:\\s(?:${T_B}))?|(?:${T_B})(?:\\s(?:${L_R}))?)`;
|
||||
const IN_COLOR_SPACE = `in\\s+(?:${CS_RECT}|${CS_HUE})`;
|
||||
const LINE_SYNTAX_LINEAR = [
|
||||
`(?:${DIM_ANGLE}|${TO_SIDE_CORNER})(?:\\s+${IN_COLOR_SPACE})?`,
|
||||
`${IN_COLOR_SPACE}(?:\\s+(?:${DIM_ANGLE}|${TO_SIDE_CORNER}))?`
|
||||
].join('|');
|
||||
const LINE_SYNTAX_RADIAL = [
|
||||
`(?:${RAD_SHAPE})(?:\\s+(?:${RAD_SIZE}))?(?:\\s+${AT_POSITION})?(?:\\s+${IN_COLOR_SPACE})?`,
|
||||
`(?:${RAD_SIZE})(?:\\s+(?:${RAD_SHAPE}))?(?:\\s+${AT_POSITION})?(?:\\s+${IN_COLOR_SPACE})?`,
|
||||
`${AT_POSITION}(?:\\s+${IN_COLOR_SPACE})?`,
|
||||
`${IN_COLOR_SPACE}(?:\\s+${RAD_SHAPE})(?:\\s+(?:${RAD_SIZE}))?(?:\\s+${AT_POSITION})?`,
|
||||
`${IN_COLOR_SPACE}(?:\\s+${RAD_SIZE})(?:\\s+(?:${RAD_SHAPE}))?(?:\\s+${AT_POSITION})?`,
|
||||
`${IN_COLOR_SPACE}(?:\\s+${AT_POSITION})?`
|
||||
].join('|');
|
||||
const LINE_SYNTAX_CONIC = [
|
||||
`${FROM_ANGLE}(?:\\s+${AT_POSITION})?(?:\\s+${IN_COLOR_SPACE})?`,
|
||||
`${AT_POSITION}(?:\\s+${IN_COLOR_SPACE})?`,
|
||||
`${IN_COLOR_SPACE}(?:\\s+${FROM_ANGLE})?(?:\\s+${AT_POSITION})?`
|
||||
].join('|');
|
||||
const DEFAULT_LINEAR = [/to\s+bottom/];
|
||||
const DEFAULT_RADIAL = [/ellipse/, /farthest-corner/, /at\s+center/];
|
||||
const DEFAULT_CONIC = [/at\s+center/];
|
||||
|
||||
/* type definitions */
|
||||
/**
|
||||
* @type ColorStopList - list of color stops
|
||||
*/
|
||||
type ColorStopList = [string, string, ...string[]];
|
||||
|
||||
/**
|
||||
* @typedef ValidateGradientLine - validate gradient line
|
||||
* @property line - gradient line
|
||||
* @property valid - result
|
||||
*/
|
||||
interface ValidateGradientLine {
|
||||
line: string;
|
||||
valid: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* @typedef ValidateColorStops - validate color stops
|
||||
* @property colorStops - list of color stops
|
||||
* @property valid - result
|
||||
*/
|
||||
interface ValidateColorStops {
|
||||
colorStops: string[];
|
||||
valid: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* @typedef Gradient - parsed CSS gradient
|
||||
* @property value - input value
|
||||
* @property type - gradient type
|
||||
* @property [gradientLine] - gradient line
|
||||
* @property colorStopList - list of color stops
|
||||
*/
|
||||
interface Gradient {
|
||||
value: string;
|
||||
type: string;
|
||||
gradientLine?: string;
|
||||
colorStopList: ColorStopList;
|
||||
}
|
||||
|
||||
/* regexp */
|
||||
const IS_CONIC = /^(?:repeating-)?conic-gradient$/;
|
||||
const IS_LINEAR = /^(?:repeating-)?linear-gradient$/;
|
||||
const IS_RADIAL = /^(?:repeating-)?radial-gradient$/;
|
||||
const REG_COLOR_HINT_CONIC = new RegExp(`^(?:${DIM_ANGLE_PCT})$`);
|
||||
const REG_COLOR_HINT_NON_CONIC = new RegExp(`^(?:${DIM_LEN_PCT})$`);
|
||||
const REG_DIM_CONIC = new RegExp(`(?:\\s+(?:${DIM_ANGLE_PCT})){1,2}$`);
|
||||
const REG_DIM_NON_CONIC = new RegExp(`(?:\\s+(?:${DIM_LEN_PCT})){1,2}$`);
|
||||
const REG_GRAD = /^(?:repeating-)?(?:conic|linear|radial)-gradient\(/;
|
||||
const REG_GRAD_CAPT = /^((?:repeating-)?(?:conic|linear|radial)-gradient)\(/;
|
||||
const REG_LINE_CONIC = new RegExp(`^(?:${LINE_SYNTAX_CONIC})$`);
|
||||
const REG_LINE_LINEAR = new RegExp(`^(?:${LINE_SYNTAX_LINEAR})$`);
|
||||
const REG_LINE_RADIAL = new RegExp(`^(?:${LINE_SYNTAX_RADIAL})$`);
|
||||
|
||||
/**
|
||||
* get gradient type
|
||||
* @param value - gradient value
|
||||
* @returns gradient type
|
||||
*/
|
||||
export const getGradientType = (value: string): string => {
|
||||
if (isString(value)) {
|
||||
value = value.trim();
|
||||
if (REG_GRAD.test(value)) {
|
||||
const [, type] = value.match(REG_GRAD_CAPT) as MatchedRegExp;
|
||||
return type;
|
||||
}
|
||||
}
|
||||
return '';
|
||||
};
|
||||
|
||||
/**
|
||||
* validate gradient line
|
||||
* @param value - gradient line value
|
||||
* @param type - gradient type
|
||||
* @returns result
|
||||
*/
|
||||
export const validateGradientLine = (
|
||||
value: string,
|
||||
type: string
|
||||
): ValidateGradientLine => {
|
||||
if (isString(value) && isString(type)) {
|
||||
value = value.trim();
|
||||
type = type.trim();
|
||||
let reg: RegExp | null = null;
|
||||
let defaultValues: RegExp[] = [];
|
||||
|
||||
if (IS_LINEAR.test(type)) {
|
||||
reg = REG_LINE_LINEAR;
|
||||
defaultValues = DEFAULT_LINEAR;
|
||||
} else if (IS_RADIAL.test(type)) {
|
||||
reg = REG_LINE_RADIAL;
|
||||
defaultValues = DEFAULT_RADIAL;
|
||||
} else if (IS_CONIC.test(type)) {
|
||||
reg = REG_LINE_CONIC;
|
||||
defaultValues = DEFAULT_CONIC;
|
||||
}
|
||||
if (reg) {
|
||||
const valid = reg.test(value);
|
||||
if (valid) {
|
||||
let line = value;
|
||||
for (const defaultValue of defaultValues) {
|
||||
line = line.replace(defaultValue, '');
|
||||
}
|
||||
line = line.replace(/\s{2,}/g, ' ').trim();
|
||||
return { line, valid };
|
||||
}
|
||||
return { valid, line: value };
|
||||
}
|
||||
}
|
||||
return { line: value, valid: false };
|
||||
};
|
||||
|
||||
/**
|
||||
* validate color stop list
|
||||
* @param list
|
||||
* @param type
|
||||
* @param [opt]
|
||||
* @returns result
|
||||
*/
|
||||
export const validateColorStopList = (
|
||||
list: string[],
|
||||
type: string,
|
||||
opt: Options = {}
|
||||
): ValidateColorStops => {
|
||||
if (Array.isArray(list) && list.length > 1) {
|
||||
const isConic = IS_CONIC.test(type);
|
||||
const regColorHint = isConic
|
||||
? REG_COLOR_HINT_CONIC
|
||||
: REG_COLOR_HINT_NON_CONIC;
|
||||
const regDimension = isConic ? REG_DIM_CONIC : REG_DIM_NON_CONIC;
|
||||
const valueList: string[] = [];
|
||||
// State tracker: 'color' or 'hint'
|
||||
let prevType = '';
|
||||
for (let i = 0; i < list.length; i++) {
|
||||
const item = list[i];
|
||||
if (isString(item)) {
|
||||
if (regColorHint.test(item)) {
|
||||
// Hints cannot be the first item, and two hints cannot be adjacent
|
||||
if (i === 0 || prevType === 'hint') {
|
||||
return { colorStops: list, valid: false };
|
||||
}
|
||||
prevType = 'hint';
|
||||
valueList.push(item);
|
||||
} else {
|
||||
const itemColor = item.replace(regDimension, '');
|
||||
if (isColor(itemColor, { format: VAL_SPEC })) {
|
||||
const resolvedColor = resolveColor(itemColor, opt) as string;
|
||||
prevType = 'color';
|
||||
valueList.push(item.replace(itemColor, resolvedColor));
|
||||
} else {
|
||||
return { colorStops: list, valid: false };
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return { colorStops: list, valid: false };
|
||||
}
|
||||
}
|
||||
// The last item must be a color, not a hint
|
||||
if (prevType !== 'color') {
|
||||
return { colorStops: list, valid: false };
|
||||
}
|
||||
return { valid: true, colorStops: valueList };
|
||||
}
|
||||
return { colorStops: list, valid: false };
|
||||
};
|
||||
|
||||
/**
|
||||
* parse CSS gradient
|
||||
* @param value - gradient value
|
||||
* @param [opt] - options
|
||||
* @returns parsed result
|
||||
*/
|
||||
export const parseGradient = (
|
||||
value: string,
|
||||
opt: Options = {}
|
||||
): Gradient | null => {
|
||||
if (isString(value)) {
|
||||
value = value.trim();
|
||||
const cacheKey: string = createCacheKey(
|
||||
{
|
||||
namespace: NAMESPACE,
|
||||
name: 'parseGradient',
|
||||
value
|
||||
},
|
||||
opt
|
||||
);
|
||||
const cachedResult = getCache(cacheKey);
|
||||
if (cachedResult instanceof CacheItem) {
|
||||
if (cachedResult.isNull) {
|
||||
return null;
|
||||
}
|
||||
return cachedResult.item as Gradient;
|
||||
}
|
||||
const type = getGradientType(value);
|
||||
const gradValue = value.replace(REG_GRAD, '').replace(/\)$/, '');
|
||||
if (type && gradValue) {
|
||||
const [lineOrColorStop = '', ...itemList] = splitValue(gradValue, {
|
||||
delimiter: ','
|
||||
});
|
||||
const isConic = IS_CONIC.test(type);
|
||||
const regDimension = isConic ? REG_DIM_CONIC : REG_DIM_NON_CONIC;
|
||||
let colorStop = '';
|
||||
if (regDimension.test(lineOrColorStop)) {
|
||||
const itemColor = lineOrColorStop.replace(regDimension, '');
|
||||
if (isColor(itemColor, { format: VAL_SPEC })) {
|
||||
const resolvedColor = resolveColor(itemColor, opt) as string;
|
||||
colorStop = lineOrColorStop.replace(itemColor, resolvedColor);
|
||||
}
|
||||
} else if (isColor(lineOrColorStop, { format: VAL_SPEC })) {
|
||||
colorStop = resolveColor(lineOrColorStop, opt) as string;
|
||||
}
|
||||
if (colorStop) {
|
||||
itemList.unshift(colorStop);
|
||||
const { colorStops, valid } = validateColorStopList(
|
||||
itemList,
|
||||
type,
|
||||
opt
|
||||
);
|
||||
if (valid) {
|
||||
const res: Gradient = {
|
||||
value,
|
||||
type,
|
||||
colorStopList: colorStops as ColorStopList
|
||||
};
|
||||
setCache(cacheKey, res);
|
||||
return res;
|
||||
}
|
||||
} else if (itemList.length > 1) {
|
||||
const { line: gradientLine, valid: validLine } = validateGradientLine(
|
||||
lineOrColorStop,
|
||||
type
|
||||
);
|
||||
const { colorStops, valid: validColorStops } = validateColorStopList(
|
||||
itemList,
|
||||
type,
|
||||
opt
|
||||
);
|
||||
if (validLine && validColorStops) {
|
||||
const res: Gradient = {
|
||||
value,
|
||||
type,
|
||||
gradientLine,
|
||||
colorStopList: colorStops as ColorStopList
|
||||
};
|
||||
setCache(cacheKey, res);
|
||||
return res;
|
||||
}
|
||||
}
|
||||
}
|
||||
setCache(cacheKey, null);
|
||||
return null;
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
/**
|
||||
* resolve CSS gradient
|
||||
* @param value - CSS value
|
||||
* @param [opt] - options
|
||||
* @returns result
|
||||
*/
|
||||
export const resolveGradient = (value: string, opt: Options = {}): string => {
|
||||
const { format = VAL_COMP } = opt;
|
||||
const gradient = parseGradient(value, opt);
|
||||
if (gradient) {
|
||||
const { type = '', gradientLine = '', colorStopList = [] } = gradient;
|
||||
if (type && Array.isArray(colorStopList) && colorStopList.length > 1) {
|
||||
if (gradientLine) {
|
||||
return `${type}(${gradientLine}, ${colorStopList.join(', ')})`;
|
||||
}
|
||||
return `${type}(${colorStopList.join(', ')})`;
|
||||
}
|
||||
}
|
||||
if (format === VAL_SPEC) {
|
||||
return '';
|
||||
}
|
||||
return 'none';
|
||||
};
|
||||
|
||||
/**
|
||||
* is CSS gradient
|
||||
* @param value - CSS value
|
||||
* @param [opt] - options
|
||||
* @returns result
|
||||
*/
|
||||
export const isGradient = (value: string, opt: Options = {}): boolean => {
|
||||
const gradient = parseGradient(value, opt);
|
||||
return gradient !== null;
|
||||
};
|
||||
236
node_modules/@asamuzakjp/css-color/src/js/css-var.ts
generated
vendored
Normal file
236
node_modules/@asamuzakjp/css-color/src/js/css-var.ts
generated
vendored
Normal file
@@ -0,0 +1,236 @@
|
||||
/**
|
||||
* css-var
|
||||
*/
|
||||
|
||||
import { CSSToken, TokenType, tokenize } from '@csstools/css-tokenizer';
|
||||
import {
|
||||
CacheItem,
|
||||
NullObject,
|
||||
createCacheKey,
|
||||
getCache,
|
||||
setCache
|
||||
} from './cache';
|
||||
import { isString } from './common';
|
||||
import { cssCalc } from './css-calc';
|
||||
import { isColor } from './util';
|
||||
import { Options } from './typedef';
|
||||
|
||||
/* constants */
|
||||
import { FN_VAR, SYN_FN_CALC, SYN_FN_VAR, VAL_SPEC } from './constant';
|
||||
const {
|
||||
CloseParen: PAREN_CLOSE,
|
||||
Comment: COMMENT,
|
||||
EOF,
|
||||
Ident: IDENT,
|
||||
Whitespace: W_SPACE
|
||||
} = TokenType;
|
||||
const NAMESPACE = 'css-var';
|
||||
|
||||
/* regexp */
|
||||
const REG_FN_CALC = new RegExp(SYN_FN_CALC);
|
||||
const REG_FN_VAR = new RegExp(SYN_FN_VAR);
|
||||
const REG_CSS_WIDE_KEYWORD = /^(?:inherit|initial|revert(?:-layer)?|unset)$/;
|
||||
|
||||
/**
|
||||
* resolve custom property
|
||||
* @param tokens - CSS tokens
|
||||
* @param [opt] - options
|
||||
* @returns result - [tokens, resolvedValue]
|
||||
*/
|
||||
export function resolveCustomProperty(
|
||||
tokens: CSSToken[],
|
||||
opt: Options = {}
|
||||
): [CSSToken[], string] {
|
||||
if (!Array.isArray(tokens)) {
|
||||
throw new TypeError(`${tokens} is not an array.`);
|
||||
}
|
||||
const { customProperty = {} } = opt;
|
||||
const items: string[] = [];
|
||||
while (tokens.length) {
|
||||
const token = tokens.shift();
|
||||
if (!token) {
|
||||
break;
|
||||
}
|
||||
if (!Array.isArray(token)) {
|
||||
throw new TypeError(`${token} is not an array.`);
|
||||
}
|
||||
const [type, value] = token as [TokenType, string];
|
||||
// end of var()
|
||||
if (type === PAREN_CLOSE) {
|
||||
break;
|
||||
}
|
||||
// nested var()
|
||||
if (value === FN_VAR) {
|
||||
const [, item] = resolveCustomProperty(tokens, opt);
|
||||
if (item) {
|
||||
items.push(item);
|
||||
}
|
||||
} else if (type === IDENT) {
|
||||
if (value.startsWith('--')) {
|
||||
let item;
|
||||
if (Object.hasOwn(customProperty, value)) {
|
||||
item = customProperty[value] as string;
|
||||
} else if (typeof customProperty.callback === 'function') {
|
||||
item = customProperty.callback(value);
|
||||
}
|
||||
if (item) {
|
||||
items.push(item);
|
||||
}
|
||||
} else if (value) {
|
||||
items.push(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
let resolveAsColor = false;
|
||||
if (items.length > 1) {
|
||||
resolveAsColor = isColor(items[items.length - 1]);
|
||||
}
|
||||
let resolvedValue = '';
|
||||
for (let item of items) {
|
||||
item = item.trim();
|
||||
if (REG_FN_VAR.test(item)) {
|
||||
// recurse resolveVar()
|
||||
const resolvedItem = resolveVar(item, opt);
|
||||
if (isString(resolvedItem)) {
|
||||
if (!resolveAsColor || isColor(resolvedItem)) {
|
||||
resolvedValue = resolvedItem;
|
||||
}
|
||||
}
|
||||
} else if (REG_FN_CALC.test(item)) {
|
||||
item = cssCalc(item, opt);
|
||||
if (!resolveAsColor || isColor(item)) {
|
||||
resolvedValue = item;
|
||||
}
|
||||
} else if (item && !REG_CSS_WIDE_KEYWORD.test(item)) {
|
||||
if (!resolveAsColor || isColor(item)) {
|
||||
resolvedValue = item;
|
||||
}
|
||||
}
|
||||
if (resolvedValue) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return [tokens, resolvedValue];
|
||||
}
|
||||
|
||||
/**
|
||||
* parse tokens
|
||||
* @param tokens - CSS tokens
|
||||
* @param [opt] - options
|
||||
* @returns parsed tokens
|
||||
*/
|
||||
export function parseTokens(
|
||||
tokens: CSSToken[],
|
||||
opt: Options = {}
|
||||
): string[] | NullObject {
|
||||
const res: string[] = [];
|
||||
while (tokens.length) {
|
||||
const token = tokens.shift();
|
||||
if (!token) break;
|
||||
const [type = '', value = ''] = token as [TokenType, string];
|
||||
if (value === FN_VAR) {
|
||||
const [, resolvedValue] = resolveCustomProperty(tokens, opt);
|
||||
if (!resolvedValue) {
|
||||
return new NullObject();
|
||||
}
|
||||
res.push(resolvedValue);
|
||||
} else {
|
||||
switch (type) {
|
||||
case PAREN_CLOSE: {
|
||||
if (res.length) {
|
||||
if (res[res.length - 1] === ' ') {
|
||||
res[res.length - 1] = value;
|
||||
} else {
|
||||
res.push(value);
|
||||
}
|
||||
} else {
|
||||
res.push(value);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case W_SPACE: {
|
||||
if (res.length) {
|
||||
const lastValue = res[res.length - 1];
|
||||
if (
|
||||
isString(lastValue) &&
|
||||
!lastValue.endsWith('(') &&
|
||||
lastValue !== ' '
|
||||
) {
|
||||
res.push(value);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
if (type !== COMMENT && type !== EOF) {
|
||||
res.push(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
/**
|
||||
* resolve CSS var()
|
||||
* @param value - CSS value including var()
|
||||
* @param [opt] - options
|
||||
* @returns resolved value
|
||||
*/
|
||||
export function resolveVar(
|
||||
value: string,
|
||||
opt: Options = {}
|
||||
): string | NullObject {
|
||||
const { format = '' } = opt;
|
||||
if (isString(value)) {
|
||||
if (!REG_FN_VAR.test(value) || format === VAL_SPEC) {
|
||||
return value;
|
||||
}
|
||||
value = value.trim();
|
||||
} else {
|
||||
throw new TypeError(`${value} is not a string.`);
|
||||
}
|
||||
const cacheKey: string = createCacheKey(
|
||||
{
|
||||
namespace: NAMESPACE,
|
||||
name: 'resolveVar',
|
||||
value
|
||||
},
|
||||
opt
|
||||
);
|
||||
const cachedResult = getCache(cacheKey);
|
||||
if (cachedResult instanceof CacheItem) {
|
||||
if (cachedResult.isNull) {
|
||||
return cachedResult as NullObject;
|
||||
}
|
||||
return cachedResult.item as string;
|
||||
}
|
||||
const tokens = tokenize({ css: value });
|
||||
const values = parseTokens(tokens, opt);
|
||||
if (Array.isArray(values)) {
|
||||
let color = values.join('');
|
||||
if (REG_FN_CALC.test(color)) {
|
||||
color = cssCalc(color, opt);
|
||||
}
|
||||
setCache(cacheKey, color);
|
||||
return color;
|
||||
} else {
|
||||
setCache(cacheKey, null);
|
||||
return new NullObject();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* CSS var()
|
||||
* @param value - CSS value including var()
|
||||
* @param [opt] - options
|
||||
* @returns resolved value
|
||||
*/
|
||||
export const cssVar = (value: string, opt: Options = {}): string => {
|
||||
const resolvedValue = resolveVar(value, opt);
|
||||
if (isString(resolvedValue)) {
|
||||
return resolvedValue;
|
||||
}
|
||||
return '';
|
||||
};
|
||||
673
node_modules/@asamuzakjp/css-color/src/js/relative-color.ts
generated
vendored
Normal file
673
node_modules/@asamuzakjp/css-color/src/js/relative-color.ts
generated
vendored
Normal file
@@ -0,0 +1,673 @@
|
||||
/**
|
||||
* relative-color
|
||||
*/
|
||||
|
||||
import { SyntaxFlag, color as colorParser } from '@csstools/css-color-parser';
|
||||
import {
|
||||
ComponentValue,
|
||||
parseComponentValue
|
||||
} from '@csstools/css-parser-algorithms';
|
||||
import { CSSToken, TokenType, tokenize } from '@csstools/css-tokenizer';
|
||||
import {
|
||||
CacheItem,
|
||||
NullObject,
|
||||
createCacheKey,
|
||||
getCache,
|
||||
setCache
|
||||
} from './cache';
|
||||
import { NAMED_COLORS, convertColorToRgb } from './color';
|
||||
import { isString, isStringOrNumber } from './common';
|
||||
import { resolveDimension, serializeCalc } from './css-calc';
|
||||
import { resolveColor } from './resolve';
|
||||
import { roundToPrecision, splitValue } from './util';
|
||||
import {
|
||||
ColorChannels,
|
||||
MatchedRegExp,
|
||||
Options,
|
||||
StringColorChannels
|
||||
} from './typedef';
|
||||
|
||||
/* constants */
|
||||
import {
|
||||
CS_LAB,
|
||||
CS_LCH,
|
||||
FN_LIGHT_DARK,
|
||||
FN_REL,
|
||||
FN_REL_CAPT,
|
||||
FN_VAR,
|
||||
NONE,
|
||||
SYN_COLOR_TYPE,
|
||||
SYN_FN_MATH_START,
|
||||
SYN_FN_VAR,
|
||||
SYN_MIX,
|
||||
VAL_SPEC
|
||||
} from './constant';
|
||||
const {
|
||||
CloseParen: PAREN_CLOSE,
|
||||
Comment: COMMENT,
|
||||
Delim: DELIM,
|
||||
Dimension: DIM,
|
||||
EOF,
|
||||
Function: FUNC,
|
||||
Ident: IDENT,
|
||||
Number: NUM,
|
||||
OpenParen: PAREN_OPEN,
|
||||
Percentage: PCT,
|
||||
Whitespace: W_SPACE
|
||||
} = TokenType;
|
||||
const { HasNoneKeywords: KEY_NONE } = SyntaxFlag;
|
||||
const NAMESPACE = 'relative-color';
|
||||
|
||||
/* constants */
|
||||
const OCT = 8;
|
||||
const DEC = 10;
|
||||
const HEX = 16;
|
||||
const MAX_PCT = 100;
|
||||
const MAX_RGB = 255;
|
||||
const COLOR_CHANNELS = new Map([
|
||||
['color', ['r', 'g', 'b', 'alpha']],
|
||||
['hsl', ['h', 's', 'l', 'alpha']],
|
||||
['hsla', ['h', 's', 'l', 'alpha']],
|
||||
['hwb', ['h', 'w', 'b', 'alpha']],
|
||||
['lab', ['l', 'a', 'b', 'alpha']],
|
||||
['lch', ['l', 'c', 'h', 'alpha']],
|
||||
['oklab', ['l', 'a', 'b', 'alpha']],
|
||||
['oklch', ['l', 'c', 'h', 'alpha']],
|
||||
['rgb', ['r', 'g', 'b', 'alpha']],
|
||||
['rgba', ['r', 'g', 'b', 'alpha']]
|
||||
]);
|
||||
|
||||
/* type definitions */
|
||||
/**
|
||||
* @type NumberOrStringColorChannels - color channel
|
||||
*/
|
||||
type NumberOrStringColorChannels = ColorChannels & StringColorChannels;
|
||||
|
||||
/* regexp */
|
||||
const REG_COLOR_CAPT = new RegExp(
|
||||
`^${FN_REL}(${SYN_COLOR_TYPE}|${SYN_MIX})\\s+`
|
||||
);
|
||||
const REG_CS_HSL = /(?:hsla?|hwb)$/;
|
||||
const REG_CS_CIE = new RegExp(`^(?:${CS_LAB}|${CS_LCH})$`);
|
||||
const REG_FN_CALC_SUM = /^(?:abs|sig?n|cos|tan)\(/;
|
||||
const REG_FN_MATH_START = new RegExp(SYN_FN_MATH_START);
|
||||
const REG_FN_REL = new RegExp(FN_REL);
|
||||
const REG_FN_REL_CAPT = new RegExp(`^${FN_REL_CAPT}`);
|
||||
const REG_FN_REL_START = new RegExp(`^${FN_REL}`);
|
||||
const REG_FN_VAR = new RegExp(SYN_FN_VAR);
|
||||
|
||||
/**
|
||||
* resolve relative color channels
|
||||
* @param value
|
||||
* - CSS color value
|
||||
* - system colors are not supported
|
||||
* @param [opt] - options
|
||||
* @param [opt.currentColor]
|
||||
* - color to use for `currentcolor` keyword
|
||||
* - if omitted, it will be treated as a missing color
|
||||
* i.e. `rgb(none none none / none)`
|
||||
* @param [opt.customProperty]
|
||||
* - custom properties
|
||||
* - pair of `--` prefixed property name and value,
|
||||
* e.g. `customProperty: { '--some-color': '#0000ff' }`
|
||||
* - and/or `callback` function to get the value of the custom property,
|
||||
* e.g. `customProperty: { callback: someDeclaration.getPropertyValue }`
|
||||
* @param [opt.dimension]
|
||||
* - dimension, convert relative length to pixels
|
||||
* - pair of unit and it's value as a number in pixels,
|
||||
* e.g. `dimension: { em: 12, rem: 16, vw: 10.26 }`
|
||||
* - and/or `callback` function to get the value as a number in pixels,
|
||||
* e.g. `dimension: { callback: convertUnitToPixel }`
|
||||
* @param [opt.format]
|
||||
* - output format, one of below
|
||||
* - `computedValue` (default), [computed value][139] of the color
|
||||
* - `specifiedValue`, [specified value][140] of the color
|
||||
* - `hex`, hex color notation, i.e. `rrggbb`
|
||||
* - `hexAlpha`, hex color notation with alpha channel, i.e. `#rrggbbaa`
|
||||
* @returns
|
||||
* - one of rgba?(), #rrggbb(aa)?, color-name, '(empty-string)',
|
||||
* color(color-space r g b / alpha), color(color-space x y z / alpha),
|
||||
* lab(l a b / alpha), lch(l c h / alpha), oklab(l a b / alpha),
|
||||
* oklch(l c h / alpha), null
|
||||
* - in `computedValue`, values are numbers, however `rgb()` values are
|
||||
* integers
|
||||
* - in `specifiedValue`, returns `empty string` for unknown and/or invalid
|
||||
* color
|
||||
* - in `hex`, returns `null` for `transparent`, and also returns `null` if
|
||||
* any of `r`, `g`, `b`, `alpha` is not a number
|
||||
* - in `hexAlpha`, returns `#00000000` for `transparent`,
|
||||
* however returns `null` if any of `r`, `g`, `b`, `alpha` is not a number
|
||||
*/
|
||||
export function resolveColorChannels(
|
||||
tokens: CSSToken[],
|
||||
opt: Options = {}
|
||||
): NumberOrStringColorChannels | NullObject {
|
||||
if (!Array.isArray(tokens)) {
|
||||
throw new TypeError(`${tokens} is not an array.`);
|
||||
}
|
||||
const { colorSpace = '', format = '' } = opt;
|
||||
const colorChannel = COLOR_CHANNELS.get(colorSpace);
|
||||
// invalid color channel
|
||||
if (!colorChannel) {
|
||||
return new NullObject();
|
||||
}
|
||||
const mathFunc = new Set();
|
||||
const channels: [
|
||||
(number | string)[],
|
||||
(number | string)[],
|
||||
(number | string)[],
|
||||
(number | string)[]
|
||||
] = [[], [], [], []];
|
||||
let i = 0;
|
||||
let nest = 0;
|
||||
let func = '';
|
||||
let precededPct = false;
|
||||
for (const token of tokens) {
|
||||
if (!Array.isArray(token)) {
|
||||
throw new TypeError(`${token} is not an array.`);
|
||||
}
|
||||
const [type, value, , , detail] = token as [
|
||||
TokenType,
|
||||
string,
|
||||
number,
|
||||
number,
|
||||
{ value: string | number } | undefined
|
||||
];
|
||||
const channel = channels[i];
|
||||
if (Array.isArray(channel)) {
|
||||
switch (type) {
|
||||
case DELIM: {
|
||||
if (func) {
|
||||
if (
|
||||
(value === '+' || value === '-') &&
|
||||
precededPct &&
|
||||
!REG_FN_CALC_SUM.test(func)
|
||||
) {
|
||||
return new NullObject();
|
||||
}
|
||||
precededPct = false;
|
||||
channel.push(value);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case DIM: {
|
||||
if (!func || !REG_FN_CALC_SUM.test(func)) {
|
||||
return new NullObject();
|
||||
}
|
||||
const resolvedValue = resolveDimension(token, opt);
|
||||
if (isString(resolvedValue)) {
|
||||
channel.push(resolvedValue);
|
||||
} else {
|
||||
channel.push(value);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case FUNC: {
|
||||
channel.push(value);
|
||||
func = value;
|
||||
nest++;
|
||||
if (REG_FN_MATH_START.test(value)) {
|
||||
mathFunc.add(nest);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case IDENT: {
|
||||
// invalid channel key
|
||||
if (!colorChannel.includes(value)) {
|
||||
return new NullObject();
|
||||
}
|
||||
channel.push(value);
|
||||
if (!func) {
|
||||
i++;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case NUM: {
|
||||
channel.push(Number(detail?.value));
|
||||
if (!func) {
|
||||
i++;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case PAREN_OPEN: {
|
||||
channel.push(value);
|
||||
nest++;
|
||||
break;
|
||||
}
|
||||
case PAREN_CLOSE: {
|
||||
if (func) {
|
||||
const lastValue = channel[channel.length - 1];
|
||||
if (lastValue === ' ') {
|
||||
channel[channel.length - 1] = value;
|
||||
} else {
|
||||
channel.push(value);
|
||||
}
|
||||
if (mathFunc.has(nest)) {
|
||||
mathFunc.delete(nest);
|
||||
}
|
||||
nest--;
|
||||
if (nest === 0) {
|
||||
func = '';
|
||||
i++;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case PCT: {
|
||||
if (!func) {
|
||||
return new NullObject();
|
||||
} else if (!REG_FN_CALC_SUM.test(func)) {
|
||||
let lastValue: string | number | undefined;
|
||||
for (let j = channel.length - 1; j >= 0; j--) {
|
||||
if (channel[j] !== ' ') {
|
||||
lastValue = channel[j];
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (lastValue === '+' || lastValue === '-') {
|
||||
return new NullObject();
|
||||
} else if (lastValue === '*' || lastValue === '/') {
|
||||
precededPct = false;
|
||||
} else {
|
||||
precededPct = true;
|
||||
}
|
||||
}
|
||||
channel.push(Number(detail?.value) / MAX_PCT);
|
||||
break;
|
||||
}
|
||||
case W_SPACE: {
|
||||
if (channel.length && func) {
|
||||
const lastValue = channel[channel.length - 1];
|
||||
if (typeof lastValue === 'number') {
|
||||
channel.push(value);
|
||||
} else if (
|
||||
isString(lastValue) &&
|
||||
!lastValue.endsWith('(') &&
|
||||
lastValue !== ' '
|
||||
) {
|
||||
channel.push(value);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
if (type !== COMMENT && type !== EOF && func) {
|
||||
channel.push(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
const channelValues = [];
|
||||
for (const channel of channels) {
|
||||
if (channel.length === 1) {
|
||||
const [resolvedValue] = channel;
|
||||
if (isStringOrNumber(resolvedValue)) {
|
||||
channelValues.push(resolvedValue);
|
||||
}
|
||||
} else if (channel.length) {
|
||||
const resolvedValue = serializeCalc(channel.join(''), {
|
||||
format
|
||||
});
|
||||
channelValues.push(resolvedValue);
|
||||
}
|
||||
}
|
||||
return channelValues as NumberOrStringColorChannels;
|
||||
}
|
||||
|
||||
/**
|
||||
* extract origin color
|
||||
* @param value - CSS color value
|
||||
* @param [opt] - options
|
||||
* @returns origin color value
|
||||
*/
|
||||
export function extractOriginColor(
|
||||
value: string,
|
||||
opt: Options = {}
|
||||
): string | NullObject {
|
||||
const { colorScheme = 'normal', currentColor = '', format = '' } = opt;
|
||||
if (isString(value)) {
|
||||
value = value.toLowerCase().trim();
|
||||
if (!value) {
|
||||
return new NullObject();
|
||||
}
|
||||
if (!REG_FN_REL_START.test(value)) {
|
||||
return value;
|
||||
}
|
||||
} else {
|
||||
return new NullObject();
|
||||
}
|
||||
const cacheKey: string = createCacheKey(
|
||||
{
|
||||
namespace: NAMESPACE,
|
||||
name: 'extractOriginColor',
|
||||
value
|
||||
},
|
||||
opt
|
||||
);
|
||||
const cachedResult = getCache(cacheKey);
|
||||
if (cachedResult instanceof CacheItem) {
|
||||
if (cachedResult.isNull) {
|
||||
return cachedResult as NullObject;
|
||||
}
|
||||
return cachedResult.item as string;
|
||||
}
|
||||
if (/currentcolor/.test(value)) {
|
||||
if (currentColor) {
|
||||
value = value.replace(/currentcolor/g, currentColor);
|
||||
} else {
|
||||
setCache(cacheKey, null);
|
||||
return new NullObject();
|
||||
}
|
||||
}
|
||||
let colorSpace = '';
|
||||
if (REG_FN_REL_CAPT.test(value)) {
|
||||
[, colorSpace] = value.match(REG_FN_REL_CAPT) as MatchedRegExp;
|
||||
}
|
||||
opt.colorSpace = colorSpace;
|
||||
if (value.includes(FN_LIGHT_DARK)) {
|
||||
const colorParts = value
|
||||
.replace(new RegExp(`^${colorSpace}\\(`), '')
|
||||
.replace(/\)$/, '');
|
||||
const [, originColor = ''] = splitValue(colorParts);
|
||||
const specifiedOriginColor = resolveColor(originColor, {
|
||||
colorScheme,
|
||||
format: VAL_SPEC
|
||||
}) as string;
|
||||
if (specifiedOriginColor === '') {
|
||||
setCache(cacheKey, null);
|
||||
return new NullObject();
|
||||
}
|
||||
if (format === VAL_SPEC) {
|
||||
value = value.replace(originColor, specifiedOriginColor);
|
||||
} else {
|
||||
const resolvedOriginColor = resolveColor(specifiedOriginColor, opt);
|
||||
if (isString(resolvedOriginColor)) {
|
||||
value = value.replace(originColor, resolvedOriginColor);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (REG_COLOR_CAPT.test(value)) {
|
||||
const [, originColor] = value.match(REG_COLOR_CAPT) as MatchedRegExp;
|
||||
const [, restValue] = value.split(originColor) as MatchedRegExp;
|
||||
if (/^[a-z]+$/.test(originColor)) {
|
||||
if (
|
||||
!/^transparent$/.test(originColor) &&
|
||||
!Object.hasOwn(NAMED_COLORS, originColor)
|
||||
) {
|
||||
setCache(cacheKey, null);
|
||||
return new NullObject();
|
||||
}
|
||||
} else if (format === VAL_SPEC) {
|
||||
const resolvedOriginColor = resolveColor(originColor, opt);
|
||||
if (isString(resolvedOriginColor)) {
|
||||
value = value.replace(originColor, resolvedOriginColor);
|
||||
}
|
||||
}
|
||||
if (format === VAL_SPEC) {
|
||||
const tokens = tokenize({ css: restValue });
|
||||
const channelValues = resolveColorChannels(tokens, opt);
|
||||
if (channelValues instanceof NullObject) {
|
||||
setCache(cacheKey, null);
|
||||
return channelValues;
|
||||
}
|
||||
const [v1, v2, v3, v4] = channelValues;
|
||||
let channelValue = '';
|
||||
if (isStringOrNumber(v4)) {
|
||||
channelValue = ` ${v1} ${v2} ${v3} / ${v4})`;
|
||||
} else {
|
||||
channelValue = ` ${channelValues.join(' ')})`;
|
||||
}
|
||||
if (restValue !== channelValue) {
|
||||
value = value.replace(restValue, channelValue);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// nested relative color
|
||||
const [, restValue] = value.split(REG_FN_REL_START) as MatchedRegExp;
|
||||
const tokens = tokenize({ css: restValue });
|
||||
const originColor: string[] = [];
|
||||
let nest = 0;
|
||||
let tokenIndex = 0;
|
||||
for (const [type, tokenValue] of tokens) {
|
||||
tokenIndex++;
|
||||
switch (type) {
|
||||
case FUNC:
|
||||
case PAREN_OPEN: {
|
||||
originColor.push(tokenValue);
|
||||
nest++;
|
||||
break;
|
||||
}
|
||||
case PAREN_CLOSE: {
|
||||
const lastValue = originColor[originColor.length - 1];
|
||||
if (lastValue === ' ') {
|
||||
originColor[originColor.length - 1] = tokenValue;
|
||||
} else if (isString(lastValue)) {
|
||||
originColor.push(tokenValue);
|
||||
}
|
||||
nest--;
|
||||
break;
|
||||
}
|
||||
case W_SPACE: {
|
||||
const lastValue = originColor[originColor.length - 1];
|
||||
if (
|
||||
isString(lastValue) &&
|
||||
!lastValue.endsWith('(') &&
|
||||
lastValue !== ' '
|
||||
) {
|
||||
originColor.push(tokenValue);
|
||||
}
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
if (type !== COMMENT && type !== EOF) {
|
||||
originColor.push(tokenValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (nest === 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
const resolvedOriginColor = resolveRelativeColor(
|
||||
originColor.join('').trim(),
|
||||
opt
|
||||
);
|
||||
if (resolvedOriginColor instanceof NullObject) {
|
||||
setCache(cacheKey, null);
|
||||
return resolvedOriginColor;
|
||||
}
|
||||
const channelValues = resolveColorChannels(tokens.slice(tokenIndex), opt);
|
||||
if (channelValues instanceof NullObject) {
|
||||
setCache(cacheKey, null);
|
||||
return channelValues;
|
||||
}
|
||||
const [v1, v2, v3, v4] = channelValues;
|
||||
let channelValue = '';
|
||||
if (isStringOrNumber(v4)) {
|
||||
channelValue = ` ${v1} ${v2} ${v3} / ${v4})`;
|
||||
} else {
|
||||
channelValue = ` ${channelValues.join(' ')})`;
|
||||
}
|
||||
value = value.replace(restValue, `${resolvedOriginColor}${channelValue}`);
|
||||
}
|
||||
setCache(cacheKey, value);
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* resolve relative color
|
||||
* @param value - CSS relative color value
|
||||
* @param [opt] - options
|
||||
* @returns resolved value
|
||||
*/
|
||||
export function resolveRelativeColor(
|
||||
value: string,
|
||||
opt: Options = {}
|
||||
): string | NullObject {
|
||||
const { format = '' } = opt;
|
||||
if (isString(value)) {
|
||||
if (REG_FN_VAR.test(value)) {
|
||||
// var() must be resolved before resolveRelativeColor()
|
||||
if (format !== VAL_SPEC) {
|
||||
throw new SyntaxError(`Unexpected token ${FN_VAR} found.`);
|
||||
}
|
||||
return value;
|
||||
} else if (!REG_FN_REL.test(value)) {
|
||||
return value;
|
||||
}
|
||||
value = value.toLowerCase().trim();
|
||||
} else {
|
||||
throw new TypeError(`${value} is not a string.`);
|
||||
}
|
||||
const cacheKey: string = createCacheKey(
|
||||
{
|
||||
namespace: NAMESPACE,
|
||||
name: 'resolveRelativeColor',
|
||||
value
|
||||
},
|
||||
opt
|
||||
);
|
||||
const cachedResult = getCache(cacheKey);
|
||||
if (cachedResult instanceof CacheItem) {
|
||||
if (cachedResult.isNull) {
|
||||
return cachedResult as NullObject;
|
||||
}
|
||||
return cachedResult.item as string;
|
||||
}
|
||||
const originColor = extractOriginColor(value, opt);
|
||||
if (originColor instanceof NullObject) {
|
||||
setCache(cacheKey, null);
|
||||
return originColor;
|
||||
}
|
||||
value = originColor;
|
||||
if (format === VAL_SPEC) {
|
||||
if (value.startsWith('rgba(')) {
|
||||
value = value.replace('rgba(', 'rgb(');
|
||||
} else if (value.startsWith('hsla(')) {
|
||||
value = value.replace('hsla(', 'hsl(');
|
||||
}
|
||||
return value;
|
||||
}
|
||||
const tokens = tokenize({ css: value });
|
||||
const components = parseComponentValue(tokens) as ComponentValue;
|
||||
const parsedComponents = colorParser(components);
|
||||
if (!parsedComponents) {
|
||||
setCache(cacheKey, null);
|
||||
return new NullObject();
|
||||
}
|
||||
const {
|
||||
alpha: alphaComponent,
|
||||
channels: channelsComponent,
|
||||
colorNotation,
|
||||
syntaxFlags
|
||||
} = parsedComponents;
|
||||
let alpha: number | string;
|
||||
if (Number.isNaN(Number(alphaComponent))) {
|
||||
if (syntaxFlags instanceof Set && syntaxFlags.has(KEY_NONE)) {
|
||||
alpha = NONE;
|
||||
} else {
|
||||
alpha = 0;
|
||||
}
|
||||
} else {
|
||||
alpha = roundToPrecision(Number(alphaComponent), OCT);
|
||||
}
|
||||
let v1: number | string;
|
||||
let v2: number | string;
|
||||
let v3: number | string;
|
||||
[v1, v2, v3] = channelsComponent;
|
||||
let resolvedValue;
|
||||
if (REG_CS_CIE.test(colorNotation)) {
|
||||
const hasNone = syntaxFlags instanceof Set && syntaxFlags.has(KEY_NONE);
|
||||
if (Number.isNaN(v1)) {
|
||||
if (hasNone) {
|
||||
v1 = NONE;
|
||||
} else {
|
||||
v1 = 0;
|
||||
}
|
||||
} else {
|
||||
v1 = roundToPrecision(v1, HEX);
|
||||
}
|
||||
if (Number.isNaN(v2)) {
|
||||
if (hasNone) {
|
||||
v2 = NONE;
|
||||
} else {
|
||||
v2 = 0;
|
||||
}
|
||||
} else {
|
||||
v2 = roundToPrecision(v2, HEX);
|
||||
}
|
||||
if (Number.isNaN(v3)) {
|
||||
if (hasNone) {
|
||||
v3 = NONE;
|
||||
} else {
|
||||
v3 = 0;
|
||||
}
|
||||
} else {
|
||||
v3 = roundToPrecision(v3, HEX);
|
||||
}
|
||||
if (alpha === 1) {
|
||||
resolvedValue = `${colorNotation}(${v1} ${v2} ${v3})`;
|
||||
} else {
|
||||
resolvedValue = `${colorNotation}(${v1} ${v2} ${v3} / ${alpha})`;
|
||||
}
|
||||
} else if (REG_CS_HSL.test(colorNotation)) {
|
||||
if (Number.isNaN(v1)) {
|
||||
v1 = 0;
|
||||
}
|
||||
if (Number.isNaN(v2)) {
|
||||
v2 = 0;
|
||||
}
|
||||
if (Number.isNaN(v3)) {
|
||||
v3 = 0;
|
||||
}
|
||||
let [r, g, b] = convertColorToRgb(
|
||||
`${colorNotation}(${v1} ${v2} ${v3} / ${alpha})`
|
||||
) as ColorChannels;
|
||||
r = roundToPrecision(r / MAX_RGB, DEC);
|
||||
g = roundToPrecision(g / MAX_RGB, DEC);
|
||||
b = roundToPrecision(b / MAX_RGB, DEC);
|
||||
if (alpha === 1) {
|
||||
resolvedValue = `color(srgb ${r} ${g} ${b})`;
|
||||
} else {
|
||||
resolvedValue = `color(srgb ${r} ${g} ${b} / ${alpha})`;
|
||||
}
|
||||
} else {
|
||||
const cs = colorNotation === 'rgb' ? 'srgb' : colorNotation;
|
||||
const hasNone = syntaxFlags instanceof Set && syntaxFlags.has(KEY_NONE);
|
||||
if (Number.isNaN(v1)) {
|
||||
if (hasNone) {
|
||||
v1 = NONE;
|
||||
} else {
|
||||
v1 = 0;
|
||||
}
|
||||
} else {
|
||||
v1 = roundToPrecision(v1, DEC);
|
||||
}
|
||||
if (Number.isNaN(v2)) {
|
||||
if (hasNone) {
|
||||
v2 = NONE;
|
||||
} else {
|
||||
v2 = 0;
|
||||
}
|
||||
} else {
|
||||
v2 = roundToPrecision(v2, DEC);
|
||||
}
|
||||
if (Number.isNaN(v3)) {
|
||||
if (hasNone) {
|
||||
v3 = NONE;
|
||||
} else {
|
||||
v3 = 0;
|
||||
}
|
||||
} else {
|
||||
v3 = roundToPrecision(v3, DEC);
|
||||
}
|
||||
if (alpha === 1) {
|
||||
resolvedValue = `color(${cs} ${v1} ${v2} ${v3})`;
|
||||
} else {
|
||||
resolvedValue = `color(${cs} ${v1} ${v2} ${v3} / ${alpha})`;
|
||||
}
|
||||
}
|
||||
setCache(cacheKey, resolvedValue);
|
||||
return resolvedValue;
|
||||
}
|
||||
322
node_modules/@asamuzakjp/css-color/src/js/resolve.ts
generated
vendored
Normal file
322
node_modules/@asamuzakjp/css-color/src/js/resolve.ts
generated
vendored
Normal file
@@ -0,0 +1,322 @@
|
||||
/**
|
||||
* resolve
|
||||
*/
|
||||
|
||||
import {
|
||||
CacheItem,
|
||||
NullObject,
|
||||
createCacheKey,
|
||||
getCache,
|
||||
setCache
|
||||
} from './cache';
|
||||
import {
|
||||
convertRgbToHex,
|
||||
resolveColorFunc,
|
||||
resolveColorMix,
|
||||
resolveColorValue
|
||||
} from './color';
|
||||
import { isString } from './common';
|
||||
import { cssCalc } from './css-calc';
|
||||
import { resolveVar } from './css-var';
|
||||
import { resolveRelativeColor } from './relative-color';
|
||||
import { splitValue } from './util';
|
||||
import {
|
||||
ComputedColorChannels,
|
||||
Options,
|
||||
SpecifiedColorChannels
|
||||
} from './typedef';
|
||||
|
||||
/* constants */
|
||||
import {
|
||||
FN_COLOR,
|
||||
FN_MIX,
|
||||
SYN_FN_CALC,
|
||||
SYN_FN_LIGHT_DARK,
|
||||
SYN_FN_REL,
|
||||
SYN_FN_VAR,
|
||||
VAL_COMP,
|
||||
VAL_SPEC
|
||||
} from './constant';
|
||||
const NAMESPACE = 'resolve';
|
||||
const RGB_TRANSPARENT = 'rgba(0, 0, 0, 0)';
|
||||
|
||||
/* regexp */
|
||||
const REG_FN_CALC = new RegExp(SYN_FN_CALC);
|
||||
const REG_FN_LIGHT_DARK = new RegExp(SYN_FN_LIGHT_DARK);
|
||||
const REG_FN_REL = new RegExp(SYN_FN_REL);
|
||||
const REG_FN_VAR = new RegExp(SYN_FN_VAR);
|
||||
|
||||
/**
|
||||
* resolve color
|
||||
* @param value - CSS color value
|
||||
* @param [opt] - options
|
||||
* @returns resolved color
|
||||
*/
|
||||
export const resolveColor = (
|
||||
value: string,
|
||||
opt: Options = {}
|
||||
): string | NullObject => {
|
||||
if (!isString(value)) {
|
||||
throw new TypeError(`${value} is not a string.`);
|
||||
}
|
||||
value = value.trim();
|
||||
const {
|
||||
colorScheme = 'normal',
|
||||
currentColor = '',
|
||||
format = VAL_COMP,
|
||||
nullable = false
|
||||
} = opt;
|
||||
const cacheKey: string = createCacheKey(
|
||||
{ namespace: NAMESPACE, name: 'resolve', value },
|
||||
opt
|
||||
);
|
||||
const cachedResult = getCache(cacheKey);
|
||||
if (cachedResult instanceof CacheItem) {
|
||||
if (cachedResult.isNull) {
|
||||
return cachedResult as NullObject;
|
||||
}
|
||||
return cachedResult.item as string;
|
||||
}
|
||||
// 1. var() resolution
|
||||
if (REG_FN_VAR.test(value)) {
|
||||
if (format === VAL_SPEC) {
|
||||
setCache(cacheKey, value);
|
||||
return value;
|
||||
}
|
||||
const resolvedVar = resolveVar(value, opt);
|
||||
if (resolvedVar instanceof NullObject) {
|
||||
const res =
|
||||
format === 'hex' || format === 'hexAlpha' || nullable
|
||||
? resolvedVar
|
||||
: RGB_TRANSPARENT;
|
||||
setCache(cacheKey, res);
|
||||
return res;
|
||||
}
|
||||
value = resolvedVar;
|
||||
}
|
||||
if (opt.format !== format) {
|
||||
opt.format = format;
|
||||
}
|
||||
value = value.toLowerCase();
|
||||
// 2. light-dark() resolution
|
||||
if (REG_FN_LIGHT_DARK.test(value) && value.endsWith(')')) {
|
||||
const colorParts = value.replace(REG_FN_LIGHT_DARK, '').replace(/\)$/, '');
|
||||
const [light = '', dark = ''] = splitValue(colorParts, { delimiter: ',' });
|
||||
if (light && dark) {
|
||||
if (format === VAL_SPEC) {
|
||||
const lightColor = resolveColor(light, opt);
|
||||
const darkColor = resolveColor(dark, opt);
|
||||
const res =
|
||||
lightColor && darkColor
|
||||
? `light-dark(${lightColor}, ${darkColor})`
|
||||
: '';
|
||||
setCache(cacheKey, res);
|
||||
return res;
|
||||
}
|
||||
const chosen = colorScheme === 'dark' ? dark : light;
|
||||
const resolved = resolveColor(chosen, opt);
|
||||
const res =
|
||||
resolved instanceof NullObject && !nullable
|
||||
? RGB_TRANSPARENT
|
||||
: resolved;
|
||||
setCache(cacheKey, res);
|
||||
return res;
|
||||
}
|
||||
// fallback for invalid light-dark
|
||||
const invalidRes =
|
||||
format === VAL_SPEC
|
||||
? ''
|
||||
: format === 'hex' || format === 'hexAlpha'
|
||||
? new NullObject()
|
||||
: RGB_TRANSPARENT;
|
||||
setCache(cacheKey, invalidRes);
|
||||
return invalidRes;
|
||||
}
|
||||
// 3. Relative Color resolution
|
||||
if (REG_FN_REL.test(value)) {
|
||||
const resolvedRel = resolveRelativeColor(value, opt);
|
||||
if (format === VAL_COMP) {
|
||||
const res =
|
||||
resolvedRel instanceof NullObject && !nullable
|
||||
? RGB_TRANSPARENT
|
||||
: resolvedRel;
|
||||
setCache(cacheKey, res);
|
||||
return res;
|
||||
}
|
||||
if (format === VAL_SPEC) {
|
||||
const res = resolvedRel instanceof NullObject ? '' : resolvedRel;
|
||||
setCache(cacheKey, res);
|
||||
return res;
|
||||
}
|
||||
value = resolvedRel instanceof NullObject ? '' : resolvedRel;
|
||||
}
|
||||
// 4. calc() resolution
|
||||
if (REG_FN_CALC.test(value)) {
|
||||
value = cssCalc(value, opt);
|
||||
}
|
||||
// 5. Keyword & Color-space resolution
|
||||
let cs = '';
|
||||
let r = NaN;
|
||||
let g = NaN;
|
||||
let b = NaN;
|
||||
let alpha = NaN;
|
||||
if (value === 'transparent') {
|
||||
let res: string | NullObject;
|
||||
switch (format) {
|
||||
case VAL_SPEC: {
|
||||
res = value;
|
||||
break;
|
||||
}
|
||||
case 'hex': {
|
||||
res = new NullObject();
|
||||
break;
|
||||
}
|
||||
case 'hexAlpha': {
|
||||
res = '#00000000';
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
res = RGB_TRANSPARENT;
|
||||
}
|
||||
}
|
||||
setCache(cacheKey, res);
|
||||
return res;
|
||||
}
|
||||
if (value === 'currentcolor') {
|
||||
if (format === VAL_SPEC) {
|
||||
setCache(cacheKey, value);
|
||||
return value;
|
||||
}
|
||||
if (currentColor) {
|
||||
let resolvedCurrent;
|
||||
if (currentColor.startsWith(FN_MIX)) {
|
||||
resolvedCurrent = resolveColorMix(currentColor, opt);
|
||||
} else if (currentColor.startsWith(FN_COLOR)) {
|
||||
resolvedCurrent = resolveColorFunc(currentColor, opt);
|
||||
} else {
|
||||
resolvedCurrent = resolveColorValue(currentColor, opt);
|
||||
}
|
||||
if (resolvedCurrent instanceof NullObject) {
|
||||
setCache(cacheKey, resolvedCurrent);
|
||||
return resolvedCurrent;
|
||||
}
|
||||
[cs, r, g, b, alpha] = resolvedCurrent as ComputedColorChannels;
|
||||
} else {
|
||||
// value is handled below if not VAL_COMP
|
||||
const res = format === VAL_COMP ? RGB_TRANSPARENT : value;
|
||||
if (format === VAL_COMP) {
|
||||
setCache(cacheKey, res);
|
||||
return res;
|
||||
}
|
||||
}
|
||||
} else if (format === VAL_SPEC) {
|
||||
let res = '';
|
||||
if (value.startsWith(FN_MIX)) {
|
||||
res = resolveColorMix(value, opt) as string;
|
||||
} else if (value.startsWith(FN_COLOR)) {
|
||||
const [scs, rr, gg, bb, aa] = resolveColorFunc(
|
||||
value,
|
||||
opt
|
||||
) as SpecifiedColorChannels;
|
||||
res =
|
||||
aa === 1
|
||||
? `color(${scs} ${rr} ${gg} ${bb})`
|
||||
: `color(${scs} ${rr} ${gg} ${bb} / ${aa})`;
|
||||
} else {
|
||||
const rgb = resolveColorValue(value, opt);
|
||||
if (isString(rgb)) {
|
||||
res = rgb;
|
||||
} else {
|
||||
const [scs, rr, gg, bb, aa] = rgb as SpecifiedColorChannels;
|
||||
if (scs === 'rgb') {
|
||||
res =
|
||||
aa === 1
|
||||
? `${scs}(${rr}, ${gg}, ${bb})`
|
||||
: `${scs}a(${rr}, ${gg}, ${bb}, ${aa})`;
|
||||
} else {
|
||||
res =
|
||||
aa === 1
|
||||
? `${scs}(${rr} ${gg} ${bb})`
|
||||
: `${scs}(${rr} ${gg} ${bb} / ${aa})`;
|
||||
}
|
||||
}
|
||||
}
|
||||
setCache(cacheKey, res);
|
||||
return res;
|
||||
} else if (value.startsWith(FN_MIX)) {
|
||||
if (currentColor) {
|
||||
value = value.replace(/currentcolor/g, currentColor);
|
||||
}
|
||||
value = value.replace(/transparent/g, RGB_TRANSPARENT);
|
||||
const resolvedMix = resolveColorMix(value, opt);
|
||||
if (resolvedMix instanceof NullObject) {
|
||||
setCache(cacheKey, resolvedMix);
|
||||
return resolvedMix;
|
||||
}
|
||||
[cs, r, g, b, alpha] = resolvedMix as ComputedColorChannels;
|
||||
} else if (value.startsWith(FN_COLOR)) {
|
||||
const resolvedFunc = resolveColorFunc(value, opt);
|
||||
if (resolvedFunc instanceof NullObject) {
|
||||
setCache(cacheKey, resolvedFunc);
|
||||
return resolvedFunc;
|
||||
}
|
||||
[cs, r, g, b, alpha] = resolvedFunc as ComputedColorChannels;
|
||||
} else if (value) {
|
||||
const resolvedVal = resolveColorValue(value, opt);
|
||||
if (resolvedVal instanceof NullObject) {
|
||||
setCache(cacheKey, resolvedVal);
|
||||
return resolvedVal;
|
||||
}
|
||||
[cs, r, g, b, alpha] = resolvedVal as ComputedColorChannels;
|
||||
}
|
||||
// 6. Format Finalization
|
||||
let finalRes: string | NullObject = '';
|
||||
switch (format) {
|
||||
case 'hex':
|
||||
case 'hexAlpha': {
|
||||
if (
|
||||
Number.isNaN(r) ||
|
||||
Number.isNaN(g) ||
|
||||
Number.isNaN(b) ||
|
||||
Number.isNaN(alpha) ||
|
||||
(format === 'hex' && alpha === 0)
|
||||
) {
|
||||
finalRes = new NullObject();
|
||||
} else {
|
||||
finalRes = convertRgbToHex([r, g, b, format === 'hex' ? 1 : alpha]);
|
||||
}
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
if (cs === 'rgb') {
|
||||
finalRes =
|
||||
alpha === 1
|
||||
? `${cs}(${r}, ${g}, ${b})`
|
||||
: `${cs}a(${r}, ${g}, ${b}, ${alpha})`;
|
||||
} else if (['lab', 'lch', 'oklab', 'oklch'].includes(cs)) {
|
||||
finalRes =
|
||||
alpha === 1
|
||||
? `${cs}(${r} ${g} ${b})`
|
||||
: `${cs}(${r} ${g} ${b} / ${alpha})`;
|
||||
} else {
|
||||
finalRes =
|
||||
alpha === 1
|
||||
? `color(${cs} ${r} ${g} ${b})`
|
||||
: `color(${cs} ${r} ${g} ${b} / ${alpha})`;
|
||||
}
|
||||
}
|
||||
}
|
||||
setCache(cacheKey, finalRes);
|
||||
return finalRes;
|
||||
};
|
||||
|
||||
/**
|
||||
* resolve CSS color
|
||||
* @param value - CSS color value. system colors are not supported
|
||||
* @param [opt] - options
|
||||
*/
|
||||
export const resolve = (value: string, opt: Options = {}): string | null => {
|
||||
opt.nullable = false;
|
||||
const resolvedValue = resolveColor(value, opt);
|
||||
return resolvedValue instanceof NullObject ? null : (resolvedValue as string);
|
||||
};
|
||||
88
node_modules/@asamuzakjp/css-color/src/js/typedef.ts
generated
vendored
Normal file
88
node_modules/@asamuzakjp/css-color/src/js/typedef.ts
generated
vendored
Normal file
@@ -0,0 +1,88 @@
|
||||
/**
|
||||
* typedef
|
||||
*/
|
||||
|
||||
/* type definitions */
|
||||
/**
|
||||
* @typedef Options - options
|
||||
* @property [alpha] - enable alpha
|
||||
* @property [colorSpace] - color space
|
||||
* @property [currentColor] - color for currentcolor
|
||||
* @property [customProperty] - custom properties
|
||||
* @property [d50] - white point in d50
|
||||
* @property [dimension] - dimension
|
||||
* @property [format] - output format
|
||||
* @property [key] - key
|
||||
*/
|
||||
export interface Options {
|
||||
alpha?: boolean;
|
||||
colorScheme?: string;
|
||||
colorSpace?: string;
|
||||
currentColor?: string;
|
||||
customProperty?: Record<string, string | ((K: string) => string)>;
|
||||
d50?: boolean;
|
||||
delimiter?: string | string[];
|
||||
dimension?: Record<string, number | ((K: string) => number)>;
|
||||
format?: string;
|
||||
nullable?: boolean;
|
||||
preserveComment?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* @type ColorChannels - color channels
|
||||
*/
|
||||
export type ColorChannels = [x: number, y: number, z: number, alpha: number];
|
||||
|
||||
/**
|
||||
* @type StringColorChannels - color channels
|
||||
*/
|
||||
export type StringColorChannels = [
|
||||
x: string,
|
||||
y: string,
|
||||
z: string,
|
||||
alpha: string | undefined
|
||||
];
|
||||
|
||||
/**
|
||||
* @type StringColorSpacedChannels - specified value
|
||||
*/
|
||||
export type StringColorSpacedChannels = [
|
||||
cs: string,
|
||||
x: string,
|
||||
y: string,
|
||||
z: string,
|
||||
alpha: string | undefined
|
||||
];
|
||||
|
||||
/**
|
||||
* @type ComputedColorChannels - computed value
|
||||
*/
|
||||
export type ComputedColorChannels = [
|
||||
cs: string,
|
||||
x: number,
|
||||
y: number,
|
||||
z: number,
|
||||
alpha: number
|
||||
];
|
||||
|
||||
/**
|
||||
* @type SpecifiedColorChannels - specified value
|
||||
*/
|
||||
export type SpecifiedColorChannels = [
|
||||
cs: string,
|
||||
x: number | string,
|
||||
y: number | string,
|
||||
z: number | string,
|
||||
alpha: number | string
|
||||
];
|
||||
|
||||
/**
|
||||
* @type MatchedRegExp - matched regexp array
|
||||
*/
|
||||
export type MatchedRegExp = [
|
||||
match: string,
|
||||
gr1: string,
|
||||
gr2: string,
|
||||
gr3: string,
|
||||
gr4: string
|
||||
];
|
||||
423
node_modules/@asamuzakjp/css-color/src/js/util.ts
generated
vendored
Normal file
423
node_modules/@asamuzakjp/css-color/src/js/util.ts
generated
vendored
Normal file
@@ -0,0 +1,423 @@
|
||||
/**
|
||||
* util
|
||||
*/
|
||||
|
||||
import { TokenType, tokenize } from '@csstools/css-tokenizer';
|
||||
import { CacheItem, createCacheKey, getCache, setCache } from './cache';
|
||||
import { isString } from './common';
|
||||
import { resolveColor } from './resolve';
|
||||
import { Options } from './typedef';
|
||||
|
||||
/* constants */
|
||||
import { NAMED_COLORS } from './color';
|
||||
import { SYN_COLOR_TYPE, SYN_MIX, VAL_SPEC } from './constant';
|
||||
const {
|
||||
CloseParen: PAREN_CLOSE,
|
||||
Comma: COMMA,
|
||||
Comment: COMMENT,
|
||||
Delim: DELIM,
|
||||
EOF,
|
||||
Function: FUNC,
|
||||
OpenParen: PAREN_OPEN,
|
||||
Whitespace: W_SPACE
|
||||
} = TokenType;
|
||||
const NAMESPACE = 'util';
|
||||
|
||||
/* numeric constants */
|
||||
const DEC = 10;
|
||||
const HEX = 16;
|
||||
const DEG = 360;
|
||||
const DEG_HALF = 180;
|
||||
|
||||
/* regexp */
|
||||
const REG_COLOR = new RegExp(`^(?:${SYN_COLOR_TYPE})$`);
|
||||
const REG_DIMENSION = /^([+-]?(?:\d+(?:\.\d+)?|\.\d+)(?:e[+-]?\d+)?)([a-z]*)$/i;
|
||||
const REG_FN_COLOR =
|
||||
/^(?:(?:ok)?l(?:ab|ch)|color(?:-mix)?|hsla?|hwb|rgba?|var)\(/;
|
||||
const REG_MIX = new RegExp(SYN_MIX);
|
||||
const REG_DASHED_IDENT = /--[\w-]+/g;
|
||||
const REG_COMMA = /^,$/;
|
||||
const REG_SLASH = /^\/$/;
|
||||
const REG_WHITESPACE = /^\s+$/;
|
||||
|
||||
/**
|
||||
* split value
|
||||
* NOTE: comments are stripped, it can be preserved if, in the options param,
|
||||
* `delimiter` is either ',' or '/' and with `preserveComment` set to `true`
|
||||
* @param value - CSS value
|
||||
* @param [opt] - options
|
||||
* @returns array of values
|
||||
*/
|
||||
export const splitValue = (value: string, opt: Options = {}): string[] => {
|
||||
if (!isString(value)) {
|
||||
throw new TypeError(`${value} is not a string.`);
|
||||
}
|
||||
const strValue = value.trim();
|
||||
const { delimiter = ' ', preserveComment = false } = opt;
|
||||
const cacheKey: string = createCacheKey(
|
||||
{
|
||||
namespace: NAMESPACE,
|
||||
name: 'splitValue',
|
||||
value: strValue
|
||||
},
|
||||
{ delimiter, preserveComment }
|
||||
);
|
||||
const cachedResult = getCache(cacheKey);
|
||||
if (cachedResult instanceof CacheItem) {
|
||||
return cachedResult.item as string[];
|
||||
}
|
||||
let regDelimiter;
|
||||
switch (delimiter) {
|
||||
case ',': {
|
||||
regDelimiter = REG_COMMA;
|
||||
break;
|
||||
}
|
||||
case '/': {
|
||||
regDelimiter = REG_SLASH;
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
regDelimiter = REG_WHITESPACE;
|
||||
}
|
||||
}
|
||||
const tokens = tokenize({ css: strValue });
|
||||
let nest = 0;
|
||||
let currentStr = '';
|
||||
const res: string[] = [];
|
||||
for (const [type, val] of tokens) {
|
||||
switch (type) {
|
||||
case COMMA:
|
||||
case DELIM: {
|
||||
if (nest === 0 && regDelimiter.test(val)) {
|
||||
res.push(currentStr.trim());
|
||||
currentStr = '';
|
||||
} else {
|
||||
currentStr += val;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case COMMENT: {
|
||||
if (preserveComment && (delimiter === ',' || delimiter === '/')) {
|
||||
currentStr += val;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case FUNC:
|
||||
case PAREN_OPEN: {
|
||||
currentStr += val;
|
||||
nest++;
|
||||
break;
|
||||
}
|
||||
case PAREN_CLOSE: {
|
||||
currentStr += val;
|
||||
nest--;
|
||||
break;
|
||||
}
|
||||
case W_SPACE: {
|
||||
if (regDelimiter.test(val)) {
|
||||
if (nest === 0) {
|
||||
if (currentStr) {
|
||||
res.push(currentStr.trim());
|
||||
currentStr = '';
|
||||
}
|
||||
} else {
|
||||
currentStr += ' ';
|
||||
}
|
||||
} else if (!currentStr.endsWith(' ')) {
|
||||
currentStr += ' ';
|
||||
}
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
if (type === EOF) {
|
||||
res.push(currentStr.trim());
|
||||
currentStr = '';
|
||||
} else {
|
||||
currentStr += val;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
setCache(cacheKey, res);
|
||||
return res;
|
||||
};
|
||||
|
||||
/**
|
||||
* extract dashed-ident tokens
|
||||
* @param value - CSS value
|
||||
* @returns array of dashed-ident tokens
|
||||
*/
|
||||
export const extractDashedIdent = (value: string): string[] => {
|
||||
if (!isString(value)) {
|
||||
throw new TypeError(`${value} is not a string.`);
|
||||
}
|
||||
const strValue = value.trim();
|
||||
const cacheKey: string = createCacheKey({
|
||||
namespace: NAMESPACE,
|
||||
name: 'extractDashedIdent',
|
||||
value: strValue
|
||||
});
|
||||
const cachedResult = getCache(cacheKey);
|
||||
if (cachedResult instanceof CacheItem) {
|
||||
return cachedResult.item as string[];
|
||||
}
|
||||
const matches = strValue.match(REG_DASHED_IDENT);
|
||||
const res = matches ? [...new Set(matches)] : [];
|
||||
setCache(cacheKey, res);
|
||||
return res;
|
||||
};
|
||||
|
||||
/**
|
||||
* is color
|
||||
* @param value - CSS value
|
||||
* @param [opt] - options
|
||||
* @returns result
|
||||
*/
|
||||
export const isColor = (value: unknown, opt: Options = {}): boolean => {
|
||||
if (!isString(value)) {
|
||||
return false;
|
||||
}
|
||||
const str = value.toLowerCase().trim();
|
||||
if (!str) {
|
||||
return false;
|
||||
}
|
||||
if (/^[a-z]+$/.test(str)) {
|
||||
return (
|
||||
str === 'currentcolor' ||
|
||||
str === 'transparent' ||
|
||||
Object.hasOwn(NAMED_COLORS, str)
|
||||
);
|
||||
}
|
||||
if (REG_COLOR.test(str) || REG_MIX.test(str)) {
|
||||
return true;
|
||||
}
|
||||
if (REG_FN_COLOR.test(str)) {
|
||||
const colorOpt = { ...opt, nullable: true };
|
||||
if (!colorOpt.format) colorOpt.format = VAL_SPEC;
|
||||
return !!resolveColor(str, colorOpt);
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
/**
|
||||
* round to specified precision
|
||||
* @param value - numeric value
|
||||
* @param bit - minimum bits
|
||||
* @returns rounded value
|
||||
*/
|
||||
export const roundToPrecision = (value: number, bit: number = 0): number => {
|
||||
if (!Number.isFinite(value)) {
|
||||
throw new TypeError(`${value} is not a finite number.`);
|
||||
}
|
||||
if (!Number.isFinite(bit)) {
|
||||
throw new TypeError(`${bit} is not a finite number.`);
|
||||
}
|
||||
if (bit < 0 || bit > HEX) {
|
||||
throw new RangeError(`${bit} is not between 0 and ${HEX}.`);
|
||||
}
|
||||
if (bit === 0) {
|
||||
return Math.round(value);
|
||||
}
|
||||
const precision = bit === HEX ? 6 : bit < DEC ? 4 : 5;
|
||||
return parseFloat(value.toPrecision(precision));
|
||||
};
|
||||
|
||||
/**
|
||||
* interpolate hue
|
||||
* @param hueA - hue value
|
||||
* @param hueB - hue value
|
||||
* @param arc - shorter | longer | increasing | decreasing
|
||||
* @returns result - [hueA, hueB]
|
||||
*/
|
||||
export const interpolateHue = (
|
||||
hueA: number,
|
||||
hueB: number,
|
||||
arc: string = 'shorter'
|
||||
): [number, number] => {
|
||||
if (!Number.isFinite(hueA)) {
|
||||
throw new TypeError(`${hueA} is not a finite number.`);
|
||||
}
|
||||
if (!Number.isFinite(hueB)) {
|
||||
throw new TypeError(`${hueB} is not a finite number.`);
|
||||
}
|
||||
let a = hueA;
|
||||
let b = hueB;
|
||||
switch (arc) {
|
||||
case 'decreasing': {
|
||||
if (b > a) {
|
||||
a += DEG;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'increasing': {
|
||||
if (b < a) {
|
||||
b += DEG;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'longer': {
|
||||
if (b > a && b < a + DEG_HALF) {
|
||||
a += DEG;
|
||||
} else if (b > a - DEG_HALF && b <= a) {
|
||||
b += DEG;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'shorter':
|
||||
default: {
|
||||
if (b > a + DEG_HALF) {
|
||||
a += DEG;
|
||||
} else if (b < a - DEG_HALF) {
|
||||
b += DEG;
|
||||
}
|
||||
}
|
||||
}
|
||||
return [a, b];
|
||||
};
|
||||
|
||||
/* absolute font size to pixel ratio */
|
||||
const absoluteFontSize = new Map([
|
||||
['xx-small', 9 / 16],
|
||||
['x-small', 5 / 8],
|
||||
['small', 13 / 16],
|
||||
['medium', 1],
|
||||
['large', 9 / 8],
|
||||
['x-large', 3 / 2],
|
||||
['xx-large', 2],
|
||||
['xxx-large', 3]
|
||||
]);
|
||||
|
||||
/* relative font size to pixel ratio */
|
||||
const relativeFontSize = new Map([
|
||||
['smaller', 1 / 1.2],
|
||||
['larger', 1.2]
|
||||
]);
|
||||
|
||||
/* absolute length to pixel ratio */
|
||||
const absoluteLength = new Map([
|
||||
['cm', 96 / 2.54],
|
||||
['mm', 96 / 25.4],
|
||||
['q', 96 / 101.6],
|
||||
['in', 96],
|
||||
['pc', 16],
|
||||
['pt', 96 / 72],
|
||||
['px', 1]
|
||||
]);
|
||||
|
||||
/* relative length to pixel ratio */
|
||||
const relativeLength = new Map([
|
||||
['rcap', 1],
|
||||
['rch', 0.5],
|
||||
['rem', 1],
|
||||
['rex', 0.5],
|
||||
['ric', 1],
|
||||
['rlh', 1.2]
|
||||
]);
|
||||
|
||||
/**
|
||||
* resolve length in pixels
|
||||
* @param value - value
|
||||
* @param unit - unit
|
||||
* @param [opt] - options
|
||||
* @returns pixelated value
|
||||
*/
|
||||
export const resolveLengthInPixels = (
|
||||
value: number | string,
|
||||
unit: string | undefined,
|
||||
opt: Options = {}
|
||||
): number => {
|
||||
const { dimension = {} } = opt;
|
||||
const { callback, em, rem, vh, vw } = dimension as {
|
||||
callback: (K: string) => number;
|
||||
em: number;
|
||||
rem: number;
|
||||
vh: number;
|
||||
vw: number;
|
||||
};
|
||||
if (isString(value)) {
|
||||
const str = value.toLowerCase().trim();
|
||||
const ratio = absoluteFontSize.get(str);
|
||||
if (ratio !== undefined) {
|
||||
return ratio * rem;
|
||||
}
|
||||
const relRatio = relativeFontSize.get(str);
|
||||
if (relRatio !== undefined) {
|
||||
return relRatio * em;
|
||||
}
|
||||
return Number.NaN;
|
||||
}
|
||||
if (Number.isFinite(value) && unit) {
|
||||
const u = unit.toLowerCase();
|
||||
if (Object.hasOwn(dimension, u)) {
|
||||
return value * Number(dimension[u]);
|
||||
}
|
||||
if (typeof callback === 'function') {
|
||||
return value * (callback(u) ?? Number.NaN);
|
||||
}
|
||||
const absRatio = absoluteLength.get(u);
|
||||
if (absRatio !== undefined) {
|
||||
return value * absRatio;
|
||||
}
|
||||
const relRatio = relativeLength.get(u);
|
||||
if (relRatio !== undefined) {
|
||||
return value * relRatio * rem;
|
||||
}
|
||||
const rUnitRatio = relativeLength.get(`r${u}`);
|
||||
if (rUnitRatio !== undefined) {
|
||||
return value * rUnitRatio * em;
|
||||
}
|
||||
switch (u) {
|
||||
case 'vb':
|
||||
case 'vi': {
|
||||
return value * vw;
|
||||
}
|
||||
case 'vmax': {
|
||||
return value * Math.max(vh, vw);
|
||||
}
|
||||
case 'vmin': {
|
||||
return value * Math.min(vh, vw);
|
||||
}
|
||||
default:
|
||||
}
|
||||
}
|
||||
// unsupported or invalid value
|
||||
return Number.NaN;
|
||||
};
|
||||
|
||||
/**
|
||||
* is absolute size or length
|
||||
* @param value - value
|
||||
* @param unit - unit
|
||||
* @returns result
|
||||
*/
|
||||
export const isAbsoluteSizeOrLength = (
|
||||
value: number | string,
|
||||
unit: string | undefined
|
||||
): boolean => {
|
||||
if (isString(value)) {
|
||||
return absoluteFontSize.has(value.toLowerCase().trim());
|
||||
}
|
||||
if (isString(unit)) {
|
||||
return absoluteLength.has(unit.toLowerCase().trim());
|
||||
}
|
||||
return value === 0;
|
||||
};
|
||||
|
||||
/**
|
||||
* is absolute font size
|
||||
* @param css - css
|
||||
* @returns result
|
||||
*/
|
||||
export const isAbsoluteFontSize = (css: unknown): boolean => {
|
||||
if (!isString(css)) {
|
||||
return false;
|
||||
}
|
||||
const str = css.trim();
|
||||
if (isAbsoluteSizeOrLength(str, undefined)) {
|
||||
return true;
|
||||
}
|
||||
const match = str.match(REG_DIMENSION);
|
||||
return match
|
||||
? isAbsoluteSizeOrLength(Number(match[1]), match[2] || undefined)
|
||||
: false;
|
||||
};
|
||||
21
node_modules/@asamuzakjp/dom-selector/LICENSE
generated
vendored
Normal file
21
node_modules/@asamuzakjp/dom-selector/LICENSE
generated
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2023 asamuzaK (Kazz)
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
324
node_modules/@asamuzakjp/dom-selector/README.md
generated
vendored
Normal file
324
node_modules/@asamuzakjp/dom-selector/README.md
generated
vendored
Normal file
@@ -0,0 +1,324 @@
|
||||
# DOM Selector
|
||||
|
||||
[](https://github.com/asamuzaK/domSelector/actions/workflows/node.js.yml)
|
||||
[](https://github.com/asamuzaK/domSelector/actions/workflows/github-code-scanning/codeql)
|
||||
[](https://www.npmjs.com/package/@asamuzakjp/dom-selector)
|
||||
|
||||
A CSS selector engine.
|
||||
|
||||
## Install
|
||||
|
||||
```console
|
||||
npm i @asamuzakjp/dom-selector
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
```javascript
|
||||
import { DOMSelector } from '@asamuzakjp/dom-selector';
|
||||
import { JSDOM } from 'jsdom';
|
||||
|
||||
const { window } = new JSDOM();
|
||||
const {
|
||||
closest, matches, querySelector, querySelectorAll
|
||||
} = new DOMSelector(window);
|
||||
```
|
||||
|
||||
<!-- Generated by documentation.js. Update this documentation by updating the source code. -->
|
||||
|
||||
### matches(selector, node, opt)
|
||||
|
||||
matches - equivalent to [Element.matches()][64]
|
||||
|
||||
#### Parameters
|
||||
|
||||
- `selector` **[string][59]** CSS selector
|
||||
- `node` **[object][60]** Element node
|
||||
- `opt` **[object][60]?** options
|
||||
- `opt.noexcept` **[boolean][61]?** no exception
|
||||
- `opt.warn` **[boolean][61]?** console warn e.g. unsupported pseudo-class
|
||||
|
||||
Returns **[boolean][61]** `true` if matched, `false` otherwise
|
||||
|
||||
|
||||
### closest(selector, node, opt)
|
||||
|
||||
closest - equivalent to [Element.closest()][65]
|
||||
|
||||
#### Parameters
|
||||
|
||||
- `selector` **[string][59]** CSS selector
|
||||
- `node` **[object][60]** Element node
|
||||
- `opt` **[object][60]?** options
|
||||
- `opt.noexcept` **[boolean][61]?** no exception
|
||||
- `opt.warn` **[boolean][61]?** console warn e.g. unsupported pseudo-class
|
||||
|
||||
Returns **[object][60]?** matched node
|
||||
|
||||
|
||||
### querySelector(selector, node, opt)
|
||||
|
||||
querySelector - equivalent to [Document.querySelector()][66], [DocumentFragment.querySelector()][67] and [Element.querySelector()][68]
|
||||
|
||||
#### Parameters
|
||||
|
||||
- `selector` **[string][59]** CSS selector
|
||||
- `node` **[object][60]** Document, DocumentFragment or Element node
|
||||
- `opt` **[object][60]?** options
|
||||
- `opt.noexcept` **[boolean][61]?** no exception
|
||||
- `opt.warn` **[boolean][61]?** console warn e.g. unsupported pseudo-class
|
||||
|
||||
Returns **[object][60]?** matched node
|
||||
|
||||
|
||||
### querySelectorAll(selector, node, opt)
|
||||
|
||||
querySelectorAll - equivalent to [Document.querySelectorAll()][69], [DocumentFragment.querySelectorAll()][70] and [Element.querySelectorAll()][71]
|
||||
**NOTE**: returns Array, not NodeList
|
||||
|
||||
#### Parameters
|
||||
|
||||
- `selector` **[string][59]** CSS selector
|
||||
- `node` **[object][60]** Document, DocumentFragment or Element node
|
||||
- `opt` **[object][60]?** options
|
||||
- `opt.noexcept` **[boolean][61]?** no exception
|
||||
- `opt.warn` **[boolean][61]?** console warn e.g. unsupported pseudo-class
|
||||
|
||||
Returns **[Array][62]<([object][60] \| [undefined][63])>** array of matched nodes
|
||||
|
||||
|
||||
## Monkey patch jsdom
|
||||
|
||||
``` javascript
|
||||
import { DOMSelector } from '@asamuzakjp/dom-selector';
|
||||
import { JSDOM } from 'jsdom';
|
||||
|
||||
const dom = new JSDOM('', {
|
||||
runScripts: 'dangerously',
|
||||
url: 'http://localhost/',
|
||||
beforeParse: window => {
|
||||
const domSelector = new DOMSelector(window);
|
||||
|
||||
const matches = domSelector.matches.bind(domSelector);
|
||||
window.Element.prototype.matches = function (...args) {
|
||||
if (!args.length) {
|
||||
throw new window.TypeError('1 argument required, but only 0 present.');
|
||||
}
|
||||
const [selector] = args;
|
||||
return matches(selector, this);
|
||||
};
|
||||
|
||||
const closest = domSelector.closest.bind(domSelector);
|
||||
window.Element.prototype.closest = function (...args) {
|
||||
if (!args.length) {
|
||||
throw new window.TypeError('1 argument required, but only 0 present.');
|
||||
}
|
||||
const [selector] = args;
|
||||
return closest(selector, this);
|
||||
};
|
||||
|
||||
const querySelector = domSelector.querySelector.bind(domSelector);
|
||||
window.Document.prototype.querySelector = function (...args) {
|
||||
if (!args.length) {
|
||||
throw new window.TypeError('1 argument required, but only 0 present.');
|
||||
}
|
||||
const [selector] = args;
|
||||
return querySelector(selector, this);
|
||||
};
|
||||
window.DocumentFragment.prototype.querySelector = function (...args) {
|
||||
if (!args.length) {
|
||||
throw new window.TypeError('1 argument required, but only 0 present.');
|
||||
}
|
||||
const [selector] = args;
|
||||
return querySelector(selector, this);
|
||||
};
|
||||
window.Element.prototype.querySelector = function (...args) {
|
||||
if (!args.length) {
|
||||
throw new window.TypeError('1 argument required, but only 0 present.');
|
||||
}
|
||||
const [selector] = args;
|
||||
return querySelector(selector, this);
|
||||
};
|
||||
|
||||
const querySelectorAll = domSelector.querySelectorAll.bind(domSelector);
|
||||
window.Document.prototype.querySelectorAll = function (...args) {
|
||||
if (!args.length) {
|
||||
throw new window.TypeError('1 argument required, but only 0 present.');
|
||||
}
|
||||
const [selector] = args;
|
||||
return querySelectorAll(selector, this);
|
||||
};
|
||||
window.DocumentFragment.prototype.querySelectorAll = function (...args) {
|
||||
if (!args.length) {
|
||||
throw new window.TypeError('1 argument required, but only 0 present.');
|
||||
}
|
||||
const [selector] = args;
|
||||
return querySelectorAll(selector, this);
|
||||
};
|
||||
window.Element.prototype.querySelectorAll = function (...args) {
|
||||
if (!args.length) {
|
||||
throw new window.TypeError('1 argument required, but only 0 present.');
|
||||
}
|
||||
const [selector] = args;
|
||||
return querySelectorAll(selector, this);
|
||||
};
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
|
||||
## Supported CSS selectors
|
||||
|
||||
|Pattern|Supported|Note|
|
||||
|:--------|:-------:|:--------|
|
||||
|\*|✓| |
|
||||
|E|✓| |
|
||||
|ns\|E|✓| |
|
||||
|\*\|E|✓| |
|
||||
|\|E|✓| |
|
||||
|E F|✓| |
|
||||
|E > F|✓| |
|
||||
|E + F|✓| |
|
||||
|E ~ F|✓| |
|
||||
|F \|\| E|Unsupported| |
|
||||
|E.warning|✓| |
|
||||
|E#myid|✓| |
|
||||
|E\[foo\]|✓| |
|
||||
|E\[foo="bar"\]|✓| |
|
||||
|E\[foo="bar" i\]|✓| |
|
||||
|E\[foo="bar" s\]|✓| |
|
||||
|E\[foo~="bar"\]|✓| |
|
||||
|E\[foo^="bar"\]|✓| |
|
||||
|E\[foo$="bar"\]|✓| |
|
||||
|E\[foo*="bar"\]|✓| |
|
||||
|E\[foo\|="en"\]|✓| |
|
||||
|E:is(s1, s2, …)|✓| |
|
||||
|E:not(s1, s2, …)|✓| |
|
||||
|E:where(s1, s2, …)|✓| |
|
||||
|E:has(rs1, rs2, …)|✓| |
|
||||
|E:defined|Partially supported|Matching with MathML is not yet supported.|
|
||||
|E:dir(ltr)|✓| |
|
||||
|E:lang(en)|✓| |
|
||||
|E:any‑link|✓| |
|
||||
|E:link|✓| |
|
||||
|E:visited|✓|Returns `false` or `null` to prevent fingerprinting.|
|
||||
|E:local‑link|✓| |
|
||||
|E:target|✓| |
|
||||
|E:target‑within|✓| |
|
||||
|E:scope|✓| |
|
||||
|E:hover|✓| |
|
||||
|E:active|✓| |
|
||||
|E:focus|✓| |
|
||||
|E:focus‑visible|✓| |
|
||||
|E:focus‑within|✓| |
|
||||
|E:current|Unsupported| |
|
||||
|E:current(s)|Unsupported| |
|
||||
|E:past|Unsupported| |
|
||||
|E:future|Unsupported| |
|
||||
|E:open<br>E:closed|Partially supported|Matching with <select>, e.g. `select:open`, is not supported.|
|
||||
|E:popover-open|Unsupported| |
|
||||
|E:enabled<br>E:disabled|✓| |
|
||||
|E:read‑write<br>E:read‑only|✓| |
|
||||
|E:placeholder‑shown|✓| |
|
||||
|E:default|✓| |
|
||||
|E:checked|✓| |
|
||||
|E:indeterminate|✓| |
|
||||
|E:blank|Unsupported| |
|
||||
|E:valid<br>E:invalid|✓| |
|
||||
|E:in-range<br>E:out-of-range|✓| |
|
||||
|E:required<br>E:optional|✓| |
|
||||
|E:user‑valid<br>E:user‑invalid|Unsupported| |
|
||||
|E:root|✓| |
|
||||
|E:empty|✓| |
|
||||
|E:nth‑child(n [of S]?)|✓| |
|
||||
|E:nth‑last‑child(n [of S]?)|✓| |
|
||||
|E:first‑child|✓| |
|
||||
|E:last‑child|✓| |
|
||||
|E:only‑child|✓| |
|
||||
|E:nth‑of‑type(n)|✓| |
|
||||
|E:nth‑last‑of‑type(n)|✓| |
|
||||
|E:first‑of‑type|✓| |
|
||||
|E:last‑of‑type|✓| |
|
||||
|E:only‑of‑type|✓| |
|
||||
|E:nth‑col(n)|Unsupported| |
|
||||
|E:nth‑last‑col(n)|Unsupported| |
|
||||
|CE:state(v)|✓|*1|
|
||||
|:host|✓| |
|
||||
|:host(s)|✓| |
|
||||
|:host(:state(v))|✓|*1|
|
||||
|:host:has(rs1, rs2, ...)|✓| |
|
||||
|:host(s):has(rs1, rs2, ...)|✓| |
|
||||
|:host‑context(s)|✓| |
|
||||
|:host‑context(s):has(rs1, rs2, ...)|✓| |
|
||||
|&|✓|Only supports outermost `&`, i.e. equivalent to `:scope`|
|
||||
|
||||
*1: `ElementInternals.states`, i.e. `CustomStateSet`, is not implemented in jsdom, so you need to apply a patch in the custom element constructor.
|
||||
|
||||
``` javascript
|
||||
class LabeledCheckbox extends window.HTMLElement {
|
||||
#internals;
|
||||
constructor() {
|
||||
super();
|
||||
this.#internals = this.attachInternals();
|
||||
// patch CustomStateSet
|
||||
if (!this.#internals.states) {
|
||||
this.#internals.states = new Set();
|
||||
}
|
||||
this.addEventListener('click', this._onClick.bind(this));
|
||||
}
|
||||
get checked() {
|
||||
return this.#internals.states.has('checked');
|
||||
}
|
||||
set checked(flag) {
|
||||
if (flag) {
|
||||
this.#internals.states.add('checked');
|
||||
} else {
|
||||
this.#internals.states.delete('checked');
|
||||
}
|
||||
}
|
||||
_onClick(event) {
|
||||
this.checked = !this.checked;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
## Performance
|
||||
|
||||
See [benchmark](https://github.com/asamuzaK/domSelector/actions/workflows/benchmark.yml) for the latest results.
|
||||
|
||||
|
||||
## Acknowledgments
|
||||
|
||||
The following resources have been of great help in the development of the DOM Selector.
|
||||
|
||||
- [CSSTree](https://github.com/csstree/csstree)
|
||||
- [selery](https://github.com/danburzo/selery)
|
||||
- [jsdom](https://github.com/jsdom/jsdom)
|
||||
- [nwsapi](https://github.com/dperini/nwsapi)
|
||||
|
||||
---
|
||||
Copyright (c) 2023 [asamuzaK (Kazz)](https://github.com/asamuzaK/)
|
||||
|
||||
|
||||
[1]: #matches
|
||||
[2]: #parameters
|
||||
[3]: #closest
|
||||
[4]: #parameters-1
|
||||
[5]: #queryselector
|
||||
[6]: #parameters-2
|
||||
[7]: #queryselectorall
|
||||
[8]: #parameters-3
|
||||
[59]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String
|
||||
[60]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object
|
||||
[61]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean
|
||||
[62]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array
|
||||
[63]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/undefined
|
||||
[64]: https://developer.mozilla.org/docs/Web/API/Element/matches
|
||||
[65]: https://developer.mozilla.org/docs/Web/API/Element/closest
|
||||
[66]: https://developer.mozilla.org/docs/Web/API/Document/querySelector
|
||||
[67]: https://developer.mozilla.org/docs/Web/API/DocumentFragment/querySelector
|
||||
[68]: https://developer.mozilla.org/docs/Web/API/Element/querySelector
|
||||
[69]: https://developer.mozilla.org/docs/Web/API/Document/querySelectorAll
|
||||
[70]: https://developer.mozilla.org/docs/Web/API/DocumentFragment/querySelectorAll
|
||||
[71]: https://developer.mozilla.org/docs/Web/API/Element/querySelectorAll
|
||||
75
node_modules/@asamuzakjp/dom-selector/package.json
generated
vendored
Normal file
75
node_modules/@asamuzakjp/dom-selector/package.json
generated
vendored
Normal file
@@ -0,0 +1,75 @@
|
||||
{
|
||||
"name": "@asamuzakjp/dom-selector",
|
||||
"description": "A CSS selector engine.",
|
||||
"author": "asamuzaK",
|
||||
"license": "MIT",
|
||||
"homepage": "https://github.com/asamuzaK/domSelector#readme",
|
||||
"bugs": {
|
||||
"url": "https://github.com/asamuzaK/domSelector/issues"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/asamuzaK/domSelector.git"
|
||||
},
|
||||
"files": [
|
||||
"src",
|
||||
"types"
|
||||
],
|
||||
"type": "module",
|
||||
"exports": {
|
||||
".": {
|
||||
"types": "./types/index.d.ts",
|
||||
"default": "./src/index.js"
|
||||
},
|
||||
"./package.json": "./package.json"
|
||||
},
|
||||
"dependencies": {
|
||||
"@asamuzakjp/nwsapi": "^2.3.9",
|
||||
"bidi-js": "^1.0.3",
|
||||
"css-tree": "^3.2.1",
|
||||
"is-potential-custom-element-name": "^1.0.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/css-tree": "^2.3.11",
|
||||
"@types/node": "^25.5.2",
|
||||
"benchmark": "^2.1.4",
|
||||
"c8": "^11.0.0",
|
||||
"chai": "^6.2.2",
|
||||
"commander": "^14.0.3",
|
||||
"eslint": "^9.39.4",
|
||||
"eslint-config-prettier": "^10.1.8",
|
||||
"eslint-plugin-jsdoc": "^62.9.0",
|
||||
"eslint-plugin-prettier": "^5.5.5",
|
||||
"eslint-plugin-regexp": "^3.1.0",
|
||||
"eslint-plugin-unicorn": "^64.0.0",
|
||||
"globals": "^17.4.0",
|
||||
"jsdom": "^29.0.2",
|
||||
"mocha": "^11.7.5",
|
||||
"neostandard": "^0.13.0",
|
||||
"prettier": "^3.8.1",
|
||||
"sinon": "^21.0.3",
|
||||
"typescript": "^6.0.2",
|
||||
"wpt-runner": "^7.0.0"
|
||||
},
|
||||
"overrides": {
|
||||
"c8": {
|
||||
"yargs": "^18.0.0"
|
||||
},
|
||||
"jsdom": "$jsdom",
|
||||
"serialize-javascript": "^7.0.4"
|
||||
},
|
||||
"scripts": {
|
||||
"bench": "node benchmark/bench.js",
|
||||
"bench:sizzle": "node benchmark/bench-sizzle.js",
|
||||
"build": "npm run tsc && npm run lint && npm test",
|
||||
"lint": "eslint --fix .",
|
||||
"test": "c8 --reporter=text mocha --parallel --exit test/**/*.test.js",
|
||||
"test:wpt": "node test/wpt/wpt-runner.js",
|
||||
"tsc": "node scripts/index clean --dir=types -i && npx tsc",
|
||||
"update:wpt": "git submodule update --init --recursive --remote"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^20.19.0 || ^22.12.0 || >=24.0.0"
|
||||
},
|
||||
"version": "7.0.9"
|
||||
}
|
||||
335
node_modules/@asamuzakjp/dom-selector/src/index.js
generated
vendored
Normal file
335
node_modules/@asamuzakjp/dom-selector/src/index.js
generated
vendored
Normal file
@@ -0,0 +1,335 @@
|
||||
/*!
|
||||
* DOM Selector - A CSS selector engine.
|
||||
* @license MIT
|
||||
* @copyright asamuzaK (Kazz)
|
||||
* @see {@link https://github.com/asamuzaK/domSelector/blob/main/LICENSE}
|
||||
*/
|
||||
|
||||
/* import */
|
||||
import { GenerationalCache } from './js/cache.js';
|
||||
import { Finder } from './js/finder.js';
|
||||
import { filterSelector, getType, initNwsapi } from './js/utility.js';
|
||||
|
||||
/* constants */
|
||||
import {
|
||||
DOCUMENT_NODE,
|
||||
DOCUMENT_FRAGMENT_NODE,
|
||||
ELEMENT_NODE,
|
||||
TARGET_ALL,
|
||||
TARGET_FIRST,
|
||||
TARGET_LINEAL,
|
||||
TARGET_SELF
|
||||
} from './js/constant.js';
|
||||
const MAX_CACHE = 1024;
|
||||
|
||||
/**
|
||||
* @typedef {object} CheckResult
|
||||
* @property {boolean} match - The match result.
|
||||
* @property {string?} pseudoElement - The pseudo-element, if any.
|
||||
* @property {object?} ast - The AST object.
|
||||
*/
|
||||
|
||||
/* DOMSelector */
|
||||
export class DOMSelector {
|
||||
/* private fields */
|
||||
#window;
|
||||
#document;
|
||||
#finder;
|
||||
#idlUtils;
|
||||
#nwsapi;
|
||||
#cache;
|
||||
|
||||
/**
|
||||
* Creates an instance of DOMSelector.
|
||||
* @param {Window} window - The window object.
|
||||
* @param {Document} document - The document object.
|
||||
* @param {object} [opt] - Options.
|
||||
*/
|
||||
constructor(window, document, opt = {}) {
|
||||
const { idlUtils } = opt;
|
||||
this.#window = window;
|
||||
this.#document = document ?? window.document;
|
||||
this.#finder = new Finder(window);
|
||||
this.#idlUtils = idlUtils;
|
||||
this.#nwsapi = initNwsapi(window, document);
|
||||
this.#cache = new GenerationalCache(MAX_CACHE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears the internal cache of finder results.
|
||||
* @returns {void}
|
||||
*/
|
||||
clear = () => {
|
||||
this.#finder.clearResults(true);
|
||||
};
|
||||
|
||||
/**
|
||||
* Checks if an element matches a CSS selector.
|
||||
* @param {string} selector - The CSS selector to check against.
|
||||
* @param {Element} node - The element node to check.
|
||||
* @param {object} [opt] - Optional parameters.
|
||||
* @returns {CheckResult} An object containing the check result.
|
||||
*/
|
||||
check = (selector, node, opt = {}) => {
|
||||
if (!node?.nodeType) {
|
||||
const e = new this.#window.TypeError(`Unexpected type ${getType(node)}`);
|
||||
return this.#finder.onError(e, opt);
|
||||
} else if (node.nodeType !== ELEMENT_NODE) {
|
||||
const e = new this.#window.TypeError(`Unexpected node ${node.nodeName}`);
|
||||
return this.#finder.onError(e, opt);
|
||||
}
|
||||
const document = node.ownerDocument;
|
||||
if (
|
||||
document === this.#document &&
|
||||
document.contentType === 'text/html' &&
|
||||
document.documentElement &&
|
||||
node.parentNode
|
||||
) {
|
||||
const cacheKey = `check_${selector}`;
|
||||
let filterMatches = this.#cache.get(cacheKey);
|
||||
if (filterMatches === undefined) {
|
||||
filterMatches = filterSelector(selector, TARGET_SELF);
|
||||
this.#cache.set(cacheKey, filterMatches);
|
||||
}
|
||||
if (filterMatches) {
|
||||
try {
|
||||
const n = this.#idlUtils ? this.#idlUtils.wrapperForImpl(node) : node;
|
||||
const match = this.#nwsapi.match(selector, n);
|
||||
let ast = null;
|
||||
if (match) {
|
||||
const astCacheKey = `check_ast_${selector}`;
|
||||
ast = this.#cache.get(astCacheKey);
|
||||
if (ast === undefined) {
|
||||
ast = this.#finder.getAST(selector);
|
||||
this.#cache.set(astCacheKey, ast);
|
||||
}
|
||||
}
|
||||
return {
|
||||
match,
|
||||
ast,
|
||||
pseudoElement: null
|
||||
};
|
||||
} catch (e) {
|
||||
// fall through
|
||||
}
|
||||
}
|
||||
}
|
||||
if (this.#idlUtils) {
|
||||
node = this.#idlUtils.wrapperForImpl(node);
|
||||
}
|
||||
opt.check = true;
|
||||
opt.noexcept = true;
|
||||
opt.warn = false;
|
||||
return this.#finder.setup(selector, node, opt).find(TARGET_SELF);
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns true if the element matches the selector.
|
||||
* @param {string} selector - The CSS selector to match against.
|
||||
* @param {Element} node - The element node to test.
|
||||
* @param {object} [opt] - Optional parameters.
|
||||
* @returns {boolean} `true` if the element matches, or `false` otherwise.
|
||||
*/
|
||||
matches = (selector, node, opt = {}) => {
|
||||
if (!node?.nodeType) {
|
||||
const e = new this.#window.TypeError(`Unexpected type ${getType(node)}`);
|
||||
return this.#finder.onError(e, opt);
|
||||
} else if (node.nodeType !== ELEMENT_NODE) {
|
||||
const e = new this.#window.TypeError(`Unexpected node ${node.nodeName}`);
|
||||
return this.#finder.onError(e, opt);
|
||||
}
|
||||
const document = node.ownerDocument;
|
||||
if (
|
||||
document === this.#document &&
|
||||
document.contentType === 'text/html' &&
|
||||
document.documentElement &&
|
||||
node.parentNode
|
||||
) {
|
||||
const cacheKey = `matches_${selector}`;
|
||||
let filterMatches = this.#cache.get(cacheKey);
|
||||
if (filterMatches === undefined) {
|
||||
filterMatches = filterSelector(selector, TARGET_SELF);
|
||||
this.#cache.set(cacheKey, filterMatches);
|
||||
}
|
||||
if (filterMatches) {
|
||||
try {
|
||||
const n = this.#idlUtils ? this.#idlUtils.wrapperForImpl(node) : node;
|
||||
return this.#nwsapi.match(selector, n);
|
||||
} catch (e) {
|
||||
// fall through
|
||||
}
|
||||
}
|
||||
}
|
||||
let res;
|
||||
try {
|
||||
if (this.#idlUtils) {
|
||||
node = this.#idlUtils.wrapperForImpl(node);
|
||||
}
|
||||
const nodes = this.#finder.setup(selector, node, opt).find(TARGET_SELF);
|
||||
res = nodes.size;
|
||||
} catch (e) {
|
||||
this.#finder.onError(e, opt);
|
||||
}
|
||||
return !!res;
|
||||
};
|
||||
|
||||
/**
|
||||
* Traverses up the DOM tree to find the first node that matches the selector.
|
||||
* @param {string} selector - The CSS selector to match against.
|
||||
* @param {Element} node - The element from which to start traversing.
|
||||
* @param {object} [opt] - Optional parameters.
|
||||
* @returns {?Element} The first matching ancestor element, or `null`.
|
||||
*/
|
||||
closest = (selector, node, opt = {}) => {
|
||||
if (!node?.nodeType) {
|
||||
const e = new this.#window.TypeError(`Unexpected type ${getType(node)}`);
|
||||
return this.#finder.onError(e, opt);
|
||||
} else if (node.nodeType !== ELEMENT_NODE) {
|
||||
const e = new this.#window.TypeError(`Unexpected node ${node.nodeName}`);
|
||||
return this.#finder.onError(e, opt);
|
||||
}
|
||||
const document = node.ownerDocument;
|
||||
if (
|
||||
document === this.#document &&
|
||||
document.contentType === 'text/html' &&
|
||||
document.documentElement &&
|
||||
node.parentNode
|
||||
) {
|
||||
const cacheKey = `closest_${selector}`;
|
||||
let filterMatches = this.#cache.get(cacheKey);
|
||||
if (filterMatches === undefined) {
|
||||
filterMatches = filterSelector(selector, TARGET_LINEAL);
|
||||
this.#cache.set(cacheKey, filterMatches);
|
||||
}
|
||||
if (filterMatches) {
|
||||
try {
|
||||
const n = this.#idlUtils ? this.#idlUtils.wrapperForImpl(node) : node;
|
||||
return this.#nwsapi.closest(selector, n);
|
||||
} catch (e) {
|
||||
// fall through
|
||||
}
|
||||
}
|
||||
}
|
||||
let res;
|
||||
try {
|
||||
if (this.#idlUtils) {
|
||||
node = this.#idlUtils.wrapperForImpl(node);
|
||||
}
|
||||
const nodes = this.#finder.setup(selector, node, opt).find(TARGET_LINEAL);
|
||||
if (nodes.size) {
|
||||
let refNode = node;
|
||||
while (refNode) {
|
||||
if (nodes.has(refNode)) {
|
||||
res = refNode;
|
||||
break;
|
||||
}
|
||||
refNode = refNode.parentNode;
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
this.#finder.onError(e, opt);
|
||||
}
|
||||
return res ?? null;
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns the first element within the subtree that matches the selector.
|
||||
* @param {string} selector - The CSS selector to match.
|
||||
* @param {Document|DocumentFragment|Element} node - The node to find within.
|
||||
* @param {object} [opt] - Optional parameters.
|
||||
* @returns {?Element} The first matching element, or `null`.
|
||||
*/
|
||||
querySelector = (selector, node, opt = {}) => {
|
||||
if (!node?.nodeType) {
|
||||
const e = new this.#window.TypeError(`Unexpected type ${getType(node)}`);
|
||||
return this.#finder.onError(e, opt);
|
||||
}
|
||||
const document =
|
||||
node.nodeType === DOCUMENT_NODE ? node : node.ownerDocument;
|
||||
if (
|
||||
document === this.#document &&
|
||||
document.contentType === 'text/html' &&
|
||||
document.documentElement &&
|
||||
(node.nodeType !== DOCUMENT_FRAGMENT_NODE || !node.host)
|
||||
) {
|
||||
const cacheKey = `querySelector_${selector}`;
|
||||
let filterMatches = this.#cache.get(cacheKey);
|
||||
if (filterMatches === undefined) {
|
||||
filterMatches = filterSelector(selector, TARGET_FIRST);
|
||||
this.#cache.set(cacheKey, filterMatches);
|
||||
}
|
||||
if (filterMatches) {
|
||||
try {
|
||||
const n = this.#idlUtils ? this.#idlUtils.wrapperForImpl(node) : node;
|
||||
return this.#nwsapi.first(selector, n);
|
||||
} catch (e) {
|
||||
// fall through
|
||||
}
|
||||
}
|
||||
}
|
||||
let res;
|
||||
try {
|
||||
if (this.#idlUtils) {
|
||||
node = this.#idlUtils.wrapperForImpl(node);
|
||||
}
|
||||
const nodes = this.#finder.setup(selector, node, opt).find(TARGET_FIRST);
|
||||
if (nodes.size) {
|
||||
[res] = [...nodes];
|
||||
}
|
||||
} catch (e) {
|
||||
this.#finder.onError(e, opt);
|
||||
}
|
||||
return res ?? null;
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns an array of elements within the subtree that match the selector.
|
||||
* Note: This method returns an Array, not a NodeList.
|
||||
* @param {string} selector - The CSS selector to match.
|
||||
* @param {Document|DocumentFragment|Element} node - The node to find within.
|
||||
* @param {object} [opt] - Optional parameters.
|
||||
* @returns {Array<Element>} An array of elements, or an empty array.
|
||||
*/
|
||||
querySelectorAll = (selector, node, opt = {}) => {
|
||||
if (!node?.nodeType) {
|
||||
const e = new this.#window.TypeError(`Unexpected type ${getType(node)}`);
|
||||
return this.#finder.onError(e, opt);
|
||||
}
|
||||
const document =
|
||||
node.nodeType === DOCUMENT_NODE ? node : node.ownerDocument;
|
||||
if (
|
||||
document === this.#document &&
|
||||
document.contentType === 'text/html' &&
|
||||
document.documentElement &&
|
||||
(node.nodeType !== DOCUMENT_FRAGMENT_NODE || !node.host)
|
||||
) {
|
||||
const cacheKey = `querySelectorAll_${selector}`;
|
||||
let filterMatches = this.#cache.get(cacheKey);
|
||||
if (filterMatches === undefined) {
|
||||
filterMatches = filterSelector(selector, TARGET_ALL);
|
||||
this.#cache.set(cacheKey, filterMatches);
|
||||
}
|
||||
if (filterMatches) {
|
||||
try {
|
||||
const n = this.#idlUtils ? this.#idlUtils.wrapperForImpl(node) : node;
|
||||
return this.#nwsapi.select(selector, n);
|
||||
} catch (e) {
|
||||
// fall through
|
||||
}
|
||||
}
|
||||
}
|
||||
let res;
|
||||
try {
|
||||
if (this.#idlUtils) {
|
||||
node = this.#idlUtils.wrapperForImpl(node);
|
||||
}
|
||||
const nodes = this.#finder.setup(selector, node, opt).find(TARGET_ALL);
|
||||
if (nodes.size) {
|
||||
res = [...nodes];
|
||||
}
|
||||
} catch (e) {
|
||||
this.#finder.onError(e, opt);
|
||||
}
|
||||
return res ?? [];
|
||||
};
|
||||
}
|
||||
77
node_modules/@asamuzakjp/dom-selector/src/js/cache.js
generated
vendored
Normal file
77
node_modules/@asamuzakjp/dom-selector/src/js/cache.js
generated
vendored
Normal file
@@ -0,0 +1,77 @@
|
||||
/**
|
||||
* cache.js
|
||||
*/
|
||||
|
||||
/* Generational Cache */
|
||||
export class GenerationalCache {
|
||||
#max;
|
||||
#boundary;
|
||||
#current;
|
||||
#old;
|
||||
|
||||
/**
|
||||
* constructor
|
||||
* @param {number} max - max cache size
|
||||
*/
|
||||
constructor(max) {
|
||||
this.#current = new Map();
|
||||
this.#old = new Map();
|
||||
this.max = max;
|
||||
}
|
||||
|
||||
get size() {
|
||||
return this.#current.size + this.#old.size;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {number} max cache size
|
||||
*/
|
||||
get max() {
|
||||
return this.#max;
|
||||
}
|
||||
|
||||
set max(value) {
|
||||
if (Number.isFinite(value) && value > 4) {
|
||||
this.#max = value;
|
||||
this.#boundary = Math.ceil(value / 2);
|
||||
} else {
|
||||
this.#max = 4;
|
||||
this.#boundary = 2;
|
||||
}
|
||||
this.clear();
|
||||
}
|
||||
|
||||
get(key) {
|
||||
if (this.#current.has(key)) {
|
||||
return this.#current.get(key);
|
||||
}
|
||||
const value = this.#old.get(key);
|
||||
if (value !== undefined) {
|
||||
this.set(key, value);
|
||||
return value;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
set(key, value) {
|
||||
this.#current.set(key, value);
|
||||
if (this.#current.size >= this.#boundary) {
|
||||
this.#old = this.#current;
|
||||
this.#current = new Map();
|
||||
}
|
||||
}
|
||||
|
||||
has(key) {
|
||||
return this.#current.has(key) || this.#old.has(key);
|
||||
}
|
||||
|
||||
delete(key) {
|
||||
this.#current.delete(key);
|
||||
this.#old.delete(key);
|
||||
}
|
||||
|
||||
clear() {
|
||||
this.#current.clear();
|
||||
this.#old.clear();
|
||||
}
|
||||
}
|
||||
129
node_modules/@asamuzakjp/dom-selector/src/js/constant.js
generated
vendored
Normal file
129
node_modules/@asamuzakjp/dom-selector/src/js/constant.js
generated
vendored
Normal file
@@ -0,0 +1,129 @@
|
||||
/**
|
||||
* constant.js
|
||||
*/
|
||||
|
||||
/* string */
|
||||
export const ATRULE = 'Atrule';
|
||||
export const ATTR_SELECTOR = 'AttributeSelector';
|
||||
export const CLASS_SELECTOR = 'ClassSelector';
|
||||
export const COMBINATOR = 'Combinator';
|
||||
export const IDENT = 'Identifier';
|
||||
export const ID_SELECTOR = 'IdSelector';
|
||||
export const NOT_SUPPORTED_ERR = 'NotSupportedError';
|
||||
export const NTH = 'Nth';
|
||||
export const OPERATOR = 'Operator';
|
||||
export const PS_CLASS_SELECTOR = 'PseudoClassSelector';
|
||||
export const PS_ELEMENT_SELECTOR = 'PseudoElementSelector';
|
||||
export const RULE = 'Rule';
|
||||
export const SCOPE = 'Scope';
|
||||
export const SELECTOR = 'Selector';
|
||||
export const SELECTOR_LIST = 'SelectorList';
|
||||
export const STRING = 'String';
|
||||
export const SYNTAX_ERR = 'SyntaxError';
|
||||
export const TARGET_ALL = 'all';
|
||||
export const TARGET_FIRST = 'first';
|
||||
export const TARGET_LINEAL = 'lineal';
|
||||
export const TARGET_SELF = 'self';
|
||||
export const TYPE_SELECTOR = 'TypeSelector';
|
||||
|
||||
/* numeric */
|
||||
export const BIT_01 = 1;
|
||||
export const BIT_02 = 2;
|
||||
export const BIT_04 = 4;
|
||||
export const BIT_08 = 8;
|
||||
export const BIT_16 = 0x10;
|
||||
export const BIT_32 = 0x20;
|
||||
export const BIT_FFFF = 0xffff;
|
||||
export const DUO = 2;
|
||||
export const HEX = 16;
|
||||
export const TYPE_FROM = 8;
|
||||
export const TYPE_TO = -1;
|
||||
|
||||
/* Node */
|
||||
export const ELEMENT_NODE = 1;
|
||||
export const TEXT_NODE = 3;
|
||||
export const DOCUMENT_NODE = 9;
|
||||
export const DOCUMENT_FRAGMENT_NODE = 11;
|
||||
export const DOCUMENT_POSITION_PRECEDING = 2;
|
||||
export const DOCUMENT_POSITION_CONTAINS = 8;
|
||||
export const DOCUMENT_POSITION_CONTAINED_BY = 0x10;
|
||||
|
||||
/* NodeFilter */
|
||||
export const SHOW_ALL = 0xffffffff;
|
||||
export const SHOW_CONTAINER = 0x501;
|
||||
export const SHOW_DOCUMENT = 0x100;
|
||||
export const SHOW_DOCUMENT_FRAGMENT = 0x400;
|
||||
export const SHOW_ELEMENT = 1;
|
||||
|
||||
/* selectors */
|
||||
export const ALPHA_NUM = '[A-Z\\d]+';
|
||||
export const CHILD_IDX = '(?:first|last|only)-(?:child|of-type)';
|
||||
export const DIGIT = '(?:0|[1-9]\\d*)';
|
||||
export const LANG_PART = `(?:-${ALPHA_NUM})*`;
|
||||
export const PSEUDO_CLASS = `(?:any-)?link|${CHILD_IDX}|checked|empty|indeterminate|read-(?:only|write)|target`;
|
||||
export const ANB = `[+-]?(?:${DIGIT}n?|n)|(?:[+-]?${DIGIT})?n\\s*[+-]\\s*${DIGIT}`;
|
||||
// combinators
|
||||
export const COMBO = '\\s?[\\s>~+]\\s?';
|
||||
export const DESCEND = '\\s?[\\s>]\\s?';
|
||||
export const SIBLING = '\\s?[+~]\\s?';
|
||||
// LOGIC_IS: :is()
|
||||
export const LOGIC_IS = `:is\\(\\s*[^)]+\\s*\\)`;
|
||||
// N_TH: excludes An+B with selector list, e.g. :nth-child(2n+1 of .foo)
|
||||
export const N_TH = `nth-(?:last-)?(?:child|of-type)\\(\\s*(?:even|odd|${ANB})\\s*\\)`;
|
||||
// SUB_TYPE: attr, id, class, pseudo-class, note that [foo|=bar] is excluded
|
||||
export const SUB_TYPE = '\\[[^|\\]]+\\]|[#.:][\\w-]+';
|
||||
export const SUB_TYPE_WO_PSEUDO = '\\[[^|\\]]+\\]|[#.][\\w-]+';
|
||||
// TAG_TYPE: *, tag
|
||||
export const TAG_TYPE = '\\*|[A-Za-z][\\w-]*';
|
||||
export const TAG_TYPE_I = '\\*|[A-Z][\\w-]*';
|
||||
export const COMPOUND = `(?:${TAG_TYPE}|(?:${TAG_TYPE})?(?:${SUB_TYPE})+)`;
|
||||
export const COMPOUND_L = `(?:${TAG_TYPE}|(?:${TAG_TYPE})?(?:${SUB_TYPE}|${LOGIC_IS})+)`;
|
||||
export const COMPOUND_I = `(?:${TAG_TYPE_I}|(?:${TAG_TYPE_I})?(?:${SUB_TYPE})+)`;
|
||||
export const COMPOUND_WO_PSEUDO = `(?:${TAG_TYPE}|(?:${TAG_TYPE})?(?:${SUB_TYPE_WO_PSEUDO})+)`;
|
||||
export const COMPLEX = `${COMPOUND}(?:${COMBO}${COMPOUND})*`;
|
||||
export const COMPLEX_L = `${COMPOUND_L}(?:${COMBO}${COMPOUND_L})*`;
|
||||
export const HAS_COMPOUND = `has\\([\\s>]?\\s*${COMPOUND_WO_PSEUDO}\\s*\\)`;
|
||||
export const LOGIC_COMPOUND = `(?:is|not)\\(\\s*${COMPOUND_L}(?:\\s*,\\s*${COMPOUND_L})*\\s*\\)`;
|
||||
export const LOGIC_COMPLEX = `(?:is|not)\\(\\s*${COMPLEX_L}(?:\\s*,\\s*${COMPLEX_L})*\\s*\\)`;
|
||||
|
||||
/* forms and input types */
|
||||
export const FORM_PARTS = Object.freeze([
|
||||
'button',
|
||||
'input',
|
||||
'select',
|
||||
'textarea'
|
||||
]);
|
||||
export const INPUT_BUTTON = Object.freeze(['button', 'reset', 'submit']);
|
||||
export const INPUT_CHECK = Object.freeze(['checkbox', 'radio']);
|
||||
export const INPUT_DATE = Object.freeze([
|
||||
'date',
|
||||
'datetime-local',
|
||||
'month',
|
||||
'time',
|
||||
'week'
|
||||
]);
|
||||
export const INPUT_TEXT = Object.freeze([
|
||||
'email',
|
||||
'password',
|
||||
'search',
|
||||
'tel',
|
||||
'text',
|
||||
'url'
|
||||
]);
|
||||
export const INPUT_EDIT = Object.freeze([
|
||||
...INPUT_DATE,
|
||||
...INPUT_TEXT,
|
||||
'number'
|
||||
]);
|
||||
export const INPUT_LTR = Object.freeze([
|
||||
...INPUT_CHECK,
|
||||
'color',
|
||||
'date',
|
||||
'image',
|
||||
'number',
|
||||
'range',
|
||||
'time'
|
||||
]);
|
||||
|
||||
/* logical combination pseudo-classes */
|
||||
export const KEYS_LOGICAL = new Set(['has', 'is', 'not', 'where']);
|
||||
3144
node_modules/@asamuzakjp/dom-selector/src/js/finder.js
generated
vendored
Normal file
3144
node_modules/@asamuzakjp/dom-selector/src/js/finder.js
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
609
node_modules/@asamuzakjp/dom-selector/src/js/matcher.js
generated
vendored
Normal file
609
node_modules/@asamuzakjp/dom-selector/src/js/matcher.js
generated
vendored
Normal file
@@ -0,0 +1,609 @@
|
||||
/**
|
||||
* matcher.js
|
||||
*/
|
||||
|
||||
/* import */
|
||||
import { generateCSS, parseAstName, unescapeSelector } from './parser.js';
|
||||
import {
|
||||
generateException,
|
||||
getDirectionality,
|
||||
getLanguageAttribute,
|
||||
getType,
|
||||
isContentEditable,
|
||||
isCustomElement,
|
||||
isNamespaceDeclared
|
||||
} from './utility.js';
|
||||
|
||||
/* constants */
|
||||
import {
|
||||
ALPHA_NUM,
|
||||
FORM_PARTS,
|
||||
IDENT,
|
||||
INPUT_EDIT,
|
||||
LANG_PART,
|
||||
NOT_SUPPORTED_ERR,
|
||||
PS_ELEMENT_SELECTOR,
|
||||
STRING,
|
||||
SYNTAX_ERR
|
||||
} from './constant.js';
|
||||
const KEYS_FORM_PS_DISABLED = new Set([
|
||||
...FORM_PARTS,
|
||||
'fieldset',
|
||||
'optgroup',
|
||||
'option'
|
||||
]);
|
||||
const KEYS_INPUT_EDIT = new Set(INPUT_EDIT);
|
||||
const REG_LANG_VALID = new RegExp(`^(?:\\*-)?${ALPHA_NUM}${LANG_PART}$`, 'i');
|
||||
|
||||
/**
|
||||
* Validates a pseudo-element selector.
|
||||
* @param {string} astName - The name of the pseudo-element from the AST.
|
||||
* @param {string} astType - The type of the selector from the AST.
|
||||
* @param {object} [opt] - Optional parameters.
|
||||
* @param {boolean} [opt.forgive] - If true, ignores unknown pseudo-elements.
|
||||
* @param {boolean} [opt.warn] - If true, throws an error for unsupported ones.
|
||||
* @throws {DOMException} If the selector is invalid or unsupported.
|
||||
* @returns {void}
|
||||
*/
|
||||
export const matchPseudoElementSelector = (astName, astType, opt = {}) => {
|
||||
const { forgive, globalObject, warn } = opt;
|
||||
if (astType !== PS_ELEMENT_SELECTOR) {
|
||||
// Ensure the AST node is a pseudo-element selector.
|
||||
throw new TypeError(`Unexpected ast type ${getType(astType)}`);
|
||||
}
|
||||
switch (astName) {
|
||||
case 'after':
|
||||
case 'backdrop':
|
||||
case 'before':
|
||||
case 'cue':
|
||||
case 'cue-region':
|
||||
case 'first-letter':
|
||||
case 'first-line':
|
||||
case 'file-selector-button':
|
||||
case 'marker':
|
||||
case 'placeholder':
|
||||
case 'selection':
|
||||
case 'target-text': {
|
||||
// Warn if the pseudo-element is known but unsupported.
|
||||
if (warn) {
|
||||
throw generateException(
|
||||
`Unsupported pseudo-element ::${astName}`,
|
||||
NOT_SUPPORTED_ERR,
|
||||
globalObject
|
||||
);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'part':
|
||||
case 'slotted': {
|
||||
// Warn if the functional pseudo-element is known but unsupported.
|
||||
if (warn) {
|
||||
throw generateException(
|
||||
`Unsupported pseudo-element ::${astName}()`,
|
||||
NOT_SUPPORTED_ERR,
|
||||
globalObject
|
||||
);
|
||||
}
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
// Handle vendor-prefixed or unknown pseudo-elements.
|
||||
if (astName.startsWith('-webkit-')) {
|
||||
if (warn) {
|
||||
throw generateException(
|
||||
`Unsupported pseudo-element ::${astName}`,
|
||||
NOT_SUPPORTED_ERR,
|
||||
globalObject
|
||||
);
|
||||
}
|
||||
// Throw an error for unknown pseudo-elements if not forgiven.
|
||||
} else if (!forgive) {
|
||||
throw generateException(
|
||||
`Unknown pseudo-element ::${astName}`,
|
||||
SYNTAX_ERR,
|
||||
globalObject
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Matches the :dir() pseudo-class against an element's directionality.
|
||||
* @param {object} ast - The AST object for the pseudo-class.
|
||||
* @param {object} node - The element node to match against.
|
||||
* @throws {TypeError} If the AST does not contain a valid direction value.
|
||||
* @returns {boolean} - True if the directionality matches, otherwise false.
|
||||
*/
|
||||
export const matchDirectionPseudoClass = (ast, node) => {
|
||||
const { name } = ast;
|
||||
// The :dir() pseudo-class requires a direction argument (e.g., "ltr").
|
||||
if (!name) {
|
||||
const type = name === '' ? '(empty String)' : getType(name);
|
||||
throw new TypeError(`Unexpected ast type ${type}`);
|
||||
}
|
||||
// Get the computed directionality of the element.
|
||||
const dir = getDirectionality(node);
|
||||
// Compare the expected direction with the element's actual direction.
|
||||
return name === dir;
|
||||
};
|
||||
|
||||
/**
|
||||
* Matches the :lang() pseudo-class against an element's language.
|
||||
* @see https://datatracker.ietf.org/doc/html/rfc4647#section-3.3.1
|
||||
* @param {object} ast - The AST object for the pseudo-class.
|
||||
* @param {object} node - The element node to match against.
|
||||
* @returns {boolean} - True if the language matches, otherwise false.
|
||||
*/
|
||||
export const matchLanguagePseudoClass = (ast, node) => {
|
||||
// Get the effective language attribute for the current node.
|
||||
const elementLang = getLanguageAttribute(node);
|
||||
// If the element has no language, it cannot match a specific pattern.
|
||||
if (elementLang === null) {
|
||||
return false;
|
||||
}
|
||||
// Use cached regex.
|
||||
if (ast._langRegex !== undefined) {
|
||||
if (ast._langPattern === '*') {
|
||||
return elementLang !== '';
|
||||
}
|
||||
if (ast._langRegex === null) {
|
||||
return false;
|
||||
}
|
||||
return ast._langRegex.test(elementLang);
|
||||
}
|
||||
const { name, type, value } = ast;
|
||||
let langPattern;
|
||||
// Determine the language pattern from the AST.
|
||||
if (type === STRING && value) {
|
||||
langPattern = value;
|
||||
} else if (type === IDENT && name) {
|
||||
langPattern = unescapeSelector(name);
|
||||
}
|
||||
// Cache lang pattern.
|
||||
ast._langPattern = langPattern;
|
||||
// If no valid language pattern is provided, it cannot match.
|
||||
if (typeof langPattern !== 'string') {
|
||||
ast._langRegex = null;
|
||||
return false;
|
||||
}
|
||||
// Handle the universal selector '*' for :lang.
|
||||
if (langPattern === '*') {
|
||||
ast._langRegex = null;
|
||||
return elementLang !== '';
|
||||
}
|
||||
// Validate the provided language pattern structure.
|
||||
if (!REG_LANG_VALID.test(langPattern)) {
|
||||
ast._langRegex = null;
|
||||
return false;
|
||||
}
|
||||
// Build a regex for extended language range matching.
|
||||
let matcherRegex;
|
||||
if (langPattern.indexOf('-') > -1) {
|
||||
const [langMain, langSub, ...langRest] = langPattern.split('-');
|
||||
const extendedMain =
|
||||
langMain === '*' ? `${ALPHA_NUM}${LANG_PART}` : `${langMain}${LANG_PART}`;
|
||||
const extendedSub = `-${langSub}${LANG_PART}`;
|
||||
let extendedRest = '';
|
||||
for (let i = 0; i < langRest.length; i++) {
|
||||
extendedRest += `-${langRest[i]}${LANG_PART}`;
|
||||
}
|
||||
matcherRegex = new RegExp(
|
||||
`^${extendedMain}${extendedSub}${extendedRest}$`,
|
||||
'i'
|
||||
);
|
||||
} else {
|
||||
matcherRegex = new RegExp(`^${langPattern}${LANG_PART}$`, 'i');
|
||||
}
|
||||
ast._langRegex = matcherRegex;
|
||||
// Test the element's language against the constructed regex.
|
||||
return matcherRegex.test(elementLang);
|
||||
};
|
||||
|
||||
/**
|
||||
* Matches the :disabled and :enabled pseudo-classes.
|
||||
* @param {string} astName - pseudo-class name
|
||||
* @param {object} node - Element node
|
||||
* @returns {boolean} - True if matched
|
||||
*/
|
||||
export const matchDisabledPseudoClass = (astName, node) => {
|
||||
const { localName, parentNode } = node;
|
||||
if (
|
||||
!KEYS_FORM_PS_DISABLED.has(localName) &&
|
||||
!isCustomElement(node, { formAssociated: true })
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
let isDisabled = false;
|
||||
if (node.disabled || node.hasAttribute('disabled')) {
|
||||
isDisabled = true;
|
||||
} else if (localName === 'option') {
|
||||
if (
|
||||
parentNode &&
|
||||
parentNode.localName === 'optgroup' &&
|
||||
(parentNode.disabled || parentNode.hasAttribute('disabled'))
|
||||
) {
|
||||
isDisabled = true;
|
||||
}
|
||||
} else if (localName !== 'optgroup') {
|
||||
let current = parentNode;
|
||||
while (current) {
|
||||
if (
|
||||
current.localName === 'fieldset' &&
|
||||
(current.disabled || current.hasAttribute('disabled'))
|
||||
) {
|
||||
// The first <legend> in a disabled <fieldset> is not disabled.
|
||||
let legend;
|
||||
let element = current.firstElementChild;
|
||||
while (element) {
|
||||
if (element.localName === 'legend') {
|
||||
legend = element;
|
||||
break;
|
||||
}
|
||||
element = element.nextElementSibling;
|
||||
}
|
||||
if (!legend || !legend.contains(node)) {
|
||||
isDisabled = true;
|
||||
}
|
||||
// Found the containing fieldset, stop searching up.
|
||||
break;
|
||||
}
|
||||
current = current.parentNode;
|
||||
}
|
||||
}
|
||||
if (astName === 'disabled') {
|
||||
return isDisabled;
|
||||
}
|
||||
return !isDisabled;
|
||||
};
|
||||
|
||||
/**
|
||||
* Match the :read-only and :read-write pseudo-classes
|
||||
* @param {string} astName - pseudo-class name
|
||||
* @param {object} node - Element node
|
||||
* @returns {boolean} - True if matched
|
||||
*/
|
||||
export const matchReadOnlyPseudoClass = (astName, node) => {
|
||||
const { localName } = node;
|
||||
let isReadOnly = false;
|
||||
switch (localName) {
|
||||
case 'textarea':
|
||||
case 'input': {
|
||||
const isEditableInput = !node.type || KEYS_INPUT_EDIT.has(node.type);
|
||||
if (localName === 'textarea' || isEditableInput) {
|
||||
isReadOnly =
|
||||
node.readOnly ||
|
||||
node.hasAttribute('readonly') ||
|
||||
node.disabled ||
|
||||
node.hasAttribute('disabled');
|
||||
} else {
|
||||
// Non-editable input types are always read-only
|
||||
isReadOnly = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
isReadOnly = !isContentEditable(node);
|
||||
}
|
||||
}
|
||||
if (astName === 'read-only') {
|
||||
return isReadOnly;
|
||||
}
|
||||
return !isReadOnly;
|
||||
};
|
||||
|
||||
/**
|
||||
* Matches an attribute selector against an element.
|
||||
* This function handles various attribute matchers like '=', '~=', '^=', etc.,
|
||||
* and considers namespaces and case sensitivity based on document type.
|
||||
* @param {object} ast - The AST for the attribute selector.
|
||||
* @param {object} node - The element node to match against.
|
||||
* @param {object} [opt] - Optional parameters.
|
||||
* @param {boolean} [opt.check] - True if running in an internal check.
|
||||
* @param {boolean} [opt.forgive] - True to forgive certain syntax errors.
|
||||
* @returns {boolean} - True if the attribute selector matches, otherwise false.
|
||||
*/
|
||||
export const matchAttributeSelector = (ast, node, opt = {}) => {
|
||||
const {
|
||||
flags: astFlags,
|
||||
matcher: astMatcher,
|
||||
name: astName,
|
||||
value: astValue
|
||||
} = ast;
|
||||
const { check, forgive, globalObject } = opt;
|
||||
// Validate selector flags ('i' or 's').
|
||||
if (typeof astFlags === 'string' && !/^[is]$/i.test(astFlags) && !forgive) {
|
||||
const css = generateCSS(ast);
|
||||
throw generateException(
|
||||
`Invalid selector ${css}`,
|
||||
SYNTAX_ERR,
|
||||
globalObject
|
||||
);
|
||||
}
|
||||
const { attributes } = node;
|
||||
// An element with no attributes cannot match.
|
||||
if (!attributes || !attributes.length) {
|
||||
return false;
|
||||
}
|
||||
// Determine case sensitivity based on document type and flags.
|
||||
let caseInsensitive;
|
||||
if (node.ownerDocument.contentType === 'text/html') {
|
||||
if (typeof astFlags === 'string' && /^s$/i.test(astFlags)) {
|
||||
caseInsensitive = false;
|
||||
} else {
|
||||
caseInsensitive = true;
|
||||
}
|
||||
} else if (typeof astFlags === 'string' && /^i$/i.test(astFlags)) {
|
||||
caseInsensitive = true;
|
||||
} else {
|
||||
caseInsensitive = false;
|
||||
}
|
||||
// Prepare the attribute name from the selector for matching.
|
||||
let astAttrName = unescapeSelector(astName.name);
|
||||
if (caseInsensitive) {
|
||||
astAttrName = astAttrName.toLowerCase();
|
||||
}
|
||||
// A set to store the values of attributes whose names match.
|
||||
const attrValues = new Set();
|
||||
// Handle namespaced attribute names (e.g., [*|attr], [ns|attr]).
|
||||
if (astAttrName.indexOf('|') > -1) {
|
||||
const { prefix: astPrefix, localName: astLocalName } =
|
||||
parseAstName(astAttrName);
|
||||
for (const item of attributes) {
|
||||
let { name: itemName, value: itemValue } = item;
|
||||
if (caseInsensitive) {
|
||||
itemName = itemName.toLowerCase();
|
||||
itemValue = itemValue.toLowerCase();
|
||||
}
|
||||
const colonIdx = itemName.indexOf(':');
|
||||
switch (astPrefix) {
|
||||
case '': {
|
||||
if (astLocalName === itemName) {
|
||||
attrValues.add(itemValue);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case '*': {
|
||||
if (colonIdx > -1) {
|
||||
const itemLocalName = itemName
|
||||
.substring(colonIdx + 1)
|
||||
.replace(/^:/, '');
|
||||
if (itemLocalName === astLocalName) {
|
||||
attrValues.add(itemValue);
|
||||
}
|
||||
} else if (astLocalName === itemName) {
|
||||
attrValues.add(itemValue);
|
||||
}
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
if (!check) {
|
||||
if (forgive) {
|
||||
return false;
|
||||
}
|
||||
const css = generateCSS(ast);
|
||||
throw generateException(
|
||||
`Invalid selector ${css}`,
|
||||
SYNTAX_ERR,
|
||||
globalObject
|
||||
);
|
||||
}
|
||||
if (colonIdx > -1) {
|
||||
const itemPrefix = itemName.substring(0, colonIdx);
|
||||
const itemLocalName = itemName
|
||||
.substring(colonIdx + 1)
|
||||
.replace(/^:/, '');
|
||||
// Ignore the 'xml:lang' attribute.
|
||||
if (itemPrefix === 'xml' && itemLocalName === 'lang') {
|
||||
continue;
|
||||
} else if (
|
||||
astPrefix === itemPrefix &&
|
||||
astLocalName === itemLocalName
|
||||
) {
|
||||
const namespaceDeclared = isNamespaceDeclared(astPrefix, node);
|
||||
if (namespaceDeclared) {
|
||||
attrValues.add(itemValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Handle non-namespaced attribute names.
|
||||
} else {
|
||||
for (let { name: itemName, value: itemValue } of attributes) {
|
||||
if (caseInsensitive) {
|
||||
itemName = itemName.toLowerCase();
|
||||
itemValue = itemValue.toLowerCase();
|
||||
}
|
||||
const colonIdx = itemName.indexOf(':');
|
||||
if (colonIdx > -1) {
|
||||
const itemPrefix = itemName.substring(0, colonIdx);
|
||||
const itemLocalName = itemName
|
||||
.substring(colonIdx + 1)
|
||||
.replace(/^:/, '');
|
||||
// The attribute is starting with ':'.
|
||||
if (!itemPrefix && astAttrName === `:${itemLocalName}`) {
|
||||
attrValues.add(itemValue);
|
||||
// Ignore the 'xml:lang' attribute.
|
||||
} else if (itemPrefix === 'xml' && itemLocalName === 'lang') {
|
||||
continue;
|
||||
} else if (astAttrName === itemLocalName) {
|
||||
attrValues.add(itemValue);
|
||||
}
|
||||
} else if (astAttrName === itemName) {
|
||||
attrValues.add(itemValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!attrValues.size) {
|
||||
return false;
|
||||
}
|
||||
// Prepare the value from the selector's RHS for comparison.
|
||||
const { name: astIdentValue, value: astStringValue } = astValue ?? {};
|
||||
let attrValue;
|
||||
if (astIdentValue) {
|
||||
if (caseInsensitive) {
|
||||
attrValue = astIdentValue.toLowerCase().replace(/\\(?!\\)/g, '');
|
||||
} else {
|
||||
attrValue = astIdentValue.replace(/\\(?!\\)/g, '');
|
||||
}
|
||||
} else if (astStringValue) {
|
||||
if (caseInsensitive) {
|
||||
attrValue = astStringValue.toLowerCase().replace(/\\(?!\\)/g, '');
|
||||
} else {
|
||||
attrValue = astStringValue.replace(/\\(?!\\)/g, '');
|
||||
}
|
||||
} else if (astStringValue === '') {
|
||||
attrValue = astStringValue;
|
||||
}
|
||||
// Perform the final match based on the specified matcher.
|
||||
switch (astMatcher) {
|
||||
case '=': {
|
||||
return typeof attrValue === 'string' && attrValues.has(attrValue);
|
||||
}
|
||||
case '~=': {
|
||||
if (attrValue && typeof attrValue === 'string') {
|
||||
if (/\s/.test(attrValue)) {
|
||||
return false;
|
||||
}
|
||||
if (ast._tildeTarget === undefined) {
|
||||
ast._tildeTarget = ` ${attrValue} `;
|
||||
}
|
||||
const target = ast._tildeTarget;
|
||||
for (const value of attrValues) {
|
||||
if (` ${value.replace(/[\t\r\n\f]/g, ' ')} `.includes(target)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
case '|=': {
|
||||
if (attrValue && typeof attrValue === 'string') {
|
||||
for (const value of attrValues) {
|
||||
if (value === attrValue || value.startsWith(`${attrValue}-`)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
case '^=': {
|
||||
if (attrValue && typeof attrValue === 'string') {
|
||||
for (const value of attrValues) {
|
||||
if (value.startsWith(`${attrValue}`)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
case '$=': {
|
||||
if (attrValue && typeof attrValue === 'string') {
|
||||
for (const value of attrValues) {
|
||||
if (value.endsWith(`${attrValue}`)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
case '*=': {
|
||||
if (attrValue && typeof attrValue === 'string') {
|
||||
for (const value of attrValues) {
|
||||
if (value.includes(`${attrValue}`)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
case null:
|
||||
default: {
|
||||
// This case handles attribute existence checks (e.g., '[disabled]').
|
||||
return true;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* match type selector
|
||||
* @param {object} ast - AST
|
||||
* @param {object} node - Element node
|
||||
* @param {object} [opt] - options
|
||||
* @param {boolean} [opt.check] - running in internal check()
|
||||
* @param {boolean} [opt.forgive] - forgive undeclared namespace
|
||||
* @returns {boolean} - result
|
||||
*/
|
||||
export const matchTypeSelector = (ast, node, opt = {}) => {
|
||||
const astName = unescapeSelector(ast.name);
|
||||
const { localName, namespaceURI, prefix } = node;
|
||||
const { check, forgive, globalObject } = opt;
|
||||
let { prefix: astPrefix, localName: astLocalName } = parseAstName(
|
||||
astName,
|
||||
node
|
||||
);
|
||||
const isHTML =
|
||||
node.ownerDocument.contentType === 'text/html' &&
|
||||
(!namespaceURI || namespaceURI === 'http://www.w3.org/1999/xhtml');
|
||||
if (isHTML && localName === astLocalName && !astName.includes('|')) {
|
||||
return true;
|
||||
}
|
||||
const firstChar = localName.charCodeAt(0);
|
||||
const isAlphabet =
|
||||
(firstChar >= 65 && firstChar <= 90) ||
|
||||
(firstChar >= 97 && firstChar <= 122);
|
||||
if (isHTML && isAlphabet) {
|
||||
astPrefix = astPrefix.toLowerCase();
|
||||
astLocalName = astLocalName.toLowerCase();
|
||||
}
|
||||
let nodePrefix;
|
||||
let nodeLocalName;
|
||||
const colonIdx = localName.indexOf(':');
|
||||
if (colonIdx > -1) {
|
||||
nodePrefix = localName.substring(0, colonIdx);
|
||||
nodeLocalName = localName.substring(colonIdx + 1);
|
||||
} else {
|
||||
nodePrefix = prefix || '';
|
||||
nodeLocalName = localName;
|
||||
}
|
||||
const isUniversal = astLocalName === '*';
|
||||
switch (astPrefix) {
|
||||
case '': {
|
||||
return (
|
||||
!nodePrefix &&
|
||||
!namespaceURI &&
|
||||
(isUniversal || astLocalName === nodeLocalName)
|
||||
);
|
||||
}
|
||||
case '*': {
|
||||
return isUniversal || astLocalName === nodeLocalName;
|
||||
}
|
||||
default: {
|
||||
if (!check) {
|
||||
if (forgive) {
|
||||
return false;
|
||||
}
|
||||
const css = generateCSS(ast);
|
||||
throw generateException(
|
||||
`Invalid selector ${css}`,
|
||||
SYNTAX_ERR,
|
||||
globalObject
|
||||
);
|
||||
}
|
||||
const astNS = node.lookupNamespaceURI(astPrefix);
|
||||
const nodeNS = node.lookupNamespaceURI(nodePrefix);
|
||||
if (astNS === nodeNS && astPrefix === nodePrefix) {
|
||||
return isUniversal || astLocalName === nodeLocalName;
|
||||
} else if (!forgive && !astNS) {
|
||||
throw generateException(
|
||||
`Undeclared namespace ${astPrefix}`,
|
||||
SYNTAX_ERR,
|
||||
globalObject
|
||||
);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
};
|
||||
434
node_modules/@asamuzakjp/dom-selector/src/js/parser.js
generated
vendored
Normal file
434
node_modules/@asamuzakjp/dom-selector/src/js/parser.js
generated
vendored
Normal file
@@ -0,0 +1,434 @@
|
||||
/**
|
||||
* parser.js
|
||||
*/
|
||||
|
||||
/* import */
|
||||
import * as cssTree from 'css-tree';
|
||||
import { getType } from './utility.js';
|
||||
|
||||
/* constants */
|
||||
import {
|
||||
ATTR_SELECTOR,
|
||||
BIT_01,
|
||||
BIT_02,
|
||||
BIT_04,
|
||||
BIT_08,
|
||||
BIT_16,
|
||||
BIT_32,
|
||||
BIT_FFFF,
|
||||
CLASS_SELECTOR,
|
||||
DUO,
|
||||
HEX,
|
||||
ID_SELECTOR,
|
||||
KEYS_LOGICAL,
|
||||
NTH,
|
||||
PS_CLASS_SELECTOR,
|
||||
PS_ELEMENT_SELECTOR,
|
||||
SELECTOR,
|
||||
SYNTAX_ERR,
|
||||
TYPE_SELECTOR
|
||||
} from './constant.js';
|
||||
const AST_SORT_ORDER = new Map([
|
||||
[PS_ELEMENT_SELECTOR, BIT_01],
|
||||
[ID_SELECTOR, BIT_02],
|
||||
[CLASS_SELECTOR, BIT_04],
|
||||
[TYPE_SELECTOR, BIT_08],
|
||||
[ATTR_SELECTOR, BIT_16],
|
||||
[PS_CLASS_SELECTOR, BIT_32]
|
||||
]);
|
||||
const KEYS_PS_CLASS_STATE = new Set([
|
||||
'checked',
|
||||
'closed',
|
||||
'disabled',
|
||||
'empty',
|
||||
'enabled',
|
||||
'in-range',
|
||||
'indeterminate',
|
||||
'invalid',
|
||||
'open',
|
||||
'out-of-range',
|
||||
'placeholder-shown',
|
||||
'read-only',
|
||||
'read-write',
|
||||
'valid'
|
||||
]);
|
||||
const KEYS_SHADOW_HOST = new Set(['host', 'host-context']);
|
||||
const REG_EMPTY_PS_FUNC =
|
||||
/(?<=:(?:dir|has|host(?:-context)?|is|lang|not|nth-(?:last-)?(?:child|of-type)|where))\(\s+\)/g;
|
||||
const REG_SHADOW_PS_ELEMENT = /^part|slotted$/;
|
||||
const U_FFFD = '\uFFFD';
|
||||
|
||||
/**
|
||||
* Unescapes a CSS selector string.
|
||||
* @param {string} selector - The CSS selector to unescape.
|
||||
* @returns {string} The unescaped selector string.
|
||||
*/
|
||||
export const unescapeSelector = (selector = '') => {
|
||||
if (typeof selector === 'string' && selector.indexOf('\\', 0) >= 0) {
|
||||
const arr = selector.split('\\');
|
||||
const selectorItems = [arr[0]];
|
||||
const l = arr.length;
|
||||
for (let i = 1; i < l; i++) {
|
||||
const item = arr[i];
|
||||
if (item === '' && i === l - 1) {
|
||||
selectorItems.push(U_FFFD);
|
||||
} else {
|
||||
const hexExists = /^([\da-f]{1,6}\s?)/i.exec(item);
|
||||
if (hexExists) {
|
||||
const [, hex] = hexExists;
|
||||
let str;
|
||||
try {
|
||||
const low = parseInt('D800', HEX);
|
||||
const high = parseInt('DFFF', HEX);
|
||||
const deci = parseInt(hex, HEX);
|
||||
if (deci === 0 || (deci >= low && deci <= high)) {
|
||||
str = U_FFFD;
|
||||
} else {
|
||||
str = String.fromCodePoint(deci);
|
||||
}
|
||||
} catch (e) {
|
||||
str = U_FFFD;
|
||||
}
|
||||
let postStr = '';
|
||||
if (item.length > hex.length) {
|
||||
postStr = item.substring(hex.length);
|
||||
}
|
||||
selectorItems.push(`${str}${postStr}`);
|
||||
// whitespace
|
||||
} else if (/^[\n\r\f]/.test(item)) {
|
||||
selectorItems.push(`\\${item}`);
|
||||
} else {
|
||||
selectorItems.push(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
return selectorItems.join('');
|
||||
}
|
||||
return selector;
|
||||
};
|
||||
|
||||
/**
|
||||
* Preprocesses a selector string according to the specification.
|
||||
* @see https://drafts.csswg.org/css-syntax-3/#input-preprocessing
|
||||
* @param {string} value - The value to preprocess.
|
||||
* @returns {string} The preprocessed selector string.
|
||||
*/
|
||||
export const preprocess = value => {
|
||||
// Non-string values will be converted to string.
|
||||
if (typeof value !== 'string') {
|
||||
if (value === undefined || value === null) {
|
||||
return getType(value).toLowerCase();
|
||||
} else if (Array.isArray(value)) {
|
||||
return value.join(',');
|
||||
} else if (Object.hasOwn(value, 'toString')) {
|
||||
return value.toString();
|
||||
} else {
|
||||
throw new DOMException(`Invalid selector ${value}`, SYNTAX_ERR);
|
||||
}
|
||||
}
|
||||
let selector = value;
|
||||
let index = 0;
|
||||
while (index >= 0) {
|
||||
// @see https://drafts.csswg.org/selectors/#id-selectors
|
||||
index = selector.indexOf('#', index);
|
||||
if (index < 0) {
|
||||
break;
|
||||
}
|
||||
const preHash = selector.substring(0, index + 1);
|
||||
let postHash = selector.substring(index + 1);
|
||||
const codePoint = postHash.codePointAt(0);
|
||||
if (codePoint > BIT_FFFF) {
|
||||
const str = `\\${codePoint.toString(HEX)} `;
|
||||
if (postHash.length === DUO) {
|
||||
postHash = str;
|
||||
} else {
|
||||
postHash = `${str}${postHash.substring(DUO)}`;
|
||||
}
|
||||
}
|
||||
selector = `${preHash}${postHash}`;
|
||||
index++;
|
||||
}
|
||||
selector = selector
|
||||
.replace(/\f|\r\n?/g, '\n')
|
||||
.replace(/[\0\uD800-\uDFFF]|\\$/g, U_FFFD);
|
||||
if (selector === '&') {
|
||||
return '';
|
||||
}
|
||||
return selector.replace(/\x26/g, ':scope');
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates an Abstract Syntax Tree (AST) from a CSS selector string.
|
||||
* @param {string} sel - The CSS selector string.
|
||||
* @returns {object} The parsed AST object.
|
||||
*/
|
||||
export const parseSelector = sel => {
|
||||
const selector = preprocess(sel);
|
||||
// invalid selectors
|
||||
if (/^$|^\s*>|,\s*$/.test(selector)) {
|
||||
throw new DOMException(`Invalid selector ${selector}`, SYNTAX_ERR);
|
||||
}
|
||||
try {
|
||||
return cssTree.parse(selector, {
|
||||
context: 'selectorList'
|
||||
});
|
||||
} catch (e) {
|
||||
const { message } = e;
|
||||
if (
|
||||
/^(?:"\]"|Attribute selector [()\s,=~^$*|]+) is expected$/.test(
|
||||
message
|
||||
) &&
|
||||
!selector.endsWith(']')
|
||||
) {
|
||||
const index = selector.lastIndexOf('[');
|
||||
const selPart = selector.substring(index);
|
||||
if (selPart.includes('"')) {
|
||||
const quotes = selPart.match(/"/g).length;
|
||||
if (quotes % 2) {
|
||||
return parseSelector(`${selector}"]`);
|
||||
}
|
||||
return parseSelector(`${selector}]`);
|
||||
}
|
||||
return parseSelector(`${selector}]`);
|
||||
} else if (message === '")" is expected') {
|
||||
// workaround for https://github.com/csstree/csstree/issues/283
|
||||
if (REG_EMPTY_PS_FUNC.test(selector)) {
|
||||
return parseSelector(`${selector.replaceAll(REG_EMPTY_PS_FUNC, '()')}`);
|
||||
} else if (!selector.endsWith(')')) {
|
||||
return parseSelector(`${selector})`);
|
||||
} else {
|
||||
throw new DOMException(`Invalid selector ${selector}`, SYNTAX_ERR);
|
||||
}
|
||||
} else {
|
||||
throw new DOMException(`Invalid selector ${selector}`, SYNTAX_ERR);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Walks the provided AST to collect selector branches and gather information
|
||||
* about its contents.
|
||||
* @param {object} ast - The AST to traverse.
|
||||
* @param {boolean} toObject - True if converts ast to object, false otherwise.
|
||||
* @returns {{branches: Array<object>, info: object}} An object containing the selector branches and info.
|
||||
*/
|
||||
export const walkAST = (ast = {}, toObject = false) => {
|
||||
const branches = new Set();
|
||||
const info = {
|
||||
hasForgivenPseudoFunc: false,
|
||||
hasHasPseudoFunc: false,
|
||||
hasLogicalPseudoFunc: false,
|
||||
hasNotPseudoFunc: false,
|
||||
hasNthChildOfSelector: false,
|
||||
hasNestedSelector: false,
|
||||
hasStatePseudoClass: false
|
||||
};
|
||||
const opt = {
|
||||
enter(node) {
|
||||
switch (node.type) {
|
||||
case CLASS_SELECTOR: {
|
||||
if (/^-?\d/.test(node.name)) {
|
||||
throw new DOMException(
|
||||
`Invalid selector .${node.name}`,
|
||||
SYNTAX_ERR
|
||||
);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ID_SELECTOR: {
|
||||
if (/^-?\d/.test(node.name)) {
|
||||
throw new DOMException(
|
||||
`Invalid selector #${node.name}`,
|
||||
SYNTAX_ERR
|
||||
);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case PS_CLASS_SELECTOR: {
|
||||
if (KEYS_LOGICAL.has(node.name)) {
|
||||
info.hasNestedSelector = true;
|
||||
info.hasLogicalPseudoFunc = true;
|
||||
if (node.name === 'has') {
|
||||
info.hasHasPseudoFunc = true;
|
||||
} else if (node.name === 'not') {
|
||||
info.hasNotPseudoFunc = true;
|
||||
} else {
|
||||
info.hasForgivenPseudoFunc = true;
|
||||
}
|
||||
} else if (KEYS_PS_CLASS_STATE.has(node.name)) {
|
||||
info.hasStatePseudoClass = true;
|
||||
} else if (
|
||||
KEYS_SHADOW_HOST.has(node.name) &&
|
||||
Array.isArray(node.children) &&
|
||||
node.children.length
|
||||
) {
|
||||
info.hasNestedSelector = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case PS_ELEMENT_SELECTOR: {
|
||||
if (REG_SHADOW_PS_ELEMENT.test(node.name)) {
|
||||
info.hasNestedSelector = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case NTH: {
|
||||
if (node.selector) {
|
||||
info.hasNestedSelector = true;
|
||||
info.hasNthChildOfSelector = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case SELECTOR: {
|
||||
branches.add(node.children);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
}
|
||||
}
|
||||
};
|
||||
const clonedAst = cssTree.clone(ast);
|
||||
cssTree.walk(toObject ? cssTree.toPlainObject(clonedAst) : clonedAst, opt);
|
||||
if (info.hasNestedSelector === true) {
|
||||
cssTree.findAll(clonedAst, (node, item, list) => {
|
||||
if (list) {
|
||||
if (node.type === PS_CLASS_SELECTOR && KEYS_LOGICAL.has(node.name)) {
|
||||
const itemList = list.filter(i => {
|
||||
const { name, type } = i;
|
||||
return type === PS_CLASS_SELECTOR && KEYS_LOGICAL.has(name);
|
||||
});
|
||||
for (const { children } of itemList) {
|
||||
// SelectorList
|
||||
for (const { children: grandChildren } of children) {
|
||||
// Selector
|
||||
for (const { children: greatGrandChildren } of grandChildren) {
|
||||
if (branches.has(greatGrandChildren)) {
|
||||
branches.delete(greatGrandChildren);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (
|
||||
node.type === PS_CLASS_SELECTOR &&
|
||||
KEYS_SHADOW_HOST.has(node.name) &&
|
||||
Array.isArray(node.children) &&
|
||||
node.children.length
|
||||
) {
|
||||
const itemList = list.filter(i => {
|
||||
const { children, name, type } = i;
|
||||
const res =
|
||||
type === PS_CLASS_SELECTOR &&
|
||||
KEYS_SHADOW_HOST.has(name) &&
|
||||
Array.isArray(children) &&
|
||||
children.length;
|
||||
return res;
|
||||
});
|
||||
for (const { children } of itemList) {
|
||||
// Selector
|
||||
for (const { children: grandChildren } of children) {
|
||||
if (branches.has(grandChildren)) {
|
||||
branches.delete(grandChildren);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (
|
||||
node.type === PS_ELEMENT_SELECTOR &&
|
||||
REG_SHADOW_PS_ELEMENT.test(node.name)
|
||||
) {
|
||||
const itemList = list.filter(i => {
|
||||
const { name, type } = i;
|
||||
const res =
|
||||
type === PS_ELEMENT_SELECTOR && REG_SHADOW_PS_ELEMENT.test(name);
|
||||
return res;
|
||||
});
|
||||
for (const { children } of itemList) {
|
||||
// Selector
|
||||
for (const { children: grandChildren } of children) {
|
||||
if (branches.has(grandChildren)) {
|
||||
branches.delete(grandChildren);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (node.type === NTH && node.selector) {
|
||||
const itemList = list.filter(i => {
|
||||
const { selector, type } = i;
|
||||
const res = type === NTH && selector;
|
||||
return res;
|
||||
});
|
||||
for (const { selector } of itemList) {
|
||||
const { children } = selector;
|
||||
// Selector
|
||||
for (const { children: grandChildren } of children) {
|
||||
if (branches.has(grandChildren)) {
|
||||
branches.delete(grandChildren);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
return {
|
||||
info,
|
||||
branches: [...branches]
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Comparison function for sorting AST nodes based on specificity.
|
||||
* @param {object} a - The first AST node.
|
||||
* @param {object} b - The second AST node.
|
||||
* @returns {number} -1, 0 or 1, depending on the sort order.
|
||||
*/
|
||||
export const compareASTNodes = (a, b) => {
|
||||
const bitA = AST_SORT_ORDER.get(a.type);
|
||||
const bitB = AST_SORT_ORDER.get(b.type);
|
||||
if (bitA === bitB) {
|
||||
return 0;
|
||||
} else if (bitA > bitB) {
|
||||
return 1;
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Sorts a collection of AST nodes based on CSS specificity rules.
|
||||
* @param {Array<object>} asts - A collection of AST nodes to sort.
|
||||
* @returns {Array<object>} A new array containing the sorted AST nodes.
|
||||
*/
|
||||
export const sortAST = asts => {
|
||||
const arr = [...asts];
|
||||
if (arr.length > 1) {
|
||||
arr.sort(compareASTNodes);
|
||||
}
|
||||
return arr;
|
||||
};
|
||||
|
||||
/**
|
||||
* Parses a type selector's name, which may include a namespace prefix.
|
||||
* @param {string} selector - The type selector name (e.g., 'ns|E' or 'E').
|
||||
* @returns {{prefix: string, localName: string}} An object with `prefix` and
|
||||
* `localName` properties.
|
||||
*/
|
||||
export const parseAstName = selector => {
|
||||
let prefix;
|
||||
let localName;
|
||||
if (selector && typeof selector === 'string') {
|
||||
if (selector.indexOf('|') > -1) {
|
||||
[prefix, localName] = selector.split('|');
|
||||
} else {
|
||||
prefix = '*';
|
||||
localName = selector;
|
||||
}
|
||||
} else {
|
||||
throw new DOMException(`Invalid selector ${selector}`, SYNTAX_ERR);
|
||||
}
|
||||
return {
|
||||
prefix,
|
||||
localName
|
||||
};
|
||||
};
|
||||
|
||||
/* Re-exported from css-tree. */
|
||||
export { find as findAST, generate as generateCSS } from 'css-tree';
|
||||
1113
node_modules/@asamuzakjp/dom-selector/src/js/utility.js
generated
vendored
Normal file
1113
node_modules/@asamuzakjp/dom-selector/src/js/utility.js
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
15
node_modules/@asamuzakjp/dom-selector/types/index.d.ts
generated
vendored
Normal file
15
node_modules/@asamuzakjp/dom-selector/types/index.d.ts
generated
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
export class DOMSelector {
|
||||
constructor(window: Window, document: Document, opt?: object);
|
||||
clear: () => void;
|
||||
check: (selector: string, node: Element, opt?: object) => CheckResult;
|
||||
matches: (selector: string, node: Element, opt?: object) => boolean;
|
||||
closest: (selector: string, node: Element, opt?: object) => Element | null;
|
||||
querySelector: (selector: string, node: Document | DocumentFragment | Element, opt?: object) => Element | null;
|
||||
querySelectorAll: (selector: string, node: Document | DocumentFragment | Element, opt?: object) => Array<Element>;
|
||||
#private;
|
||||
}
|
||||
export type CheckResult = {
|
||||
match: boolean;
|
||||
pseudoElement: string | null;
|
||||
ast: object | null;
|
||||
};
|
||||
12
node_modules/@asamuzakjp/dom-selector/types/js/cache.d.ts
generated
vendored
Normal file
12
node_modules/@asamuzakjp/dom-selector/types/js/cache.d.ts
generated
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
export class GenerationalCache {
|
||||
constructor(max: number);
|
||||
set max(value: number);
|
||||
get max(): number;
|
||||
get size(): number;
|
||||
get(key: any): any;
|
||||
set(key: any, value: any): void;
|
||||
has(key: any): boolean;
|
||||
delete(key: any): void;
|
||||
clear(): void;
|
||||
#private;
|
||||
}
|
||||
77
node_modules/@asamuzakjp/dom-selector/types/js/constant.d.ts
generated
vendored
Normal file
77
node_modules/@asamuzakjp/dom-selector/types/js/constant.d.ts
generated
vendored
Normal file
@@ -0,0 +1,77 @@
|
||||
export const ATRULE: "Atrule";
|
||||
export const ATTR_SELECTOR: "AttributeSelector";
|
||||
export const CLASS_SELECTOR: "ClassSelector";
|
||||
export const COMBINATOR: "Combinator";
|
||||
export const IDENT: "Identifier";
|
||||
export const ID_SELECTOR: "IdSelector";
|
||||
export const NOT_SUPPORTED_ERR: "NotSupportedError";
|
||||
export const NTH: "Nth";
|
||||
export const OPERATOR: "Operator";
|
||||
export const PS_CLASS_SELECTOR: "PseudoClassSelector";
|
||||
export const PS_ELEMENT_SELECTOR: "PseudoElementSelector";
|
||||
export const RULE: "Rule";
|
||||
export const SCOPE: "Scope";
|
||||
export const SELECTOR: "Selector";
|
||||
export const SELECTOR_LIST: "SelectorList";
|
||||
export const STRING: "String";
|
||||
export const SYNTAX_ERR: "SyntaxError";
|
||||
export const TARGET_ALL: "all";
|
||||
export const TARGET_FIRST: "first";
|
||||
export const TARGET_LINEAL: "lineal";
|
||||
export const TARGET_SELF: "self";
|
||||
export const TYPE_SELECTOR: "TypeSelector";
|
||||
export const BIT_01: 1;
|
||||
export const BIT_02: 2;
|
||||
export const BIT_04: 4;
|
||||
export const BIT_08: 8;
|
||||
export const BIT_16: 16;
|
||||
export const BIT_32: 32;
|
||||
export const BIT_FFFF: 65535;
|
||||
export const DUO: 2;
|
||||
export const HEX: 16;
|
||||
export const TYPE_FROM: 8;
|
||||
export const TYPE_TO: -1;
|
||||
export const ELEMENT_NODE: 1;
|
||||
export const TEXT_NODE: 3;
|
||||
export const DOCUMENT_NODE: 9;
|
||||
export const DOCUMENT_FRAGMENT_NODE: 11;
|
||||
export const DOCUMENT_POSITION_PRECEDING: 2;
|
||||
export const DOCUMENT_POSITION_CONTAINS: 8;
|
||||
export const DOCUMENT_POSITION_CONTAINED_BY: 16;
|
||||
export const SHOW_ALL: 4294967295;
|
||||
export const SHOW_CONTAINER: 1281;
|
||||
export const SHOW_DOCUMENT: 256;
|
||||
export const SHOW_DOCUMENT_FRAGMENT: 1024;
|
||||
export const SHOW_ELEMENT: 1;
|
||||
export const ALPHA_NUM: "[A-Z\\d]+";
|
||||
export const CHILD_IDX: "(?:first|last|only)-(?:child|of-type)";
|
||||
export const DIGIT: "(?:0|[1-9]\\d*)";
|
||||
export const LANG_PART: "(?:-[A-Z\\d]+)*";
|
||||
export const PSEUDO_CLASS: "(?:any-)?link|(?:first|last|only)-(?:child|of-type)|checked|empty|indeterminate|read-(?:only|write)|target";
|
||||
export const ANB: "[+-]?(?:(?:0|[1-9]\\d*)n?|n)|(?:[+-]?(?:0|[1-9]\\d*))?n\\s*[+-]\\s*(?:0|[1-9]\\d*)";
|
||||
export const COMBO: "\\s?[\\s>~+]\\s?";
|
||||
export const DESCEND: "\\s?[\\s>]\\s?";
|
||||
export const SIBLING: "\\s?[+~]\\s?";
|
||||
export const LOGIC_IS: ":is\\(\\s*[^)]+\\s*\\)";
|
||||
export const N_TH: "nth-(?:last-)?(?:child|of-type)\\(\\s*(?:even|odd|[+-]?(?:(?:0|[1-9]\\d*)n?|n)|(?:[+-]?(?:0|[1-9]\\d*))?n\\s*[+-]\\s*(?:0|[1-9]\\d*))\\s*\\)";
|
||||
export const SUB_TYPE: "\\[[^|\\]]+\\]|[#.:][\\w-]+";
|
||||
export const SUB_TYPE_WO_PSEUDO: "\\[[^|\\]]+\\]|[#.][\\w-]+";
|
||||
export const TAG_TYPE: "\\*|[A-Za-z][\\w-]*";
|
||||
export const TAG_TYPE_I: "\\*|[A-Z][\\w-]*";
|
||||
export const COMPOUND: "(?:\\*|[A-Za-z][\\w-]*|(?:\\*|[A-Za-z][\\w-]*)?(?:\\[[^|\\]]+\\]|[#.:][\\w-]+)+)";
|
||||
export const COMPOUND_L: "(?:\\*|[A-Za-z][\\w-]*|(?:\\*|[A-Za-z][\\w-]*)?(?:\\[[^|\\]]+\\]|[#.:][\\w-]+|:is\\(\\s*[^)]+\\s*\\))+)";
|
||||
export const COMPOUND_I: "(?:\\*|[A-Z][\\w-]*|(?:\\*|[A-Z][\\w-]*)?(?:\\[[^|\\]]+\\]|[#.:][\\w-]+)+)";
|
||||
export const COMPOUND_WO_PSEUDO: "(?:\\*|[A-Za-z][\\w-]*|(?:\\*|[A-Za-z][\\w-]*)?(?:\\[[^|\\]]+\\]|[#.][\\w-]+)+)";
|
||||
export const COMPLEX: "(?:\\*|[A-Za-z][\\w-]*|(?:\\*|[A-Za-z][\\w-]*)?(?:\\[[^|\\]]+\\]|[#.:][\\w-]+)+)(?:\\s?[\\s>~+]\\s?(?:\\*|[A-Za-z][\\w-]*|(?:\\*|[A-Za-z][\\w-]*)?(?:\\[[^|\\]]+\\]|[#.:][\\w-]+)+))*";
|
||||
export const COMPLEX_L: "(?:\\*|[A-Za-z][\\w-]*|(?:\\*|[A-Za-z][\\w-]*)?(?:\\[[^|\\]]+\\]|[#.:][\\w-]+|:is\\(\\s*[^)]+\\s*\\))+)(?:\\s?[\\s>~+]\\s?(?:\\*|[A-Za-z][\\w-]*|(?:\\*|[A-Za-z][\\w-]*)?(?:\\[[^|\\]]+\\]|[#.:][\\w-]+|:is\\(\\s*[^)]+\\s*\\))+))*";
|
||||
export const HAS_COMPOUND: "has\\([\\s>]?\\s*(?:\\*|[A-Za-z][\\w-]*|(?:\\*|[A-Za-z][\\w-]*)?(?:\\[[^|\\]]+\\]|[#.][\\w-]+)+)\\s*\\)";
|
||||
export const LOGIC_COMPOUND: "(?:is|not)\\(\\s*(?:\\*|[A-Za-z][\\w-]*|(?:\\*|[A-Za-z][\\w-]*)?(?:\\[[^|\\]]+\\]|[#.:][\\w-]+|:is\\(\\s*[^)]+\\s*\\))+)(?:\\s*,\\s*(?:\\*|[A-Za-z][\\w-]*|(?:\\*|[A-Za-z][\\w-]*)?(?:\\[[^|\\]]+\\]|[#.:][\\w-]+|:is\\(\\s*[^)]+\\s*\\))+))*\\s*\\)";
|
||||
export const LOGIC_COMPLEX: "(?:is|not)\\(\\s*(?:\\*|[A-Za-z][\\w-]*|(?:\\*|[A-Za-z][\\w-]*)?(?:\\[[^|\\]]+\\]|[#.:][\\w-]+|:is\\(\\s*[^)]+\\s*\\))+)(?:\\s?[\\s>~+]\\s?(?:\\*|[A-Za-z][\\w-]*|(?:\\*|[A-Za-z][\\w-]*)?(?:\\[[^|\\]]+\\]|[#.:][\\w-]+|:is\\(\\s*[^)]+\\s*\\))+))*(?:\\s*,\\s*(?:\\*|[A-Za-z][\\w-]*|(?:\\*|[A-Za-z][\\w-]*)?(?:\\[[^|\\]]+\\]|[#.:][\\w-]+|:is\\(\\s*[^)]+\\s*\\))+)(?:\\s?[\\s>~+]\\s?(?:\\*|[A-Za-z][\\w-]*|(?:\\*|[A-Za-z][\\w-]*)?(?:\\[[^|\\]]+\\]|[#.:][\\w-]+|:is\\(\\s*[^)]+\\s*\\))+))*)*\\s*\\)";
|
||||
export const FORM_PARTS: readonly string[];
|
||||
export const INPUT_BUTTON: readonly string[];
|
||||
export const INPUT_CHECK: readonly string[];
|
||||
export const INPUT_DATE: readonly string[];
|
||||
export const INPUT_TEXT: readonly string[];
|
||||
export const INPUT_EDIT: readonly string[];
|
||||
export const INPUT_LTR: readonly string[];
|
||||
export const KEYS_LOGICAL: Set<string>;
|
||||
62
node_modules/@asamuzakjp/dom-selector/types/js/finder.d.ts
generated
vendored
Normal file
62
node_modules/@asamuzakjp/dom-selector/types/js/finder.d.ts
generated
vendored
Normal file
@@ -0,0 +1,62 @@
|
||||
export class Finder {
|
||||
constructor(window: object);
|
||||
onError: (e: Error, opt?: {
|
||||
noexcept?: boolean | undefined;
|
||||
}) => void;
|
||||
setup: (selector: string, node: object, opt?: {
|
||||
check?: boolean | undefined;
|
||||
noexcept?: boolean | undefined;
|
||||
warn?: boolean | undefined;
|
||||
}) => object;
|
||||
clearResults: (all?: boolean) => void;
|
||||
private _handleFocusEvent;
|
||||
private _handleKeyboardEvent;
|
||||
private _handleMouseEvent;
|
||||
private _registerEventListeners;
|
||||
private _processSelectorBranches;
|
||||
private _correspond;
|
||||
private _createTreeWalker;
|
||||
private _getSelectorBranches;
|
||||
private _getFilteredChildren;
|
||||
private _collectNthChild;
|
||||
private _collectNthOfType;
|
||||
private _matchAnPlusB;
|
||||
private _matchHasPseudoFunc;
|
||||
private _evaluateHasPseudo;
|
||||
private _matchLogicalPseudoFunc;
|
||||
private _matchPseudoClassSelector;
|
||||
private _evaluateHostPseudo;
|
||||
private _evaluateHostContextPseudo;
|
||||
private _matchShadowHostPseudoClass;
|
||||
private _matchSelectorForElement;
|
||||
private _matchSelectorForShadowRoot;
|
||||
private _matchSelector;
|
||||
private _matchLeaves;
|
||||
private _getFilterLeaves;
|
||||
private _traverseAllDescendants;
|
||||
private _findDescendantNodes;
|
||||
private _collectCombinatorMatches;
|
||||
private _matchCombinator;
|
||||
private _traverseAndCollectNodes;
|
||||
private _findPrecede;
|
||||
private _findNodeWalker;
|
||||
private _matchSelf;
|
||||
private _findLineal;
|
||||
private _findEntryNodesForPseudoElement;
|
||||
private _findEntryNodesForId;
|
||||
private _findEntryNodesForClass;
|
||||
private _findEntryNodesForType;
|
||||
private _findEntryNodesForOther;
|
||||
private _findEntryNodes;
|
||||
private _determineTraversalStrategy;
|
||||
private _processPendingItems;
|
||||
private _collectNodes;
|
||||
private _getCombinedNodes;
|
||||
private _matchNodeNext;
|
||||
private _matchNodePrev;
|
||||
private _processComplexBranchAll;
|
||||
private _processComplexBranchFirst;
|
||||
find: (targetType: string) => Set<object>;
|
||||
getAST: (selector: string) => object;
|
||||
#private;
|
||||
}
|
||||
16
node_modules/@asamuzakjp/dom-selector/types/js/matcher.d.ts
generated
vendored
Normal file
16
node_modules/@asamuzakjp/dom-selector/types/js/matcher.d.ts
generated
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
export function matchPseudoElementSelector(astName: string, astType: string, opt?: {
|
||||
forgive?: boolean | undefined;
|
||||
warn?: boolean | undefined;
|
||||
}): void;
|
||||
export function matchDirectionPseudoClass(ast: object, node: object): boolean;
|
||||
export function matchLanguagePseudoClass(ast: object, node: object): boolean;
|
||||
export function matchDisabledPseudoClass(astName: string, node: object): boolean;
|
||||
export function matchReadOnlyPseudoClass(astName: string, node: object): boolean;
|
||||
export function matchAttributeSelector(ast: object, node: object, opt?: {
|
||||
check?: boolean | undefined;
|
||||
forgive?: boolean | undefined;
|
||||
}): boolean;
|
||||
export function matchTypeSelector(ast: object, node: object, opt?: {
|
||||
check?: boolean | undefined;
|
||||
forgive?: boolean | undefined;
|
||||
}): boolean;
|
||||
14
node_modules/@asamuzakjp/dom-selector/types/js/parser.d.ts
generated
vendored
Normal file
14
node_modules/@asamuzakjp/dom-selector/types/js/parser.d.ts
generated
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
export function unescapeSelector(selector?: string): string;
|
||||
export function preprocess(value: string): string;
|
||||
export function parseSelector(sel: string): object;
|
||||
export function walkAST(ast?: object, toObject?: boolean): {
|
||||
branches: Array<object>;
|
||||
info: object;
|
||||
};
|
||||
export function compareASTNodes(a: object, b: object): number;
|
||||
export function sortAST(asts: Array<object>): Array<object>;
|
||||
export function parseAstName(selector: string): {
|
||||
prefix: string;
|
||||
localName: string;
|
||||
};
|
||||
export { find as findAST, generate as generateCSS } from "css-tree";
|
||||
30
node_modules/@asamuzakjp/dom-selector/types/js/utility.d.ts
generated
vendored
Normal file
30
node_modules/@asamuzakjp/dom-selector/types/js/utility.d.ts
generated
vendored
Normal file
@@ -0,0 +1,30 @@
|
||||
export function getType(o: object): string;
|
||||
export function verifyArray(arr: any[], type: string): any[];
|
||||
export function generateException(msg: string, name: string, globalObject?: object): DOMException;
|
||||
export function findNestedHas(leaf: object): object | null;
|
||||
export function findLogicalWithNestedHas(leaf: object): object | null;
|
||||
export function filterNodesByAnB(nodes: Array<object>, anb: {
|
||||
a: number;
|
||||
b: number;
|
||||
reverse?: boolean | undefined;
|
||||
}): Array<object>;
|
||||
export function resolveContent(node: object): Array<object | boolean>;
|
||||
export function traverseNode(node: object, walker: object, force?: boolean): object | null;
|
||||
export function isCustomElement(node: object, opt?: object): boolean;
|
||||
export function getSlottedTextContent(node: object): string | null;
|
||||
export function getDirectionality(node: object): string | null;
|
||||
export function getLanguageAttribute(node: object): string | null;
|
||||
export function isContentEditable(node: object): boolean;
|
||||
export function isVisible(node: object): boolean;
|
||||
export function isFocusVisible(node: object): boolean;
|
||||
export function isFocusableArea(node: object): boolean;
|
||||
export function isFocusable(node: object): boolean;
|
||||
export function getNamespaceURI(ns: string, node: object): string | null;
|
||||
export function isNamespaceDeclared(ns?: string, node?: object): boolean;
|
||||
export function isPreceding(nodeA: object, nodeB: object): boolean;
|
||||
export function compareNodes(a: object, b: object): number;
|
||||
export function sortNodes(nodes?: Array<object> | Set<object>): Array<object>;
|
||||
export function concatNestedSelectors(selectors: Array<Array<string>>): string;
|
||||
export function extractNestedSelectors(css: string): Array<Array<string>>;
|
||||
export function initNwsapi(window: object, document: object): object;
|
||||
export function filterSelector(selector: string, target: string): boolean;
|
||||
22
node_modules/@asamuzakjp/nwsapi/LICENSE
generated
vendored
Normal file
22
node_modules/@asamuzakjp/nwsapi/LICENSE
generated
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
Copyright (c) 2007-2019 Diego Perini (http://www.iport.it/)
|
||||
|
||||
Permission is hereby granted, free of charge, to any person
|
||||
obtaining a copy of this software and associated documentation
|
||||
files (the "Software"), to deal in the Software without
|
||||
restriction, including without limitation the rights to use,
|
||||
copy, modify, merge, publish, distribute, sublicense, and/or
|
||||
sell copies of the Software, and to permit persons to whom the
|
||||
Software is furnished to do so, subject to the following
|
||||
conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
||||
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
||||
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||
OTHER DEALINGS IN THE SOFTWARE.
|
||||
132
node_modules/@asamuzakjp/nwsapi/README.md
generated
vendored
Normal file
132
node_modules/@asamuzakjp/nwsapi/README.md
generated
vendored
Normal file
@@ -0,0 +1,132 @@
|
||||
# [NWSAPI](http://dperini.github.io/nwsapi/)
|
||||
|
||||
Fast CSS Selectors API Engine
|
||||
|
||||
   
|
||||
|
||||
NWSAPI is the development progress of [NWMATCHER](https://github.com/dperini/nwmatcher) aiming at [Selectors Level 4](https://www.w3.org/TR/selectors-4/) conformance. It has been completely reworked to be easily extended and maintained. It is a right-to-left selector parser and compiler written in pure Javascript with no external dependencies. It was initially thought as a cross browser library to improve event delegation and web page scraping in various frameworks but it has become a popular replacement of the native CSS selection and matching functionality in newer browsers and headless environments.
|
||||
|
||||
It uses [regular expressions](https://en.wikipedia.org/wiki/Regular_expression) to parse CSS selector strings and [metaprogramming](https://en.wikipedia.org/wiki/Metaprogramming) to transforms these selector strings into Javascript function resolvers. This process is executed only once for each selector string allowing memoization of the function resolvers and achieving unmatched performances.
|
||||
|
||||
## Installation
|
||||
|
||||
To include NWSAPI in a standard web page:
|
||||
|
||||
```html
|
||||
<script type="text/javascript" src="nwsapi.js"></script>
|
||||
```
|
||||
|
||||
To include NWSAPI in a standard web page and automatically replace the native QSA:
|
||||
|
||||
```html
|
||||
<script type="text/javascript" src="nwsapi.js" onload="NW.Dom.install()"></script>
|
||||
```
|
||||
|
||||
To use NWSAPI with Node.js:
|
||||
|
||||
```
|
||||
$ npm install nwsapi
|
||||
```
|
||||
|
||||
NWSAPI currently supports browsers (as a global, `NW.Dom`) and headless environments (as a CommonJS module).
|
||||
|
||||
|
||||
## Supported Selectors
|
||||
|
||||
Here is a list of all the CSS2/CSS3/CSS4 [Supported selectors](https://github.com/dperini/nwsapi/wiki/CSS-supported-selectors).
|
||||
|
||||
|
||||
## Features and Compliance
|
||||
|
||||
You can read more about NWSAPI [features and compliance](https://github.com/dperini/nwsapi/wiki/Features-and-compliance) on the wiki.
|
||||
|
||||
|
||||
## API
|
||||
|
||||
### DOM Selection
|
||||
|
||||
#### `ancestor( selector, context, callback )`
|
||||
|
||||
Returns a reference to the nearest ancestor element matching `selector`, starting at `context`. Returns `null` if no element is found. If `callback` is provided, it is invoked for the matched element.
|
||||
|
||||
#### `first( selector, context, callback )`
|
||||
|
||||
Returns a reference to the first element matching `selector`, starting at `context`. Returns `null` if no element matches. If `callback` is provided, it is invoked for the matched element.
|
||||
|
||||
#### `match( selector, element, callback )`
|
||||
|
||||
Returns `true` if `element` matches `selector`, starting at `context`; returns `false` otherwise. If `callback` is provided, it is invoked for the matched element.
|
||||
|
||||
#### `select( selector, context, callback )`
|
||||
|
||||
Returns an array of all the elements matching `selector`, starting at `context`; returns empty `Array` otherwise. If `callback` is provided, it is invoked for each matching element.
|
||||
|
||||
|
||||
### DOM Helpers
|
||||
|
||||
#### `byId( id, from )`
|
||||
|
||||
Returns a reference to the first element with ID `id`, optionally filtered to descendants of the element `from`.
|
||||
|
||||
#### `byTag( tag, from )`
|
||||
|
||||
Returns an array of elements having the specified tag name `tag`, optionally filtered to descendants of the element `from`.
|
||||
|
||||
#### `byClass( class, from )`
|
||||
|
||||
Returns an array of elements having the specified class name `class`, optionally filtered to descendants of the element `from`.
|
||||
|
||||
|
||||
### Engine Configuration
|
||||
|
||||
#### `configure( options )`
|
||||
|
||||
The following is the list of currently available configuration options, their default values and descriptions, they are boolean flags that can be set to `true` or `false`:
|
||||
|
||||
* `IDS_DUPES`: true - true to allow using multiple elements having the same id, false to disallow
|
||||
* `LIVECACHE`: true - true for caching both results and resolvers, false for caching only resolvers
|
||||
* `MIXEDCASE`: true - true to match tag names case insensitive, false to match using case sensitive
|
||||
* `LOGERRORS`: true - true to print errors and warnings to the console, false to mute both of them
|
||||
|
||||
|
||||
### Examples on extending the basic functionalities
|
||||
|
||||
#### `configure( { <configuration-flag>: [ true | false ] } )`
|
||||
|
||||
Disable logging errors/warnings to console, disallow duplicate ids. Example:
|
||||
|
||||
```js
|
||||
NW.Dom.configure( { LOGERRORS: false, IDS_DUPES: false } );
|
||||
```
|
||||
NOTE: NW.Dom.configure() without parameters return the current configuration.
|
||||
|
||||
#### `registerCombinator( symbol, resolver )`
|
||||
|
||||
Registers a new symbol and its matching resolver in the combinators table. Example:
|
||||
|
||||
```js
|
||||
NW.Dom.registerCombinator( '^', 'e.parentElement' );
|
||||
```
|
||||
|
||||
#### `registerOperator( symbol, resolver )`
|
||||
|
||||
Registers a new symbol and its matching resolver in the attribute operators table. Example:
|
||||
|
||||
```js
|
||||
NW.Dom.registerOperator( '!=', { p1: '^', p2: '$', p3: 'false' } );
|
||||
```
|
||||
|
||||
#### `registerSelector( name, rexp, func )`
|
||||
|
||||
Registers a new selector, the matching RE and the resolver function, in the selectors table. Example:
|
||||
|
||||
```js
|
||||
NW.Dom.registerSelector('Controls', /^\:(control)(.*)/i,
|
||||
(function(global) {
|
||||
return function(match, source, mode, callback) {
|
||||
var status = true;
|
||||
source = 'if(/^(button|input|select|textarea)/i.test(e.nodeName)){' + source + '}';
|
||||
return { 'source': source, 'status': status };
|
||||
};
|
||||
})(this));
|
||||
```
|
||||
43
node_modules/@asamuzakjp/nwsapi/package.json
generated
vendored
Normal file
43
node_modules/@asamuzakjp/nwsapi/package.json
generated
vendored
Normal file
@@ -0,0 +1,43 @@
|
||||
{
|
||||
"name": "@asamuzakjp/nwsapi",
|
||||
"version": "2.3.9",
|
||||
"description": "Fast CSS Selectors API Engine",
|
||||
"homepage": "http://javascript.nwbox.com/nwsapi/",
|
||||
"main": "./src/nwsapi",
|
||||
"keywords": [
|
||||
"css",
|
||||
"css3",
|
||||
"css4",
|
||||
"matcher",
|
||||
"selector"
|
||||
],
|
||||
"licenses": [
|
||||
{
|
||||
"type": "MIT",
|
||||
"url": "http://javascript.nwbox.com/nwsapi/MIT-LICENSE"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"author": {
|
||||
"name": "Diego Perini",
|
||||
"email": "diego.perini@gmail.com",
|
||||
"web": "http://www.iport.it/"
|
||||
},
|
||||
"maintainers": [
|
||||
{
|
||||
"name": "Diego Perini",
|
||||
"email": "diego.perini@gmail.com",
|
||||
"web": "http://www.iport.it/"
|
||||
}
|
||||
],
|
||||
"bugs": {
|
||||
"url": "http://github.com/dperini/nwsapi/issues"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git://github.com/dperini/nwsapi.git"
|
||||
},
|
||||
"scripts": {
|
||||
"lint": "eslint ./src/nwsapi.js"
|
||||
}
|
||||
}
|
||||
1855
node_modules/@asamuzakjp/nwsapi/src/nwsapi.js
generated
vendored
Normal file
1855
node_modules/@asamuzakjp/nwsapi/src/nwsapi.js
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
19
node_modules/@bramus/specificity/LICENSE
generated
vendored
Normal file
19
node_modules/@bramus/specificity/LICENSE
generated
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
Copyright (c) 2022 Bramus Van Damme - https://www.bram.us/
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is furnished
|
||||
to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
164
node_modules/@bramus/specificity/README.md
generated
vendored
Normal file
164
node_modules/@bramus/specificity/README.md
generated
vendored
Normal file
@@ -0,0 +1,164 @@
|
||||
[](https://codepen.io/bramus/pen/WNXyoYm)
|
||||
|
||||
# Specificity
|
||||
|
||||
`@bramus/specificity` is a package to calculate the specificity of CSS Selectors. It also includes some convenience functions to compare, sort, and filter an array of specificity values.
|
||||
|
||||
Supports [Selectors Level 4](https://www.w3.org/TR/selectors-4/), including those special cases `:is()`, `:where()`, `:not()`, etc.
|
||||
|
||||
Demo: [https://codepen.io/bramus/pen/WNXyoYm](https://codepen.io/bramus/pen/WNXyoYm)
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
npm i @bramus/specificity
|
||||
```
|
||||
|
||||
## Usage / Example
|
||||
|
||||
At its core, `@bramus/specificity` exposes a `Specificity` class. Its static `calculate` method can be used to calculate the specificity of a given CSS [Selector List](https://www.w3.org/TR/selectors-4/#grouping) string.
|
||||
|
||||
```js
|
||||
import Specificity from '@bramus/specificity';
|
||||
|
||||
const specificities = Specificity.calculate('header:where(#top) nav li:nth-child(2n), #doormat');
|
||||
```
|
||||
|
||||
Because `calculate` accepts a [Selector List](https://www.w3.org/TR/selectors-4/#grouping) — which can contain more than 1 [Selector](https://www.w3.org/TR/selectors-4/#selector) — it will always return an array, with each entry being a `Specificity` instance — one per found selector.
|
||||
|
||||
```js
|
||||
const specificities = Specificity.calculate('header:where(#top) nav li:nth-child(2n), #doormat');
|
||||
specificities.map((s) => s.toString()); // ~> ["(0,1,3)","(1,0,0)"]
|
||||
```
|
||||
|
||||
💡 If you know you’re passing only a single Selector into `calculate()`, you can use JavaScript’s built-in destructuring to keep your variable names clean.
|
||||
|
||||
```js
|
||||
const [s] = Specificity.calculate('header:where(#top) nav li:nth-child(2n)');
|
||||
s.toString(); // ~> "(0,1,3)"
|
||||
```
|
||||
|
||||
💡 Under the hood, `@bramus/specificity` uses [CSSTree](https://github.com/csstree/csstree) to do the parsing of strings to Selectors. As a result, the `calculate` method also accepts a [CSSTree AST](https://github.com/csstree/csstree/blob/master/docs/ast.md) of the types `Selector` and `SelectorList`.
|
||||
|
||||
If you have a pre-parsed CSSTree AST of the type `Selector` you can pass it into `Specificity.calculateForAST()`. It [performs slightly better](#benchmark) than `Specificity.calculate()` as it needs to check fewer things. It differs from `Specificity.calculate()` in that it does not return an array of `Specificity` instances but only a single value.
|
||||
|
||||
## The Return Format
|
||||
|
||||
A calculated specificity is represented as an instance of the `Specificity` class. The `Specificity` class includes methods to get the specificity value in a certain format, along with some convenience methods to compare it against other instances.
|
||||
|
||||
```js
|
||||
// 🚀 Thunderbirds are go!
|
||||
import Specificity from '@bramus/specificity';
|
||||
|
||||
// ✨ Calculate specificity for each Selector in the given Selector List
|
||||
const specificities = Specificity.calculate('header:where(#top) nav li:nth-child(2n), #doormat');
|
||||
|
||||
// 🚚 The values in the array are instances of the Specificity class
|
||||
const s = specificities[0]; // Instance of Specificity
|
||||
|
||||
// 👀 Read the specificity value using one of its accessors
|
||||
s.value; // { a: 0, b: 1, c: 3 }
|
||||
s.a; // 0
|
||||
s.b; // 1
|
||||
s.c; // 3
|
||||
|
||||
// 🛠 Convert the calculated value to various formats using one of the toXXX() instance methods
|
||||
s.toString(); // "(0,1,3)"
|
||||
s.toArray(); // [0, 1, 3]
|
||||
s.toObject(); // { a: 0, b: 1, c: 3 }
|
||||
|
||||
// 💡 Extract the matched selector string
|
||||
s.selectorString(); // "header:where(#top) nav li:nth-child(2n)"
|
||||
|
||||
// 🔀 Use one of its instance comparison methods to compare it to another Specificity instance
|
||||
s.isEqualTo(specificities[1]); // false
|
||||
s.isGreaterThan(specificities[1]); // false
|
||||
s.isLessThan(specificities[1]); // true
|
||||
|
||||
// 💻 Don’t worry about JSON.stringify()
|
||||
JSON.stringify(s);
|
||||
// {
|
||||
// "selector": 'header:where(#top) nav li:nth-child(2n)',
|
||||
// "asObject": { "a": 0, "b": 1, "c": 3 },
|
||||
// "asArray": [0, 1, 3],
|
||||
// "asString": "(0,1,3)",
|
||||
// }
|
||||
```
|
||||
|
||||
## Utility Functions (Static Methods)
|
||||
|
||||
This package also exposes some utility functions to work with specificities. These utility functions are all exposed as static methods on the `Specificity` class.
|
||||
|
||||
- Comparing:
|
||||
|
||||
- `Specificity.compare(s1, s2)`: Compares s1 to s2. Returns a value that can be:
|
||||
- `> 0` = Sort s2 before s1 _(i.e. s1 is more specific than s2)_
|
||||
- `0` = Keep original order of s1 and s2 _(i.e. s1 and s2 are equally specific)_
|
||||
- `< 0` = Sort s1 before s2 _(i.e. s1 is less specific than s2)_
|
||||
- `Specificity.equals(s1, s2)`: Returns `true` if s1 and s2 have the same specificity. If not, `false` is returned.
|
||||
- `Specificity.greaterThan(s1, s2)`: Returns `true` if s1 has a higher specificity than s2. If not, `false` is returned.
|
||||
- `Specificity.lessThan(s1, s2)`: Returns `true` if s1 has a lower specificity than s2. If not, `false` is returned.
|
||||
|
||||
- Sorting:
|
||||
|
||||
- `Specificity.sortAsc(s1, s2, …, sN)`: Sorts the given specificities in ascending order _(low specificity to high specificity)_
|
||||
- `Specificity.sortDesc(s1, s2, …, sN)`: Sorts the given specificities in descending order _(high specificity to low specificity)_
|
||||
|
||||
- Filtering:
|
||||
- `Specificity.min(s1, s2, …, sN)`: Filters out the value with the lowest specificity
|
||||
- `Specificity.max(s1, s2, …, sN)`: Filters out the value with the highest specificity
|
||||
|
||||
A specificity passed into any of these utility functions can be any of:
|
||||
|
||||
- An instance of the included `Specificity` class
|
||||
- A simple Object such as `{'a': 1, 'b': 0, 'c': 2}`
|
||||
|
||||
## Utility Functions (Standalone)
|
||||
|
||||
All static methods the `Specificity` class exposes are also exported as standalone functions using [Subpath Exports](https://nodejs.org/api/packages.html#subpath-exports).
|
||||
|
||||
If you're only interested in including some of these functions into your project you can import them from their Subpath. As a result, your bundle size will be reduced greatly _(except for including the standalone `calculate`, as it returns an array of `Specificity` instances that relies on the whole lot)_
|
||||
|
||||
```js
|
||||
import { calculate, calculateForAST } from '@bramus/specificity/core';
|
||||
import { compare, equals, greaterThan, lessThan } from '@bramus/specificity/compare';
|
||||
import { min, max } from '@bramus/specificity/filter';
|
||||
import { sortAsc, sortDesc } from '@bramus/specificity/sort';
|
||||
```
|
||||
|
||||
## Type Definitions
|
||||
|
||||
Although `@bramus/specificity` is written in Vanilla JavaScript, it does include [Type Definitions](https://www.typescriptlang.org/docs/handbook/2/type-declarations.html) which are exposed via its `package.json`.
|
||||
|
||||
## Binary/CLI
|
||||
|
||||
`@bramus/specificity` exposes a binary named `specificity` to calculate the specificity of a given selector list on the CLI. For each selector that it finds, it'll print out the calculated specificity as a string on a new line.
|
||||
|
||||
```bash
|
||||
$ specificity "header:where(#top) nav li:nth-child(2n), #doormat"
|
||||
(0,1,3)
|
||||
(1,0,0)
|
||||
```
|
||||
|
||||
## Benchmark
|
||||
|
||||
A benchmark is included, which you can invoke using `npm run benchmark`.
|
||||
|
||||
Sample results (tested on a MacBook Air M3):
|
||||
|
||||
```
|
||||
Specificity.calculate(string) x 420,682 ops/sec ±0.34% (98 runs sampled)
|
||||
Specificity.calculate(ast) - using SelectorList x 8,994,080 ops/sec ±0.25% (98 runs sampled)
|
||||
Specificity.calculate(ast) - using Selector x 11,054,856 ops/sec ±0.39% (91 runs sampled)
|
||||
Specificity.calculateForAST(ast) x 12,652,322 ops/sec ±0.35% (96 runs sampled)
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
`@bramus/specificity` is released under the MIT public license. See the enclosed `LICENSE` for details.
|
||||
|
||||
## Acknowledgements
|
||||
|
||||
The idea to create this package was sparked by [the wonderful Specificity Calculator created by Kilian Valkhof / Polypane](https://polypane.app/css-specificity-calculator/), a highly educational tool that not only calculates the specificity, but also explains which parts are responsible for it.
|
||||
|
||||
The heavy lifting of doing the actual parsing of Selectors is done by [CSSTree](https://github.com/csstree/csstree).
|
||||
14
node_modules/@bramus/specificity/bin/cli.js
generated
vendored
Executable file
14
node_modules/@bramus/specificity/bin/cli.js
generated
vendored
Executable file
@@ -0,0 +1,14 @@
|
||||
#!/usr/bin/env node
|
||||
import Specificity from '../dist/index.js';
|
||||
|
||||
if (!process.argv[2]) {
|
||||
console.error('❌ Missing selector argument');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
try {
|
||||
const specificities = Specificity.calculate(process.argv[2]);
|
||||
console.log(specificities.map((specificity) => `${specificity}`).join('\n'));
|
||||
} catch (e) {
|
||||
console.error(`❌ ${e.message}`);
|
||||
}
|
||||
8
node_modules/@bramus/specificity/dist/index.cjs
generated
vendored
Normal file
8
node_modules/@bramus/specificity/dist/index.cjs
generated
vendored
Normal file
File diff suppressed because one or more lines are too long
7
node_modules/@bramus/specificity/dist/index.cjs.map
generated
vendored
Normal file
7
node_modules/@bramus/specificity/dist/index.cjs.map
generated
vendored
Normal file
File diff suppressed because one or more lines are too long
8
node_modules/@bramus/specificity/dist/index.js
generated
vendored
Normal file
8
node_modules/@bramus/specificity/dist/index.js
generated
vendored
Normal file
File diff suppressed because one or more lines are too long
7
node_modules/@bramus/specificity/dist/index.js.map
generated
vendored
Normal file
7
node_modules/@bramus/specificity/dist/index.js.map
generated
vendored
Normal file
File diff suppressed because one or more lines are too long
59
node_modules/@bramus/specificity/index.d.ts
generated
vendored
Normal file
59
node_modules/@bramus/specificity/index.d.ts
generated
vendored
Normal file
@@ -0,0 +1,59 @@
|
||||
// Types & Classes
|
||||
export type SpecificityArray = [number, number, number];
|
||||
export type SpecificityObject = { a: number; b: number; c: number };
|
||||
|
||||
export default class Specificity {
|
||||
static calculate(selector: string | CSSTreeAST): Array<Specificity>;
|
||||
static calculateForAST(selectorAST: CSSTreeAST): Specificity;
|
||||
static compare(s1: SpecificityInstanceOrObject, s2: SpecificityInstanceOrObject): number;
|
||||
static equals(s1: SpecificityInstanceOrObject, s2: SpecificityInstanceOrObject): boolean;
|
||||
static lessThan(s1: SpecificityInstanceOrObject, s2: SpecificityInstanceOrObject): boolean;
|
||||
static greaterThan(s1: SpecificityInstanceOrObject, s2: SpecificityInstanceOrObject): boolean;
|
||||
static min(...specificities: SpecificityInstanceOrObject[]): SpecificityInstanceOrObject;
|
||||
static max(...specificities: SpecificityInstanceOrObject[]): SpecificityInstanceOrObject;
|
||||
static sortAsc(...specificities: SpecificityInstanceOrObject[]): SpecificityInstanceOrObject;
|
||||
static sortDesc(...specificities: SpecificityInstanceOrObject[]): SpecificityInstanceOrObject;
|
||||
constructor(value: SpecificityObject, selector?: any);
|
||||
value: SpecificityObject;
|
||||
selector: string | CSSTreeAST;
|
||||
set a(arg: number);
|
||||
get a(): number;
|
||||
set b(arg: number);
|
||||
get b(): number;
|
||||
set c(arg: number);
|
||||
get c(): number;
|
||||
selectorString(): string;
|
||||
toObject(): SpecificityObject;
|
||||
toArray(): SpecificityArray;
|
||||
toString(): string;
|
||||
toJSON(): {
|
||||
selector: string;
|
||||
asObject: SpecificityObject;
|
||||
asArray: SpecificityArray;
|
||||
asString: string;
|
||||
};
|
||||
isEqualTo(otherSpecificity: SpecificityInstanceOrObject): boolean;
|
||||
isGreaterThan(otherSpecificity: SpecificityInstanceOrObject): boolean;
|
||||
isLessThan(otherSpecificity: SpecificityInstanceOrObject): boolean;
|
||||
}
|
||||
|
||||
type SpecificityInstanceOrObject = Specificity | SpecificityObject;
|
||||
type CSSTreeAST = Object; // @TODO: Define shape
|
||||
|
||||
// CORE
|
||||
export function calculate(selector: string | CSSTreeAST): Array<Specificity>;
|
||||
export function calculateForAST(selectorAST: CSSTreeAST): Specificity;
|
||||
|
||||
// UTIL: COMPARE
|
||||
export function equals(s1: SpecificityInstanceOrObject, s2: SpecificityInstanceOrObject): boolean;
|
||||
export function greaterThan(s1: SpecificityInstanceOrObject, s2: SpecificityInstanceOrObject): boolean;
|
||||
export function lessThan(s1: SpecificityInstanceOrObject, s2: SpecificityInstanceOrObject): boolean;
|
||||
export function compare(s1: SpecificityInstanceOrObject, s2: SpecificityInstanceOrObject): number;
|
||||
|
||||
// UTIL: FILTER
|
||||
export function min(specificities: SpecificityInstanceOrObject[]): SpecificityInstanceOrObject;
|
||||
export function max(specificities: SpecificityInstanceOrObject[]): SpecificityInstanceOrObject;
|
||||
|
||||
// UTIL: SORT
|
||||
export function sortAsc(specificities: SpecificityInstanceOrObject[]): SpecificityInstanceOrObject[];
|
||||
export function sortDesc(specificities: SpecificityInstanceOrObject[]): SpecificityInstanceOrObject[];
|
||||
92
node_modules/@bramus/specificity/package.json
generated
vendored
Normal file
92
node_modules/@bramus/specificity/package.json
generated
vendored
Normal file
@@ -0,0 +1,92 @@
|
||||
{
|
||||
"name": "@bramus/specificity",
|
||||
"version": "2.4.2",
|
||||
"description": "Calculate specificity of a CSS Selector",
|
||||
"type": "module",
|
||||
"main": "./dist/index.cjs",
|
||||
"module": "./dist/index.js",
|
||||
"exports": {
|
||||
".": {
|
||||
"browser": "./dist/index.js",
|
||||
"import": "./dist/index.js",
|
||||
"require": "./dist/index.cjs"
|
||||
},
|
||||
"./core": {
|
||||
"import": "./src/core/index.js"
|
||||
},
|
||||
"./util": {
|
||||
"import": "./src/util/index.js"
|
||||
},
|
||||
"./compare": {
|
||||
"import": "./src/util/compare.js"
|
||||
},
|
||||
"./filter": {
|
||||
"import": "./src/util/filter.js"
|
||||
},
|
||||
"./sort": {
|
||||
"import": "./src/util/sort.js"
|
||||
}
|
||||
},
|
||||
"unpkg": "./dist/index.js",
|
||||
"jsdelivr": "./dist/index.js",
|
||||
"files": [
|
||||
"bin",
|
||||
"src",
|
||||
"dist",
|
||||
"index.d.ts"
|
||||
],
|
||||
"types": "./index.d.ts",
|
||||
"bin": {
|
||||
"specificity": "./bin/cli.js"
|
||||
},
|
||||
"scripts": {
|
||||
"build-esm": "esbuild --bundle ./src/index.js --outfile=./dist/index.js --format=esm --sourcemap --minify",
|
||||
"build-cjs": "esbuild --bundle ./src/index.js --outfile=./dist/index.cjs --format=cjs --sourcemap --minify",
|
||||
"lint": "prettier --check '{src,test}/**/*.{ts,tsx,js,jsx}'",
|
||||
"format": "prettier --write '{src,test}/**/*.{ts,tsx,js,jsx}'",
|
||||
"build": "npm run build-esm && npm run build-cjs",
|
||||
"prepack": "npm run prevent-dirty-tree && npm run test",
|
||||
"prepublish": "npm run build",
|
||||
"pretest": "npm run build",
|
||||
"test": "mocha",
|
||||
"prebenchmark": "npm run build",
|
||||
"benchmark": "node ./benchmark/bench.cjs",
|
||||
"beta-version-patch": "npm version $(semver $npm_package_version -i prerelease --preid beta)",
|
||||
"beta-version-minor": "npm version $(semver $npm_package_version -i preminor --preid beta)",
|
||||
"beta-version-major": "npm version $(semver $npm_package_version -i premajor --preid beta)",
|
||||
"rc-version": "npm version $(semver $npm_package_version -i prerelease --preid rc)",
|
||||
"final-release": "npm version $(semver $npm_package_version -i)",
|
||||
"preversion": "npm run prevent-dirty-tree && npm run test",
|
||||
"prevent-dirty-tree": "exit $(git status --porcelain | wc -l)"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/bramus/specificity.git"
|
||||
},
|
||||
"keywords": [
|
||||
"css",
|
||||
"specificity"
|
||||
],
|
||||
"author": {
|
||||
"name": "Bramus Van Damme",
|
||||
"email": "bramus@bram.us",
|
||||
"twitter": "@bramus",
|
||||
"web": "https://www.bram.us/"
|
||||
},
|
||||
"license": "MIT",
|
||||
"bugs": {
|
||||
"url": "https://github.com/bramus/specificity/issues"
|
||||
},
|
||||
"homepage": "https://github.com/bramus/specificity#readme",
|
||||
"devDependencies": {
|
||||
"benchmark": "^2.1.4",
|
||||
"esbuild": "^0.25.0",
|
||||
"microtime": "^3.1.1",
|
||||
"mocha": "^11.1.0",
|
||||
"prettier": "^3.5.1",
|
||||
"semver": "^7.7.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"css-tree": "^3.0.0"
|
||||
}
|
||||
}
|
||||
260
node_modules/@bramus/specificity/src/core/calculate.js
generated
vendored
Normal file
260
node_modules/@bramus/specificity/src/core/calculate.js
generated
vendored
Normal file
@@ -0,0 +1,260 @@
|
||||
import parse from 'css-tree/selector-parser';
|
||||
import Specificity from '../index.js';
|
||||
import { max } from './../util/index.js';
|
||||
|
||||
/** @param {import('css-tree').Selector} selectorAST */
|
||||
const calculateForAST = (selectorAST) => {
|
||||
// Quit while you're ahead
|
||||
if (!selectorAST || selectorAST.type !== 'Selector') {
|
||||
throw new TypeError(`Passed in source is not a Selector AST`);
|
||||
}
|
||||
|
||||
// https://www.w3.org/TR/selectors-4/#specificity-rules
|
||||
let a = 0; /* ID Selectors */
|
||||
let b = 0; /* Class selectors, Attributes selectors, and Pseudo-classes */
|
||||
let c = 0; /* Type selectors and Pseudo-elements */
|
||||
|
||||
selectorAST.children.forEach((child) => {
|
||||
switch (child.type) {
|
||||
case 'IdSelector':
|
||||
a += 1;
|
||||
break;
|
||||
|
||||
case 'AttributeSelector':
|
||||
case 'ClassSelector':
|
||||
b += 1;
|
||||
break;
|
||||
|
||||
case 'PseudoClassSelector':
|
||||
switch (child.name.toLowerCase()) {
|
||||
// “The specificity of a :where() pseudo-class is replaced by zero.”
|
||||
case 'where':
|
||||
// Noop :)
|
||||
break;
|
||||
|
||||
case '-webkit-any':
|
||||
case 'any':
|
||||
if (child.children?.first) {
|
||||
b += 1;
|
||||
}
|
||||
break;
|
||||
|
||||
// “The specificity of an :is(), :not(), or :has() pseudo-class is replaced by the specificity of the most specific complex selector in its selector list argument.“
|
||||
case '-moz-any':
|
||||
case 'is':
|
||||
case 'matches':
|
||||
case 'not':
|
||||
case 'has':
|
||||
if (child.children?.first) {
|
||||
// Calculate Specificity from nested SelectorList
|
||||
const max1 = max(...calculate(child.children.first));
|
||||
|
||||
// Adjust orig specificity
|
||||
a += max1.a;
|
||||
b += max1.b;
|
||||
c += max1.c;
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
// “The specificity of an :nth-child() or :nth-last-child() selector is the specificity of the pseudo class itself (counting as one pseudo-class selector) plus the specificity of the most specific complex selector in its selector list argument”
|
||||
case 'nth-child':
|
||||
case 'nth-last-child':
|
||||
b += 1;
|
||||
|
||||
if (child.children?.first?.selector) {
|
||||
// Calculate Specificity from SelectorList
|
||||
const max2 = max(...calculate(child.children.first.selector));
|
||||
|
||||
// Adjust orig specificity
|
||||
a += max2.a;
|
||||
b += max2.b;
|
||||
c += max2.c;
|
||||
}
|
||||
break;
|
||||
|
||||
// “The specificity of :host is that of a pseudo-class. The specificity of :host() is that of a pseudo-class, plus the specificity of its argument.”
|
||||
// “The specificity of :host-context() is that of a pseudo-class, plus the specificity of its argument.”
|
||||
case 'host-context':
|
||||
case 'host':
|
||||
b += 1;
|
||||
|
||||
if (child.children?.first?.children) {
|
||||
// Workaround to a css-tree bug in which it allows complex selectors instead of only compound selectors
|
||||
// We work around it by filtering out any Combinator and successive Selectors
|
||||
const childAST = { type: 'Selector', children: [] };
|
||||
let foundCombinator = false;
|
||||
child.children.first.children.forEach((entry) => {
|
||||
if (foundCombinator) return false;
|
||||
if (entry.type === 'Combinator') {
|
||||
foundCombinator = true;
|
||||
return false;
|
||||
}
|
||||
childAST.children.push(entry);
|
||||
});
|
||||
|
||||
// Calculate Specificity from Selector
|
||||
const childSpecificity = calculate(childAST)[0];
|
||||
|
||||
// Adjust orig specificity
|
||||
a += childSpecificity.a;
|
||||
b += childSpecificity.b;
|
||||
c += childSpecificity.c;
|
||||
}
|
||||
break;
|
||||
|
||||
// Improper use of Pseudo-Class Selectors instead of a Pseudo-Element
|
||||
// @ref https://developer.mozilla.org/en-US/docs/Web/CSS/Pseudo-elements#index
|
||||
case 'after':
|
||||
case 'before':
|
||||
case 'first-letter':
|
||||
case 'first-line':
|
||||
c += 1;
|
||||
break;
|
||||
|
||||
default:
|
||||
b += 1;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'PseudoElementSelector':
|
||||
switch (child.name) {
|
||||
// “The specificity of ::slotted() is that of a pseudo-element, plus the specificity of its argument.”
|
||||
case 'slotted':
|
||||
c += 1;
|
||||
|
||||
if (child.children?.first?.children) {
|
||||
// Workaround to a css-tree bug in which it allows complex selectors instead of only compound selectors
|
||||
// We work around it by filtering out any Combinator and successive Selectors
|
||||
const childAST = { type: 'Selector', children: [] };
|
||||
let foundCombinator = false;
|
||||
child.children.first.children.forEach((entry) => {
|
||||
if (foundCombinator) return false;
|
||||
if (entry.type === 'Combinator') {
|
||||
foundCombinator = true;
|
||||
return false;
|
||||
}
|
||||
childAST.children.push(entry);
|
||||
});
|
||||
|
||||
// Calculate Specificity from Selector
|
||||
const childSpecificity = calculate(childAST)[0];
|
||||
|
||||
// Adjust orig specificity
|
||||
a += childSpecificity.a;
|
||||
b += childSpecificity.b;
|
||||
c += childSpecificity.c;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'view-transition-group':
|
||||
case 'view-transition-image-pair':
|
||||
case 'view-transition-old':
|
||||
case 'view-transition-new':
|
||||
// The specificity of a view-transition selector with a * argument is zero.
|
||||
if (child.children?.first?.value === '*') {
|
||||
break;
|
||||
}
|
||||
// The specificity of a view-transition selector with an argument is the same
|
||||
// as for other pseudo - elements, and is equivalent to a type selector.
|
||||
c += 1;
|
||||
break;
|
||||
|
||||
default:
|
||||
c += 1;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'TypeSelector':
|
||||
// Omit namespace
|
||||
let typeSelector = child.name;
|
||||
if (typeSelector.includes('|')) {
|
||||
typeSelector = typeSelector.split('|')[1];
|
||||
}
|
||||
|
||||
// “Ignore the universal selector”
|
||||
if (typeSelector !== '*') {
|
||||
c += 1;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
// NOOP
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
return new Specificity({ a, b, c }, selectorAST);
|
||||
};
|
||||
|
||||
const convertToAST = (source) => {
|
||||
// The passed in argument was a String.
|
||||
// ~> Let's try and parse to an AST
|
||||
if (typeof source === 'string' || source instanceof String) {
|
||||
try {
|
||||
return parse(source, {
|
||||
context: 'selectorList',
|
||||
});
|
||||
} catch (e) {
|
||||
throw new TypeError(`Could not convert passed in source '${source}' to SelectorList: ${e.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
// The passed in argument was an Object.
|
||||
// ~> Let's verify if it's a AST of the type Selector or SelectorList
|
||||
if (source instanceof Object) {
|
||||
if (source.type && ['Selector', 'SelectorList'].includes(source.type)) {
|
||||
return source;
|
||||
}
|
||||
|
||||
// Manually parsing subtree when the child is of the type Raw, most likely due to https://github.com/csstree/csstree/issues/151
|
||||
if (source.type && source.type === 'Raw') {
|
||||
try {
|
||||
return parse(source.value, {
|
||||
context: 'selectorList',
|
||||
});
|
||||
} catch (e) {
|
||||
throw new TypeError(`Could not convert passed in source to SelectorList: ${e.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
throw new TypeError(`Passed in source is an Object but no AST / AST of the type Selector or SelectorList`);
|
||||
}
|
||||
|
||||
throw new TypeError(`Passed in source is not a String nor an Object. I don't know what to do with it.`);
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {string} selector
|
||||
* @returns {Specificity[]}
|
||||
*/
|
||||
const calculate = (selector) => {
|
||||
// Quit while you're ahead
|
||||
if (!selector) {
|
||||
return [];
|
||||
}
|
||||
|
||||
// Make sure we have a SelectorList AST
|
||||
// If not, an exception will be thrown
|
||||
const ast = convertToAST(selector);
|
||||
|
||||
// Selector?
|
||||
if (ast.type === 'Selector') {
|
||||
return [calculateForAST(selector)];
|
||||
}
|
||||
|
||||
// SelectorList?
|
||||
// ~> Calculate Specificity for each contained Selector
|
||||
if (ast.type === 'SelectorList') {
|
||||
const specificities = [];
|
||||
ast.children.forEach((childAST) => {
|
||||
const specificity = calculateForAST(childAST);
|
||||
specificities.push(specificity);
|
||||
});
|
||||
return specificities;
|
||||
}
|
||||
};
|
||||
|
||||
export { calculate, calculateForAST };
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user