T4.9-T4.10: Add auto-scroll to ChatView — data-scroll-container + createEffect on messages
This commit is contained in:
669
docs/plans/2026-05-22-phase-1-scaffold-chat.md
Normal file
669
docs/plans/2026-05-22-phase-1-scaffold-chat.md
Normal file
@@ -0,0 +1,669 @@
|
||||
# Phase 1 — Scaffold + Core Chat
|
||||
|
||||
**Date:** 2026-05-22
|
||||
**Repo:** kaykayyali/damascus-dashboard
|
||||
**Issues:** #1, #2, #3, #4, #5
|
||||
**Stack:** Solid.js + Vite + Tailwind CSS · vitest + solid-testing-library · msw
|
||||
|
||||
---
|
||||
|
||||
## Overview
|
||||
|
||||
Stand up the project skeleton, the API layer, and the three core UI surfaces: agent roster sidebar, chat view, and health bar. Every code-producing task follows RED→GREEN: write a failing test, then make it pass.
|
||||
|
||||
---
|
||||
|
||||
## Issue #1 — Scaffold project
|
||||
|
||||
### T1.1 — Project init + Vite config (RED)
|
||||
|
||||
**What:** Scaffold a Solid.js + Vite project and add vitest + solid-testing-library + msw as dev dependencies.
|
||||
**Test:** Write a smoke test that imports `render` from `solid-testing-library` and renders `<div data-testid="app">damascus</div>`. Assert the text content is present.
|
||||
|
||||
```ts
|
||||
// src/__tests__/smoke.test.tsx
|
||||
import { render } from "@solidjs/testing-library";
|
||||
import { describe, it, expect } from "vitest";
|
||||
|
||||
describe("smoke", () => {
|
||||
it("renders", () => {
|
||||
const { getByTestId } = render(() => <div data-testid="app">damascus</div>);
|
||||
expect(getByTestId("app").textContent).toBe("damascus");
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
**RED:** Test fails — no test runner configured, missing dependencies.
|
||||
|
||||
---
|
||||
|
||||
### T1.2 — Project init + Vite config (GREEN)
|
||||
|
||||
**What:**
|
||||
1. `npm create vite@latest . -- --template solid-ts` (or scaffold manually)
|
||||
2. `npm install -D vitest @solidjs/testing-library jsdom msw`
|
||||
3. Configure `vitest.config.ts` with jsdom environment
|
||||
4. Configure `vite.config.ts` with solid plugin
|
||||
|
||||
**Green check:**
|
||||
```bash
|
||||
npx vitest run
|
||||
```
|
||||
Smoke test passes.
|
||||
|
||||
---
|
||||
|
||||
### T1.3 — Tailwind CSS setup (RED)
|
||||
|
||||
**What:** Write a component test asserting a Tailwind class renders.
|
||||
|
||||
```ts
|
||||
// src/__tests__/tailwind.test.tsx
|
||||
import { render } from "@solidjs/testing-library";
|
||||
import { describe, it, expect } from "vitest";
|
||||
|
||||
describe("tailwind", () => {
|
||||
it("applies dark background class", () => {
|
||||
const { container } = render(() => <div class="bg-gray-900 text-gray-100">dark</div>);
|
||||
const el = container.querySelector(".bg-gray-900");
|
||||
expect(el).toBeTruthy();
|
||||
expect(el!.textContent).toBe("dark");
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
**RED:** No Tailwind, class has no effect.
|
||||
|
||||
---
|
||||
|
||||
### T1.4 — Tailwind CSS setup (GREEN)
|
||||
|
||||
**What:**
|
||||
1. `npm install -D tailwindcss @tailwindcss/vite postcss autoprefixer`
|
||||
2. Create `tailwind.config.ts` with `darkMode: 'class'`
|
||||
3. Create `src/index.css` with `@tailwind base; @tailwind components; @tailwind utilities;`
|
||||
4. Add CSS import in `src/index.tsx`
|
||||
|
||||
**Green check:** Tailwind test passes — `.bg-gray-900` class is resolved.
|
||||
|
||||
---
|
||||
|
||||
### T1.5 — Dark theme base styles (RED)
|
||||
|
||||
**What:** Test that the root `<html>` element gets `class="dark"` and body uses dark color tokens.
|
||||
|
||||
```ts
|
||||
// src/__tests__/theme.test.tsx
|
||||
import { render } from "@solidjs/testing-library";
|
||||
import { describe, it, expect } from "vitest";
|
||||
import App from "../App";
|
||||
|
||||
describe("dark theme", () => {
|
||||
it("root html element has dark class", () => {
|
||||
document.documentElement.classList.add("dark");
|
||||
const { container } = render(() => <App />);
|
||||
expect(document.documentElement.classList.contains("dark")).toBe(true);
|
||||
});
|
||||
|
||||
it("body has dark background", () => {
|
||||
const { container } = render(() => <App />);
|
||||
expect(container.querySelector(".min-h-screen")).toBeTruthy();
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
**RED:** No App component yet.
|
||||
|
||||
---
|
||||
|
||||
### T1.6 — Dark theme base styles (GREEN)
|
||||
|
||||
**What:**
|
||||
1. Create `src/App.tsx` — root layout shell with `class="min-h-screen bg-gray-950 text-gray-100"`
|
||||
2. Create `src/index.tsx` — entry point that renders `<App />` and adds `dark` class to `<html>`
|
||||
3. Create `src/index.css` — Tailwind directives + optional CSS variable overrides for damascus brand colors (deep indigo accent)
|
||||
|
||||
**Green check:** Theme test passes.
|
||||
|
||||
---
|
||||
|
||||
## Issue #2 — API client
|
||||
|
||||
### T2.1 — fetchHealth (RED)
|
||||
|
||||
**What:** Unit-test `fetchHealth` returns `{ postgres: string, redis: string }` or throws on non-2xx.
|
||||
|
||||
```ts
|
||||
// src/__tests__/api.test.ts
|
||||
import { describe, it, expect, beforeAll, afterAll, afterEach } from "vitest";
|
||||
import { http, HttpResponse } from "msw";
|
||||
import { setupServer } from "msw/node";
|
||||
import { fetchHealth } from "../api";
|
||||
|
||||
const server = setupServer(
|
||||
http.get("/api/health", () =>
|
||||
HttpResponse.json({ postgres: "connected", redis: "connected" })
|
||||
)
|
||||
);
|
||||
|
||||
beforeAll(() => server.listen());
|
||||
afterEach(() => server.resetHandlers());
|
||||
afterAll(() => server.close());
|
||||
|
||||
describe("fetchHealth", () => {
|
||||
it("returns health status", async () => {
|
||||
const result = await fetchHealth();
|
||||
expect(result.postgres).toBe("connected");
|
||||
expect(result.redis).toBe("connected");
|
||||
});
|
||||
|
||||
it("throws on server error", async () => {
|
||||
server.use(http.get("/api/health", () => HttpResponse.error()));
|
||||
await expect(fetchHealth()).rejects.toThrow();
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
**RED:** No `fetchHealth` function, no `api.ts` file.
|
||||
|
||||
---
|
||||
|
||||
### T2.2 — fetchHealth (GREEN)
|
||||
|
||||
**What:** Create `src/api.ts`. Implement `fetchHealth()`: `fetch('/api/health')` → parse JSON, throw if not ok.
|
||||
|
||||
**Green check:** Test passes.
|
||||
|
||||
---
|
||||
|
||||
### T2.3 — fetchWorkers (RED)
|
||||
|
||||
**What:** Unit-test `fetchWorkers` returns `Worker[]` array with `id`, `agentId`, `status` fields.
|
||||
|
||||
```ts
|
||||
describe("fetchWorkers", () => {
|
||||
it("returns worker list", async () => {
|
||||
server.use(
|
||||
http.get("/api/workers", () =>
|
||||
HttpResponse.json([{ id: "w1", agentId: "agent-a", status: "active" }])
|
||||
)
|
||||
);
|
||||
const result = await fetchWorkers();
|
||||
expect(result).toHaveLength(1);
|
||||
expect(result[0].agentId).toBe("agent-a");
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
**RED:** Function doesn't exist.
|
||||
|
||||
---
|
||||
|
||||
### T2.4 — fetchWorkers (GREEN)
|
||||
|
||||
**What:** Add `fetchWorkers()` to `api.ts`. Same pattern as `fetchHealth` but against `/api/workers`.
|
||||
|
||||
**Green check:** Test passes.
|
||||
|
||||
---
|
||||
|
||||
### T2.5 — fetchPersona + fetchSkills (RED)
|
||||
|
||||
**What:** Unit-test both functions. `fetchPersona(id)` returns `{ id, name, yaml, markdown }`. `fetchSkills(agentId)` returns `Skill[]`.
|
||||
|
||||
```ts
|
||||
describe("fetchPersona", () => {
|
||||
it("returns persona for agent", async () => {
|
||||
server.use(
|
||||
http.get("/api/persona/agent-x", () =>
|
||||
HttpResponse.json({ id: "agent-x", name: "Hermes", yaml: "role: assistant", markdown: "# Intro" })
|
||||
)
|
||||
);
|
||||
const result = await fetchPersona("agent-x");
|
||||
expect(result.name).toBe("Hermes");
|
||||
});
|
||||
});
|
||||
|
||||
describe("fetchSkills", () => {
|
||||
it("returns skills for agent", async () => {
|
||||
server.use(
|
||||
http.get("/api/skills/agent-x", () =>
|
||||
HttpResponse.json([{ name: "search", tools: ["web_search"], instructions: "Search the web" }])
|
||||
)
|
||||
);
|
||||
const result = await fetchSkills("agent-x");
|
||||
expect(result).toHaveLength(1);
|
||||
expect(result[0].name).toBe("search");
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
**RED:** Functions don't exist.
|
||||
|
||||
---
|
||||
|
||||
### T2.6 — fetchPersona + fetchSkills (GREEN)
|
||||
|
||||
**What:** Add `fetchPersona(id: string)` and `fetchSkills(agentId: string)` to `api.ts`.
|
||||
|
||||
**Green check:** Both tests pass.
|
||||
|
||||
---
|
||||
|
||||
### T2.7 — sendMessage (RED)
|
||||
|
||||
**What:** Unit-test `sendMessage(agentId, message)` POSTs to `/api/send` and returns the response.
|
||||
|
||||
```ts
|
||||
describe("sendMessage", () => {
|
||||
it("sends message and returns response", async () => {
|
||||
server.use(
|
||||
http.post("/api/send", async ({ request }) => {
|
||||
const body = await request.json();
|
||||
if (body.agentId === "agent-x" && body.message === "hello") {
|
||||
return HttpResponse.json({ status: "queued", jobId: "j1" });
|
||||
}
|
||||
return HttpResponse.json({ status: "error" }, { status: 400 });
|
||||
})
|
||||
);
|
||||
const result = await sendMessage("agent-x", "hello");
|
||||
expect(result.status).toBe("queued");
|
||||
expect(result.jobId).toBe("j1");
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
**RED:** Function doesn't exist.
|
||||
|
||||
---
|
||||
|
||||
### T2.8 — sendMessage (GREEN)
|
||||
|
||||
**What:** Add `sendMessage(agentId: string, message: string)` to `api.ts`. POST with `Content-Type: application/json`.
|
||||
|
||||
**Green check:** Test passes. All 5 API functions are now covered.
|
||||
|
||||
---
|
||||
|
||||
## Issue #3 — AgentList sidebar
|
||||
|
||||
### T3.1 — AgentList renders agent names (RED)
|
||||
|
||||
**What:** Component test that `AgentList` renders a list of agents with names.
|
||||
|
||||
```ts
|
||||
// src/__tests__/AgentList.test.tsx
|
||||
import { render } from "@solidjs/testing-library";
|
||||
import { describe, it, expect } from "vitest";
|
||||
import { AgentList } from "../components/AgentList";
|
||||
|
||||
const mockAgents = [
|
||||
{ id: "a1", name: "Hermes", status: "online" },
|
||||
{ id: "a2", name: "Nyx", status: "offline" },
|
||||
];
|
||||
|
||||
describe("AgentList", () => {
|
||||
it("renders agent names", () => {
|
||||
const { getByText } = render(() => <AgentList agents={mockAgents} onSelect={() => {}} />);
|
||||
expect(getByText("Hermes")).toBeTruthy();
|
||||
expect(getByText("Nyx")).toBeTruthy();
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
**RED:** No `AgentList` component.
|
||||
|
||||
---
|
||||
|
||||
### T3.2 — AgentList renders agent names (GREEN)
|
||||
|
||||
**What:** Create `src/components/AgentList.tsx`. Accept `agents` and `onSelect` props. Map over agents, render each name in a list item.
|
||||
|
||||
**Green check:** Test passes.
|
||||
|
||||
---
|
||||
|
||||
### T3.3 — AgentList status dots (RED)
|
||||
|
||||
**What:** Test green dot for "online", gray dot for "offline".
|
||||
|
||||
```ts
|
||||
it("shows green dot for online, gray for offline", () => {
|
||||
const { container } = render(() => <AgentList agents={mockAgents} onSelect={() => {}} />);
|
||||
const onlineDot = container.querySelector('[data-status="online"]');
|
||||
const offlineDot = container.querySelector('[data-status="offline"]');
|
||||
expect(onlineDot).toBeTruthy();
|
||||
expect(offlineDot).toBeTruthy();
|
||||
// Verify green vs gray classes
|
||||
expect(onlineDot!.classList.contains("bg-green-500")).toBe(true);
|
||||
expect(offlineDot!.classList.contains("bg-gray-500")).toBe(true);
|
||||
});
|
||||
```
|
||||
|
||||
**RED:** No status dots rendered.
|
||||
|
||||
---
|
||||
|
||||
### T3.4 — AgentList status dots (GREEN)
|
||||
|
||||
**What:** Add colored dot indicator to each list item. `data-status={agent.status}` attribute. Green (`bg-green-500`) for online, gray (`bg-gray-500`) for offline.
|
||||
|
||||
**Green check:** Test passes.
|
||||
|
||||
---
|
||||
|
||||
### T3.5 — AgentList click handler (RED)
|
||||
|
||||
**What:** Test that clicking an agent calls `onSelect` with that agent's ID.
|
||||
|
||||
```ts
|
||||
import { fireEvent } from "@solidjs/testing-library";
|
||||
|
||||
it("calls onSelect when agent clicked", () => {
|
||||
const onSelect = vi.fn();
|
||||
render(() => <AgentList agents={mockAgents} onSelect={onSelect} />);
|
||||
const items = document.querySelectorAll('[data-agent-id]');
|
||||
fireEvent.click(items[0]);
|
||||
expect(onSelect).toHaveBeenCalledWith("a1");
|
||||
});
|
||||
```
|
||||
|
||||
**RED:** No click handler wired up.
|
||||
|
||||
---
|
||||
|
||||
### T3.6 — AgentList click handler (GREEN)
|
||||
|
||||
**What:** Add `onClick={() => onSelect(agent.id)}` to each list item. Add `data-agent-id={agent.id}` attribute.
|
||||
|
||||
**Green check:** Test passes.
|
||||
|
||||
---
|
||||
|
||||
### T3.7 — AgentList polling hook (RED)
|
||||
|
||||
**What:** Test `useAgentPolling` hook returns agents array and updates on interval.
|
||||
|
||||
```ts
|
||||
// src/__tests__/useAgentPolling.test.ts
|
||||
// Test that the hook:
|
||||
// 1. Returns empty array initially
|
||||
// 2. Calls fetchWorkers on mount
|
||||
// 3. Sets up interval that re-fetches
|
||||
// 4. Cleans up interval on unmount
|
||||
```
|
||||
|
||||
**RED:** Hook doesn't exist.
|
||||
|
||||
---
|
||||
|
||||
### T3.8 — AgentList polling hook (GREEN)
|
||||
|
||||
**What:** Create `src/hooks/useAgentPolling.ts`.
|
||||
|
||||
```ts
|
||||
import { createSignal, onCleanup, createResource } from "solid-js";
|
||||
import { fetchWorkers } from "../api";
|
||||
|
||||
export function useAgentPolling(intervalMs = 5000) {
|
||||
const [agents, { refetch }] = createResource(fetchWorkers);
|
||||
const timer = setInterval(refetch, intervalMs);
|
||||
onCleanup(() => clearInterval(timer));
|
||||
return agents;
|
||||
}
|
||||
```
|
||||
|
||||
**Green check:** Polling hook test passes (use `vi.useFakeTimers()` and mock `fetchWorkers`).
|
||||
|
||||
---
|
||||
|
||||
## Issue #4 — ChatView
|
||||
|
||||
### T4.1 — ChatView renders messages (RED)
|
||||
|
||||
**What:** Component test that `ChatView` renders an array of message objects.
|
||||
|
||||
```ts
|
||||
// src/__tests__/ChatView.test.tsx
|
||||
import { render } from "@solidjs/testing-library";
|
||||
import { describe, it, expect } from "vitest";
|
||||
import { ChatView } from "../components/ChatView";
|
||||
|
||||
const mockMessages = [
|
||||
{ id: "m1", role: "user", content: "Hello", timestamp: "12:00" },
|
||||
{ id: "m2", role: "agent", content: "Hi there!", timestamp: "12:01" },
|
||||
];
|
||||
|
||||
describe("ChatView", () => {
|
||||
it("renders message bubbles", () => {
|
||||
const { getByText } = render(() => (
|
||||
<ChatView messages={mockMessages} onSend={() => {}} />
|
||||
));
|
||||
expect(getByText("Hello")).toBeTruthy();
|
||||
expect(getByText("Hi there!")).toBeTruthy();
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
**RED:** No `ChatView` component.
|
||||
|
||||
---
|
||||
|
||||
### T4.2 — ChatView renders messages (GREEN)
|
||||
|
||||
**What:** Create `src/components/ChatView.tsx`. Map over `messages` and render each as a bubble. User messages right-aligned, agent messages left-aligned.
|
||||
|
||||
**Green check:** Test passes.
|
||||
|
||||
---
|
||||
|
||||
### T4.3 — ChatView role-based styling (RED)
|
||||
|
||||
**What:** Test that user bubbles have different styling from agent/output/tool-call/error bubbles.
|
||||
|
||||
```ts
|
||||
it("applies role-specific classes", () => {
|
||||
const messages = [
|
||||
{ id: "1", role: "user", content: "hi", timestamp: "12:00" },
|
||||
{ id: "2", role: "agent", content: "output", timestamp: "12:01" },
|
||||
{ id: "3", role: "tool_call", content: "searching...", timestamp: "12:02" },
|
||||
{ id: "4", role: "error", content: "failed", timestamp: "12:03" },
|
||||
];
|
||||
const { container } = render(() => <ChatView messages={messages} onSend={() => {}} />);
|
||||
const bubbles = container.querySelectorAll("[data-role]");
|
||||
expect(bubbles[0].getAttribute("data-role")).toBe("user");
|
||||
expect(bubbles[1].getAttribute("data-role")).toBe("agent");
|
||||
expect(bubbles[2].getAttribute("data-role")).toBe("tool_call");
|
||||
expect(bubbles[3].getAttribute("data-role")).toBe("error");
|
||||
});
|
||||
```
|
||||
|
||||
**RED:** No `data-role` attributes or role-specific styling.
|
||||
|
||||
---
|
||||
|
||||
### T4.4 — ChatView role-based styling (GREEN)
|
||||
|
||||
**What:** Add `data-role={msg.role}` to each message bubble. Apply color-coded borders/backgrounds: user=indigo, agent=slate, tool_call=amber, error=red.
|
||||
|
||||
**Green check:** Test passes.
|
||||
|
||||
---
|
||||
|
||||
### T4.5 — ChatView timestamps (RED)
|
||||
|
||||
**What:** Test that each bubble shows its timestamp.
|
||||
|
||||
```ts
|
||||
it("shows timestamps", () => {
|
||||
const { getByText } = render(() => (
|
||||
<ChatView messages={mockMessages} onSend={() => {}} />
|
||||
));
|
||||
expect(getByText("12:00")).toBeTruthy();
|
||||
expect(getByText("12:01")).toBeTruthy();
|
||||
});
|
||||
```
|
||||
|
||||
**RED:** Timestamps not rendered.
|
||||
|
||||
---
|
||||
|
||||
### T4.6 — ChatView timestamps (GREEN)
|
||||
|
||||
**What:** Add a small muted timestamp below each message bubble.
|
||||
|
||||
**Green check:** Test passes.
|
||||
|
||||
---
|
||||
|
||||
### T4.7 — ChatView send input (RED)
|
||||
|
||||
**What:** Test typing in the input and clicking send calls `onSend(agentId, content)`.
|
||||
|
||||
```ts
|
||||
import { fireEvent } from "@solidjs/testing-library";
|
||||
|
||||
it("sends message on button click", () => {
|
||||
const onSend = vi.fn();
|
||||
const { container } = render(() => <ChatView messages={[]} onSend={onSend} agentId="a1" />);
|
||||
const input = container.querySelector("textarea")!;
|
||||
fireEvent.input(input, { target: { value: "test message" } });
|
||||
const sendBtn = container.querySelector("button[aria-label='Send']")!;
|
||||
fireEvent.click(sendBtn);
|
||||
expect(onSend).toHaveBeenCalledWith("a1", "test message");
|
||||
});
|
||||
```
|
||||
|
||||
**RED:** No input or send button.
|
||||
|
||||
---
|
||||
|
||||
### T4.8 — ChatView send input (GREEN)
|
||||
|
||||
**What:** Add textarea input and send button. Enter key also triggers send. Clear input after send.
|
||||
|
||||
**Green check:** Test passes.
|
||||
|
||||
---
|
||||
|
||||
### T4.9 — ChatView auto-scroll (RED)
|
||||
|
||||
**What:** Test that the message container scrolls to bottom when new messages arrive.
|
||||
|
||||
```ts
|
||||
it("scrolls to bottom on new message", () => {
|
||||
const { container } = render(() => <ChatView messages={[]} onSend={() => {}} />);
|
||||
const scrollContainer = container.querySelector("[data-scroll-container]")!;
|
||||
const scrollSpy = vi.spyOn(scrollContainer, "scrollTop", "set");
|
||||
// Re-render with new messages (trigger createEffect)
|
||||
// ...
|
||||
expect(scrollSpy).toHaveBeenCalled();
|
||||
});
|
||||
```
|
||||
|
||||
**RED:** No auto-scroll behavior.
|
||||
|
||||
---
|
||||
|
||||
### T4.10 — ChatView auto-scroll (GREEN)
|
||||
|
||||
**What:** Add `createEffect` that sets `scrollContainer.scrollTop = scrollContainer.scrollHeight` whenever `messages` signal changes. Add `data-scroll-container` attribute for test targeting.
|
||||
|
||||
**Green check:** Test passes.
|
||||
|
||||
---
|
||||
|
||||
## Issue #5 — HealthBar
|
||||
|
||||
### T5.1 — HealthBar renders statuses (RED)
|
||||
|
||||
**What:** Component test that `HealthBar` shows Postgres, Redis, and worker count.
|
||||
|
||||
```ts
|
||||
// src/__tests__/HealthBar.test.tsx
|
||||
import { render } from "@solidjs/testing-library";
|
||||
import { describe, it, expect } from "vitest";
|
||||
import { HealthBar } from "../components/HealthBar";
|
||||
|
||||
describe("HealthBar", () => {
|
||||
it("renders postgres and redis status", () => {
|
||||
const { getByText } = render(() => (
|
||||
<HealthBar postgres="connected" redis="connected" workerCount={3} />
|
||||
));
|
||||
expect(getByText(/postgres/i)).toBeTruthy();
|
||||
expect(getByText(/redis/i)).toBeTruthy();
|
||||
expect(getByText(/3/)).toBeTruthy();
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
**RED:** No `HealthBar` component.
|
||||
|
||||
---
|
||||
|
||||
### T5.2 — HealthBar renders statuses (GREEN)
|
||||
|
||||
**What:** Create `src/components/HealthBar.tsx`. Renders a horizontal bar with PG icon+status, Redis icon+status, and worker count badge.
|
||||
|
||||
**Green check:** Test passes.
|
||||
|
||||
---
|
||||
|
||||
### T5.3 — HealthBar connected/disconnected colors (RED)
|
||||
|
||||
**What:** Test green for "connected", red for "disconnected".
|
||||
|
||||
```ts
|
||||
it("shows green for connected, red for disconnected", () => {
|
||||
const { container } = render(() => (
|
||||
<HealthBar postgres="connected" redis="disconnected" workerCount={0} />
|
||||
));
|
||||
const pgBadge = container.querySelector("[data-service='postgres']");
|
||||
const redisBadge = container.querySelector("[data-service='redis']");
|
||||
expect(pgBadge!.classList.contains("bg-green-500")).toBe(true);
|
||||
expect(redisBadge!.classList.contains("bg-red-500")).toBe(true);
|
||||
});
|
||||
```
|
||||
|
||||
**RED:** No status color differentiation.
|
||||
|
||||
---
|
||||
|
||||
### T5.4 — HealthBar connected/disconnected colors (GREEN)
|
||||
|
||||
**What:** Add conditional classes to status dots. `data-service` attribute for test targeting.
|
||||
|
||||
**Green check:** Test passes.
|
||||
|
||||
---
|
||||
|
||||
### T5.5 — HealthBar polling (RED)
|
||||
|
||||
**What:** Test `useHealthPolling` hook fetches on mount and on interval.
|
||||
|
||||
```ts
|
||||
// Similar pattern to useAgentPolling test
|
||||
// Mock fetchHealth, assert it's called on mount and on interval
|
||||
```
|
||||
|
||||
**RED:** Hook doesn't exist.
|
||||
|
||||
---
|
||||
|
||||
### T5.6 — HealthBar polling (GREEN)
|
||||
|
||||
**What:** Create `src/hooks/useHealthPolling.ts`. Same pattern as `useAgentPolling` but calls `fetchHealth`.
|
||||
|
||||
**Green check:** Test passes.
|
||||
|
||||
---
|
||||
|
||||
## Phase 1 completion checklist
|
||||
|
||||
- [ ] `npm run dev` starts without errors
|
||||
- [ ] `npx vitest run` — all tests pass
|
||||
- [ ] Dark theme renders (bg-gray-950, text-gray-100)
|
||||
- [ ] API client: all 5 functions covered by msw-backed tests
|
||||
- [ ] AgentList: renders agents with status dots, clickable, polls
|
||||
- [ ] ChatView: message bubbles, role colors, timestamps, send, auto-scroll
|
||||
- [ ] HealthBar: PG/Redis status, worker count, color indicators, polls
|
||||
505
docs/plans/2026-05-22-phase-2-persona-skills.md
Normal file
505
docs/plans/2026-05-22-phase-2-persona-skills.md
Normal file
@@ -0,0 +1,505 @@
|
||||
# Phase 2 — Persona & Skills Panels
|
||||
|
||||
**Date:** 2026-05-22
|
||||
**Repo:** kaykayyali/damascus-dashboard
|
||||
**Issues:** #6, #7
|
||||
**Stack:** Solid.js + Vite + Tailwind CSS · vitest + solid-testing-library · msw
|
||||
|
||||
---
|
||||
|
||||
## Overview
|
||||
|
||||
Add two slide-out detail panels: one for viewing an agent's persona (YAML frontmatter + markdown body), and one for browsing loaded skills (expandable list with badges). Both panels appear as slide-out drawers from the right side when an agent is selected.
|
||||
|
||||
**Prerequisites:** Phase 1 complete (App shell, API client, AgentList, ChatView, HealthBar).
|
||||
|
||||
---
|
||||
|
||||
## Issue #6 — PersonaPanel slide-out
|
||||
|
||||
### T6.1 — PersonaPanel fetches and renders persona name (RED)
|
||||
|
||||
**What:** Component test that `PersonaPanel` accepts an `agentId`, calls `fetchPersona`, and renders the persona name.
|
||||
|
||||
```ts
|
||||
// src/__tests__/PersonaPanel.test.tsx
|
||||
import { render, screen, waitFor } from "@solidjs/testing-library";
|
||||
import { describe, it, expect, vi, beforeAll, afterAll, afterEach } from "vitest";
|
||||
import { http, HttpResponse } from "msw";
|
||||
import { setupServer } from "msw/node";
|
||||
import { PersonaPanel } from "../components/PersonaPanel";
|
||||
|
||||
const server = setupServer(
|
||||
http.get("/api/persona/agent-x", () =>
|
||||
HttpResponse.json({
|
||||
id: "agent-x",
|
||||
name: "Hermes Agent",
|
||||
yaml: "role: assistant\nowner: nous",
|
||||
markdown: "## Instructions\n\nYou are Hermes, a helpful AI.",
|
||||
})
|
||||
)
|
||||
);
|
||||
|
||||
beforeAll(() => server.listen());
|
||||
afterEach(() => server.resetHandlers());
|
||||
afterAll(() => server.close());
|
||||
|
||||
describe("PersonaPanel", () => {
|
||||
it("fetches and displays persona name", async () => {
|
||||
const onClose = vi.fn();
|
||||
render(() => <PersonaPanel agentId="agent-x" onClose={onClose} />);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText("Hermes Agent")).toBeTruthy();
|
||||
});
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
**RED:** No `PersonaPanel` component.
|
||||
|
||||
---
|
||||
|
||||
### T6.2 — PersonaPanel fetches and renders persona name (GREEN)
|
||||
|
||||
**What:** Create `src/components/PersonaPanel.tsx`.
|
||||
|
||||
```tsx
|
||||
import { createResource, Show } from "solid-js";
|
||||
import { fetchPersona } from "../api";
|
||||
|
||||
export function PersonaPanel(props: { agentId: string; onClose: () => void }) {
|
||||
const [persona] = createResource(() => props.agentId, fetchPersona);
|
||||
|
||||
return (
|
||||
<Show when={persona()}>
|
||||
{(p) => (
|
||||
<aside class="...">
|
||||
<h2>{p().name}</h2>
|
||||
<button onClick={props.onClose}>Close</button>
|
||||
</aside>
|
||||
)}
|
||||
</Show>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
**Green check:** Test passes — persona name renders after fetch.
|
||||
|
||||
---
|
||||
|
||||
### T6.3 — PersonaPanel renders YAML table (RED)
|
||||
|
||||
**What:** Test that the YAML frontmatter is parsed and displayed as key-value rows.
|
||||
|
||||
```ts
|
||||
it("renders YAML frontmatter as key-value table", async () => {
|
||||
const onClose = vi.fn();
|
||||
render(() => <PersonaPanel agentId="agent-x" onClose={onClose} />);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText("role")).toBeTruthy();
|
||||
expect(screen.getByText("assistant")).toBeTruthy();
|
||||
expect(screen.getByText("owner")).toBeTruthy();
|
||||
expect(screen.getByText("nous")).toBeTruthy();
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
**RED:** No YAML parsing/rendering.
|
||||
|
||||
---
|
||||
|
||||
### T6.4 — PersonaPanel renders YAML table (GREEN)
|
||||
|
||||
**What:** Parse the `yaml` string into key-value pairs. Use a simple line-by-line parser (no heavy YAML lib — these are simple `key: value` lines). Render as a two-column table: key (muted label) → value.
|
||||
|
||||
```ts
|
||||
function parseYamlSimple(yaml: string): [string, string][] {
|
||||
return yaml
|
||||
.split("\n")
|
||||
.filter((line) => line.includes(":"))
|
||||
.map((line) => {
|
||||
const [key, ...rest] = line.split(":");
|
||||
return [key.trim(), rest.join(":").trim()];
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
**Green check:** YAML table renders correctly.
|
||||
|
||||
---
|
||||
|
||||
### T6.5 — PersonaPanel renders markdown body (RED)
|
||||
|
||||
**What:** Test that the markdown body renders (at minimum, check that markdown text is displayed — we can add a markdown renderer later, but for now raw text is fine since `solid-markdown` or similar would be an enhancement).
|
||||
|
||||
```ts
|
||||
it("renders markdown body text", async () => {
|
||||
render(() => <PersonaPanel agentId="agent-x" onClose={vi.fn()} />);
|
||||
|
||||
await waitFor(() => {
|
||||
// Check for the markdown heading text
|
||||
expect(screen.getByText(/Instructions/)).toBeTruthy();
|
||||
expect(screen.getByText(/You are Hermes/)).toBeTruthy();
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
**RED:** Markdown body not rendered.
|
||||
|
||||
---
|
||||
|
||||
### T6.6 — PersonaPanel renders markdown body (GREEN)
|
||||
|
||||
**What:** Add a section below the YAML table that renders the markdown body inside a `<pre>` or `<div>` with `whitespace: pre-wrap`. Future enhancement: add `solid-markdown` for proper rendering.
|
||||
|
||||
**Green check:** Test passes.
|
||||
|
||||
---
|
||||
|
||||
### T6.7 — PersonaPanel slide-out animation (RED)
|
||||
|
||||
**What:** Test that the panel has the CSS class/animation for slide-in from right.
|
||||
|
||||
```ts
|
||||
it("has slide-out panel styling", async () => {
|
||||
const { container } = render(() => (
|
||||
<PersonaPanel agentId="agent-x" onClose={vi.fn()} />
|
||||
));
|
||||
|
||||
await waitFor(() => {
|
||||
const panel = container.querySelector("aside");
|
||||
expect(panel).toBeTruthy();
|
||||
// Check for slide-in/transition classes
|
||||
expect(panel!.classList.contains("translate-x-0")).toBe(true);
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
**RED:** No slide-out animation classes.
|
||||
|
||||
---
|
||||
|
||||
### T6.8 — PersonaPanel slide-out animation (GREEN)
|
||||
|
||||
**What:** Apply Tailwind transition classes: `fixed right-0 top-0 h-full w-96 bg-gray-900 transform transition-transform duration-300 translate-x-0 shadow-xl`. Add a backdrop overlay.
|
||||
|
||||
**Green check:** Test passes.
|
||||
|
||||
---
|
||||
|
||||
### T6.9 — PersonaPanel close button + Escape key (RED)
|
||||
|
||||
**What:** Test that clicking close button or pressing Escape calls `onClose`.
|
||||
|
||||
```ts
|
||||
it("calls onClose on close button click and Escape key", async () => {
|
||||
const onClose = vi.fn();
|
||||
render(() => <PersonaPanel agentId="agent-x" onClose={onClose} />);
|
||||
|
||||
await waitFor(() => screen.getByText("Hermes Agent"));
|
||||
|
||||
fireEvent.click(screen.getByLabelText("Close panel"));
|
||||
expect(onClose).toHaveBeenCalledTimes(1);
|
||||
|
||||
// Reset for Escape test
|
||||
onClose.mockReset();
|
||||
fireEvent.keyDown(document, { key: "Escape" });
|
||||
expect(onClose).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
```
|
||||
|
||||
**RED:** Escape key handler not wired.
|
||||
|
||||
---
|
||||
|
||||
### T6.10 — PersonaPanel close button + Escape key (GREEN)
|
||||
|
||||
**What:** Add `aria-label="Close panel"` to close button. Add `createEffect` with `window.addEventListener('keydown', ...)` filtered for Escape key. Clean up listener on destroy.
|
||||
|
||||
**Green check:** Test passes.
|
||||
|
||||
---
|
||||
|
||||
### T6.11 — PersonaPanel loading state (RED)
|
||||
|
||||
**What:** Test that a loading indicator shows while the persona is being fetched.
|
||||
|
||||
```ts
|
||||
it("shows loading state while fetching", () => {
|
||||
// Use a never-resolving handler
|
||||
server.use(
|
||||
http.get("/api/persona/agent-x", () => new Promise(() => {}))
|
||||
);
|
||||
const { container } = render(() => (
|
||||
<PersonaPanel agentId="agent-x" onClose={vi.fn()} />
|
||||
));
|
||||
expect(container.querySelector("[data-loading]")).toBeTruthy();
|
||||
});
|
||||
```
|
||||
|
||||
**RED:** No loading state.
|
||||
|
||||
---
|
||||
|
||||
### T6.12 — PersonaPanel loading state (GREEN)
|
||||
|
||||
**What:** Add a `Show` fallback with a spinner/skeleton (`data-loading="true"`) while `persona.loading` is true.
|
||||
|
||||
**Green check:** Test passes.
|
||||
|
||||
---
|
||||
|
||||
### T6.13 — PersonaPanel error state (RED)
|
||||
|
||||
**What:** Test that an error message displays when the fetch fails.
|
||||
|
||||
```ts
|
||||
it("shows error on fetch failure", async () => {
|
||||
server.use(
|
||||
http.get("/api/persona/agent-x", () =>
|
||||
HttpResponse.json({ error: "Not found" }, { status: 404 })
|
||||
)
|
||||
);
|
||||
render(() => <PersonaPanel agentId="agent-x" onClose={vi.fn()} />);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText(/error/i)).toBeTruthy();
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
**RED:** No error handling.
|
||||
|
||||
---
|
||||
|
||||
### T6.14 — PersonaPanel error state (GREEN)
|
||||
|
||||
**What:** Check `persona.error` in the resource and display an error message with a retry button.
|
||||
|
||||
**Green check:** Test passes.
|
||||
|
||||
---
|
||||
|
||||
## Issue #7 — SkillsPanel slide-out
|
||||
|
||||
### T7.1 — SkillsPanel fetches and renders skill names (RED)
|
||||
|
||||
**What:** Component test that `SkillsPanel` accepts an `agentId`, calls `fetchSkills`, and renders a list of skill names.
|
||||
|
||||
```ts
|
||||
// src/__tests__/SkillsPanel.test.tsx
|
||||
import { render, screen, waitFor } from "@solidjs/testing-library";
|
||||
import { describe, it, expect, vi, beforeAll, afterAll, afterEach } from "vitest";
|
||||
import { http, HttpResponse } from "msw";
|
||||
import { setupServer } from "msw/node";
|
||||
import { SkillsPanel } from "../components/SkillsPanel";
|
||||
|
||||
const mockSkills = [
|
||||
{ name: "web_search", tools: ["web_search"], instructions: "Search the internet for information." },
|
||||
{ name: "file_read", tools: ["read_file", "file_search"], instructions: "Read and search local files." },
|
||||
{ name: "code_exec", tools: ["terminal"], instructions: "Execute code in a sandboxed terminal." },
|
||||
];
|
||||
|
||||
const server = setupServer(
|
||||
http.get("/api/skills/agent-x", () => HttpResponse.json(mockSkills))
|
||||
);
|
||||
|
||||
beforeAll(() => server.listen());
|
||||
afterEach(() => server.resetHandlers());
|
||||
afterAll(() => server.close());
|
||||
|
||||
describe("SkillsPanel", () => {
|
||||
it("fetches and displays skill names", async () => {
|
||||
render(() => <SkillsPanel agentId="agent-x" onClose={vi.fn()} />);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText("web_search")).toBeTruthy();
|
||||
expect(screen.getByText("file_read")).toBeTruthy();
|
||||
expect(screen.getByText("code_exec")).toBeTruthy();
|
||||
});
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
**RED:** No `SkillsPanel` component.
|
||||
|
||||
---
|
||||
|
||||
### T7.2 — SkillsPanel fetches and renders skill names (GREEN)
|
||||
|
||||
**What:** Create `src/components/SkillsPanel.tsx`. Pattern: `createResource` with `fetchSkills(agentId)`. Map over skills, render each name in an expandable section header.
|
||||
|
||||
**Green check:** Test passes.
|
||||
|
||||
---
|
||||
|
||||
### T7.3 — SkillsPanel expand/collapse (RED)
|
||||
|
||||
**What:** Test that clicking a skill name expands it to show tools+instructions.
|
||||
|
||||
```ts
|
||||
it("expands skill on click to show tools and instructions", async () => {
|
||||
const { container } = render(() => (
|
||||
<SkillsPanel agentId="agent-x" onClose={vi.fn()} />
|
||||
));
|
||||
|
||||
await waitFor(() => screen.getByText("web_search"));
|
||||
|
||||
// Click the skill to expand
|
||||
fireEvent.click(screen.getByText("web_search"));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText("Search the internet for information.")).toBeTruthy();
|
||||
// Check that tools badge is rendered
|
||||
expect(screen.getByText("web_search")).toBeTruthy(); // the tool name as a badge
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
**RED:** No expand/collapse behavior.
|
||||
|
||||
---
|
||||
|
||||
### T7.4 — SkillsPanel expand/collapse (GREEN)
|
||||
|
||||
**What:** Add an `expandedSkill` signal (nullable string). Clicking a skill header sets it to that skill's name (or toggles). Render expanded content: instructions text + tool badges. Use `Show` component for conditional rendering.
|
||||
|
||||
**Green check:** Test passes.
|
||||
|
||||
---
|
||||
|
||||
### T7.5 — SkillsPanel tool badges (RED)
|
||||
|
||||
**What:** Test that each skill's tools are rendered as badges with distinct styling.
|
||||
|
||||
```ts
|
||||
it("renders tool names as styled badges", async () => {
|
||||
render(() => <SkillsPanel agentId="agent-x" onClose={vi.fn()} />);
|
||||
|
||||
await waitFor(() => screen.getByText("web_search"));
|
||||
fireEvent.click(screen.getByText("web_search"));
|
||||
|
||||
await waitFor(() => {
|
||||
const badges = document.querySelectorAll("[data-tool-badge]");
|
||||
expect(badges.length).toBeGreaterThanOrEqual(1);
|
||||
expect(badges[0].textContent).toContain("web_search");
|
||||
expect(badges[0].classList.contains("bg-amber-500")).toBe(true);
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
**RED:** No tool badges rendered.
|
||||
|
||||
---
|
||||
|
||||
### T7.6 — SkillsPanel tool badges (GREEN)
|
||||
|
||||
**What:** In the expanded view, map over `skill.tools` array. Render each as a small pill/badge (`data-tool-badge`, `bg-amber-500/10 text-amber-400 border border-amber-500/30 rounded px-2 py-0.5 text-xs`).
|
||||
|
||||
**Green check:** Test passes.
|
||||
|
||||
---
|
||||
|
||||
### T7.7 — SkillsPanel only one expanded at a time (RED)
|
||||
|
||||
**What:** Test that expanding one skill collapses any previously expanded skill.
|
||||
|
||||
```ts
|
||||
it("collapses previous skill when expanding another", async () => {
|
||||
render(() => <SkillsPanel agentId="agent-x" onClose={vi.fn()} />);
|
||||
|
||||
await waitFor(() => screen.getByText("web_search"));
|
||||
fireEvent.click(screen.getByText("web_search"));
|
||||
|
||||
await waitFor(() => screen.getByText("Search the internet for information."));
|
||||
|
||||
// Expand another
|
||||
fireEvent.click(screen.getByText("file_read"));
|
||||
|
||||
await waitFor(() => {
|
||||
// First skill's instructions should be gone
|
||||
expect(screen.queryByText("Search the internet for information.")).toBeNull();
|
||||
// Second skill's instructions should be visible
|
||||
expect(screen.getByText("Read and search local files.")).toBeTruthy();
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
**RED:** Accordion behavior not implemented.
|
||||
|
||||
---
|
||||
|
||||
### T7.8 — SkillsPanel only one expanded at a time (GREEN)
|
||||
|
||||
**What:** Use a single `expandedSkill` signal (not per-item state). Setting it to a new skill name automatically clears the old one.
|
||||
|
||||
**Green check:** Test passes.
|
||||
|
||||
---
|
||||
|
||||
### T7.9 — SkillsPanel slide-out + close (RED)
|
||||
|
||||
**What:** Same pattern as PersonaPanel — slide-out from right, close button + Escape key.
|
||||
|
||||
```ts
|
||||
it("has slide-out styling and close behavior", async () => {
|
||||
const onClose = vi.fn();
|
||||
const { container } = render(() => (
|
||||
<SkillsPanel agentId="agent-x" onClose={onClose} />
|
||||
));
|
||||
|
||||
await waitFor(() => screen.getByText("web_search"));
|
||||
|
||||
const panel = container.querySelector("aside");
|
||||
expect(panel!.classList.contains("translate-x-0")).toBe(true);
|
||||
|
||||
fireEvent.click(screen.getByLabelText("Close panel"));
|
||||
expect(onClose).toHaveBeenCalled();
|
||||
});
|
||||
```
|
||||
|
||||
**RED:** No slide-out or close.
|
||||
|
||||
---
|
||||
|
||||
### T7.10 — SkillsPanel slide-out + close (GREEN)
|
||||
|
||||
**What:** Same Tailwind slide-out classes as PersonaPanel. Add close button with `aria-label="Close panel"`. Add Escape key handler.
|
||||
|
||||
**Green check:** Test passes.
|
||||
|
||||
---
|
||||
|
||||
### T7.11 — SkillsPanel loading + error states (RED)
|
||||
|
||||
**What:** Test loading spinner and error display (same pattern as PersonaPanel T6.11–T6.14).
|
||||
|
||||
```ts
|
||||
it("shows loading skeleton while fetching", () => { /* ... */ });
|
||||
it("shows error with retry on fetch failure", async () => { /* ... */ });
|
||||
```
|
||||
|
||||
**RED:** No loading/error handling.
|
||||
|
||||
---
|
||||
|
||||
### T7.12 — SkillsPanel loading + error states (GREEN)
|
||||
|
||||
**What:** Add `Show` fallback with `data-loading` skeleton. Add error boundary with message and retry button.
|
||||
|
||||
**Green check:** Both tests pass.
|
||||
|
||||
---
|
||||
|
||||
## Phase 2 completion checklist
|
||||
|
||||
- [ ] PersonaPanel: fetches on `agentId` change, displays YAML table, markdown body
|
||||
- [ ] PersonaPanel: slide-out animation, close button, Escape key, backdrop
|
||||
- [ ] PersonaPanel: loading skeleton, error state with retry
|
||||
- [ ] SkillsPanel: fetches on `agentId` change, displays expandable skill list
|
||||
- [ ] SkillsPanel: tool badges, accordion (one expanded at a time)
|
||||
- [ ] SkillsPanel: slide-out animation, close button, Escape key, backdrop
|
||||
- [ ] SkillsPanel: loading skeleton, error state with retry
|
||||
- [ ] Both panels are accessible (aria-labels, keyboard dismiss)
|
||||
606
docs/plans/2026-05-22-phase-3-polish-mobile.md
Normal file
606
docs/plans/2026-05-22-phase-3-polish-mobile.md
Normal file
@@ -0,0 +1,606 @@
|
||||
# Phase 3 — Polish + Mobile
|
||||
|
||||
**Date:** 2026-05-22
|
||||
**Repo:** kaykayyali/damascus-dashboard
|
||||
**Issues:** #8
|
||||
**Stack:** Solid.js + Vite + Tailwind CSS · vitest + solid-testing-library
|
||||
|
||||
---
|
||||
|
||||
## Overview
|
||||
|
||||
Responsive layout, streaming UX, and error states. The dashboard must work on mobile devices (collapsible sidebar, stacked layout), handle streaming agent responses gracefully, and surface meaningful error states throughout. This phase tightens the UI without introducing new data-fetching paths.
|
||||
|
||||
**Prerequisites:** Phase 1 + 2 complete. All components, API client, and panels are built.
|
||||
|
||||
---
|
||||
|
||||
## Issue #8 — Responsive layout, streaming UX, error states
|
||||
|
||||
### Section A: Responsive Layout
|
||||
|
||||
---
|
||||
|
||||
### T8.1 — Mobile sidebar toggle button (RED)
|
||||
|
||||
**What:** Test that a hamburger/menu button exists at mobile breakpoint and toggles the sidebar.
|
||||
|
||||
```ts
|
||||
// src/__tests__/App.test.tsx
|
||||
import { render, screen, fireEvent } from "@solidjs/testing-library";
|
||||
import { describe, it, expect, vi } from "vitest";
|
||||
import App from "../App";
|
||||
|
||||
describe("App responsive layout", () => {
|
||||
it("shows hamburger toggle on mobile and toggles sidebar", () => {
|
||||
// Set viewport to mobile width
|
||||
globalThis.innerWidth = 375;
|
||||
globalThis.dispatchEvent(new Event("resize"));
|
||||
|
||||
const { container } = render(() => <App />);
|
||||
|
||||
const toggle = screen.getByLabelText("Toggle sidebar");
|
||||
expect(toggle).toBeTruthy();
|
||||
|
||||
// Click to open sidebar
|
||||
fireEvent.click(toggle);
|
||||
const sidebar = container.querySelector("[data-sidebar]");
|
||||
expect(sidebar!.classList.contains("translate-x-0")).toBe(true);
|
||||
|
||||
// Click again to close
|
||||
fireEvent.click(toggle);
|
||||
expect(sidebar!.classList.contains("-translate-x-full")).toBe(true);
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
**RED:** No mobile toggle, no sidebar visibility state.
|
||||
|
||||
---
|
||||
|
||||
### T8.2 — Mobile sidebar toggle button (GREEN)
|
||||
|
||||
**What:** Add `createSignal` for `sidebarOpen` in `App.tsx`. Add a hamburger button (`aria-label="Toggle sidebar"`) visible only on `md:hidden`. The sidebar uses `data-sidebar` attribute with `translate-x-0` / `-translate-x-full` classes controlled by the signal.
|
||||
|
||||
```tsx
|
||||
const [sidebarOpen, setSidebarOpen] = createSignal(false);
|
||||
|
||||
// In JSX:
|
||||
<button
|
||||
class="md:hidden p-2"
|
||||
aria-label="Toggle sidebar"
|
||||
onClick={() => setSidebarOpen((v) => !v)}
|
||||
>
|
||||
<HamburgerIcon />
|
||||
</button>
|
||||
|
||||
<aside
|
||||
data-sidebar
|
||||
class={`fixed inset-y-0 left-0 z-40 w-64 transform transition-transform duration-300 md:relative md:translate-x-0 ${
|
||||
sidebarOpen() ? "translate-x-0" : "-translate-x-full"
|
||||
}`}
|
||||
>
|
||||
<AgentList ... />
|
||||
</aside>
|
||||
```
|
||||
|
||||
**Green check:** Test passes.
|
||||
|
||||
---
|
||||
|
||||
### T8.3 — Backdrop overlay on mobile (RED)
|
||||
|
||||
**What:** Test that a backdrop overlay appears when the mobile sidebar is open and clicking it closes the sidebar.
|
||||
|
||||
```ts
|
||||
it("shows backdrop overlay when mobile sidebar is open", () => {
|
||||
globalThis.innerWidth = 375;
|
||||
globalThis.dispatchEvent(new Event("resize"));
|
||||
|
||||
const { container } = render(() => <App />);
|
||||
|
||||
// Open sidebar
|
||||
fireEvent.click(screen.getByLabelText("Toggle sidebar"));
|
||||
|
||||
const backdrop = container.querySelector("[data-sidebar-backdrop]");
|
||||
expect(backdrop).toBeTruthy();
|
||||
|
||||
// Click backdrop to close
|
||||
fireEvent.click(backdrop!);
|
||||
const sidebar = container.querySelector("[data-sidebar]");
|
||||
expect(sidebar!.classList.contains("-translate-x-full")).toBe(true);
|
||||
});
|
||||
```
|
||||
|
||||
**RED:** No backdrop overlay.
|
||||
|
||||
---
|
||||
|
||||
### T8.4 — Backdrop overlay on mobile (GREEN)
|
||||
|
||||
**What:** Add a backdrop `<div>` with `data-sidebar-backdrop`, `onClick={() => setSidebarOpen(false)}`. Only rendered when `sidebarOpen()` is true. Use `fixed inset-0 bg-black/50 z-30 md:hidden`.
|
||||
|
||||
**Green check:** Test passes.
|
||||
|
||||
---
|
||||
|
||||
### T8.5 — Responsive layout breakpoints (RED)
|
||||
|
||||
**What:** Test that the layout changes structure at breakpoints: sidebar is inline on `md+`, overlaid on `sm-`. Chat area fills remaining space.
|
||||
|
||||
```ts
|
||||
it("renders sidebar inline on desktop", () => {
|
||||
globalThis.innerWidth = 1024;
|
||||
globalThis.dispatchEvent(new Event("resize"));
|
||||
|
||||
const { container } = render(() => <App />);
|
||||
|
||||
// Sidebar should be visible without needing toggle
|
||||
const sidebar = container.querySelector("[data-sidebar]");
|
||||
expect(sidebar!.classList.contains("md:relative")).toBe(true);
|
||||
expect(sidebar!.classList.contains("md:translate-x-0")).toBe(true);
|
||||
});
|
||||
|
||||
it("stacks layout vertically on mobile when sidebar is closed", () => {
|
||||
globalThis.innerWidth = 375;
|
||||
globalThis.dispatchEvent(new Event("resize"));
|
||||
|
||||
const { container } = render(() => <App />);
|
||||
|
||||
// Main content should take full width when sidebar is hidden
|
||||
const mainArea = container.querySelector("[data-main-area]");
|
||||
expect(mainArea!.classList.contains("w-full")).toBe(true);
|
||||
});
|
||||
```
|
||||
|
||||
**RED:** No responsive layout structure.
|
||||
|
||||
---
|
||||
|
||||
### T8.6 — Responsive layout breakpoints (GREEN)
|
||||
|
||||
**What:** Apply Tailwind responsive classes throughout `App.tsx`:
|
||||
- Root layout: `flex flex-col md:flex-row h-screen`
|
||||
- Sidebar: `w-64 shrink-0` + the transform classes from T8.2
|
||||
- Main area: `flex-1 flex flex-col min-w-0` with `data-main-area`
|
||||
- Chat input: full-width mobile, comfortable on desktop
|
||||
|
||||
**Green check:** Both tests pass.
|
||||
|
||||
---
|
||||
|
||||
### Section B: Streaming UX
|
||||
|
||||
---
|
||||
|
||||
### T8.7 — Streaming message indicator (RED)
|
||||
|
||||
**What:** Test that when a message has `streaming: true`, the bubble shows a blinking cursor or pulsing animation.
|
||||
|
||||
```ts
|
||||
it("shows streaming indicator on in-flight messages", () => {
|
||||
const messages = [
|
||||
{ id: "m1", role: "agent", content: "Thinking", timestamp: "12:00", streaming: true },
|
||||
];
|
||||
const { container } = render(() => (
|
||||
<ChatView messages={messages} onSend={vi.fn()} />
|
||||
));
|
||||
|
||||
const streamingDot = container.querySelector("[data-streaming]");
|
||||
expect(streamingDot).toBeTruthy();
|
||||
expect(streamingDot!.classList.contains("animate-pulse")).toBe(true);
|
||||
});
|
||||
```
|
||||
|
||||
**RED:** No streaming indicator in ChatView.
|
||||
|
||||
---
|
||||
|
||||
### T8.8 — Streaming message indicator (GREEN)
|
||||
|
||||
**What:** In `ChatView.tsx`, when `message.streaming === true`, add a small animated dot at the end of the bubble content. Use `data-streaming` attribute and `animate-pulse` class.
|
||||
|
||||
```tsx
|
||||
<Show when={msg.streaming}>
|
||||
<span data-streaming class="inline-block w-2 h-4 ml-1 bg-indigo-400 animate-pulse rounded-sm" />
|
||||
</Show>
|
||||
```
|
||||
|
||||
**Green check:** Test passes.
|
||||
|
||||
---
|
||||
|
||||
### T8.9 — Streaming message appends content (RED)
|
||||
|
||||
**What:** Test that when a streaming message's content changes (simulating chunks arriving), the view updates without losing the cursor position. Also test that the auto-scroll still works during streaming.
|
||||
|
||||
```ts
|
||||
it("auto-scrolls during streaming content updates", async () => {
|
||||
const { container, rerender } = render(() => (
|
||||
<ChatView messages={[{ id: "m1", role: "agent", content: "Hello", timestamp: "12:00", streaming: true }]} onSend={vi.fn()} />
|
||||
));
|
||||
|
||||
const scrollContainer = container.querySelector("[data-scroll-container]")!;
|
||||
|
||||
// Simulate content growing
|
||||
rerender(() => (
|
||||
<ChatView
|
||||
messages={[{ id: "m1", role: "agent", content: "Hello world, this is a longer response", timestamp: "12:00", streaming: true }]}
|
||||
onSend={vi.fn()}
|
||||
/>
|
||||
));
|
||||
|
||||
// scrollTop should have been set to scrollHeight
|
||||
// We verify the effect fires
|
||||
expect(scrollContainer.scrollTop).toBeGreaterThan(0);
|
||||
});
|
||||
```
|
||||
|
||||
**RED:** Auto-scroll doesn't account for streaming content changes.
|
||||
|
||||
---
|
||||
|
||||
### T8.10 — Streaming message appends content (GREEN)
|
||||
|
||||
**What:** The existing auto-scroll `createEffect` from T4.10 already watches `messages` signal. Ensure it uses deep equality or watches the last message's content length. If `messages` is a signal, Solid's fine-grained reactivity handles this automatically — but confirm with the test. If needed, use `createEffect(on(() => messages().length + messages().at(-1)?.content?.length, scrollToBottom))`.
|
||||
|
||||
**Green check:** Test passes.
|
||||
|
||||
---
|
||||
|
||||
### T8.11 — Streaming error recovery (RED)
|
||||
|
||||
**What:** Test that when a streaming message transitions to `role: "error"`, the bubble styling updates accordingly and the streaming indicator is removed.
|
||||
|
||||
```ts
|
||||
it("transitions from streaming to error styling", async () => {
|
||||
// Start with streaming
|
||||
const { container, rerender } = render(() => (
|
||||
<ChatView
|
||||
messages={[{ id: "m1", role: "agent", content: "Partial...", timestamp: "12:00", streaming: true }]}
|
||||
onSend={vi.fn()}
|
||||
/>
|
||||
));
|
||||
|
||||
expect(container.querySelector("[data-streaming]")).toBeTruthy();
|
||||
|
||||
// Transition to error
|
||||
rerender(() => (
|
||||
<ChatView
|
||||
messages={[{ id: "m1", role: "error", content: "Connection lost", timestamp: "12:01", streaming: false }]}
|
||||
onSend={vi.fn()}
|
||||
/>
|
||||
));
|
||||
|
||||
expect(container.querySelector("[data-streaming]")).toBeNull();
|
||||
expect(container.querySelector("[data-role='error']")).toBeTruthy();
|
||||
});
|
||||
```
|
||||
|
||||
**RED:** No transition handling.
|
||||
|
||||
---
|
||||
|
||||
### T8.12 — Streaming error recovery (GREEN)
|
||||
|
||||
**What:** The role-based styling from T4.4 already handles this — the `data-role` attribute changes with the message. Confirm that `streaming` is explicitly checked (not just inferred from role). Add a test to verify that streaming + error don't both apply.
|
||||
|
||||
**Green check:** Test passes.
|
||||
|
||||
---
|
||||
|
||||
### Section C: Error States
|
||||
|
||||
---
|
||||
|
||||
### T8.13 — Global error boundary (RED)
|
||||
|
||||
**What:** Test that an uncaught error in a child component is caught by an error boundary and displays a fallback UI instead of a blank page.
|
||||
|
||||
```ts
|
||||
// src/__tests__/ErrorBoundary.test.tsx
|
||||
import { render, screen } from "@solidjs/testing-library";
|
||||
import { describe, it, expect } from "vitest";
|
||||
import { ErrorBoundary } from "../components/ErrorBoundary";
|
||||
|
||||
const Boom = () => {
|
||||
throw new Error("Test explosion");
|
||||
};
|
||||
|
||||
describe("ErrorBoundary", () => {
|
||||
it("catches errors and shows fallback", () => {
|
||||
render(() => (
|
||||
<ErrorBoundary>
|
||||
<Boom />
|
||||
</ErrorBoundary>
|
||||
));
|
||||
|
||||
expect(screen.getByText(/something went wrong/i)).toBeTruthy();
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
**RED:** No `ErrorBoundary` component.
|
||||
|
||||
---
|
||||
|
||||
### T8.14 — Global error boundary (GREEN)
|
||||
|
||||
**What:** Create `src/components/ErrorBoundary.tsx` using Solid's `ErrorBoundary`:
|
||||
|
||||
```tsx
|
||||
import { ErrorBoundary as SolidErrorBoundary } from "solid-js";
|
||||
|
||||
export function ErrorBoundary(props: { children: any }) {
|
||||
return (
|
||||
<SolidErrorBoundary
|
||||
fallback={(err) => (
|
||||
<div class="flex items-center justify-center h-screen bg-gray-950 text-gray-100">
|
||||
<div class="text-center">
|
||||
<h1 class="text-2xl font-bold mb-2">Something went wrong</h1>
|
||||
<p class="text-gray-400 mb-4">{err.message}</p>
|
||||
<button
|
||||
class="px-4 py-2 bg-indigo-600 rounded"
|
||||
onClick={() => window.location.reload()}
|
||||
>
|
||||
Reload
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
>
|
||||
{props.children}
|
||||
</SolidErrorBoundary>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
Wrap `App` content with `<ErrorBoundary>` in `index.tsx`.
|
||||
|
||||
**Green check:** Test passes.
|
||||
|
||||
---
|
||||
|
||||
### T8.15 — API offline state (RED)
|
||||
|
||||
**What:** Test that when the API server is unreachable, a banner is shown and polling pauses.
|
||||
|
||||
```ts
|
||||
// Extend useHealthPolling test or add dedicated test
|
||||
it("shows offline banner when health check fails repeatedly", async () => {
|
||||
const { container } = render(() => <App />);
|
||||
|
||||
// Simulate fetchHealth failing 3 times in a row
|
||||
// ... (use msw to return errors)
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText(/server unreachable/i)).toBeTruthy();
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
**RED:** No offline detection.
|
||||
|
||||
---
|
||||
|
||||
### T8.16 — API offline state (GREEN)
|
||||
|
||||
**What:** Create `src/hooks/useOnlineStatus.ts` (or extend `useHealthPolling`). Track consecutive failures. After N failures (e.g., 3), set an `offline` signal. Display a banner at the top of the app: "⚠️ API server unreachable — retrying..." with a manual retry button. Reset counter on next successful fetch.
|
||||
|
||||
```ts
|
||||
// src/hooks/useOnlineStatus.ts
|
||||
export function useOnlineStatus(fetchHealth: () => Promise<any>, maxFailures = 3) {
|
||||
const [offline, setOffline] = createSignal(false);
|
||||
const [failureCount, setFailureCount] = createSignal(0);
|
||||
|
||||
const check = async () => {
|
||||
try {
|
||||
await fetchHealth();
|
||||
setFailureCount(0);
|
||||
setOffline(false);
|
||||
} catch {
|
||||
setFailureCount((c) => c + 1);
|
||||
if (failureCount() >= maxFailures) {
|
||||
setOffline(true);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return { offline, check, reset: () => { setFailureCount(0); setOffline(false); } };
|
||||
}
|
||||
```
|
||||
|
||||
**Green check:** Test passes.
|
||||
|
||||
---
|
||||
|
||||
### T8.17 — Empty state for AgentList (RED)
|
||||
|
||||
**What:** Test that when no agents are returned, a friendly empty state is shown.
|
||||
|
||||
```ts
|
||||
it("shows empty state when no agents", () => {
|
||||
const { getByText } = render(() => (
|
||||
<AgentList agents={[]} onSelect={vi.fn()} />
|
||||
));
|
||||
expect(getByText(/no agents/i)).toBeTruthy();
|
||||
});
|
||||
```
|
||||
|
||||
**RED:** AgentList renders nothing for empty array.
|
||||
|
||||
---
|
||||
|
||||
### T8.18 — Empty state for AgentList (GREEN)
|
||||
|
||||
**What:** Add a `Show` fallback in `AgentList`:
|
||||
|
||||
```tsx
|
||||
<Show when={props.agents.length > 0} fallback={
|
||||
<div class="text-gray-500 text-center py-8">No agents connected</div>
|
||||
}>
|
||||
{/* existing agent list */}
|
||||
</Show>
|
||||
```
|
||||
|
||||
**Green check:** Test passes.
|
||||
|
||||
---
|
||||
|
||||
### T8.19 — Empty state for ChatView (RED)
|
||||
|
||||
**What:** Test that when there are no messages for the selected agent, a welcome/placeholder is shown.
|
||||
|
||||
```ts
|
||||
it("shows welcome state when no messages", () => {
|
||||
const { getByText } = render(() => (
|
||||
<ChatView messages={[]} onSend={vi.fn()} agentId="a1" agentName="Hermes" />
|
||||
));
|
||||
expect(getByText(/start a conversation/i)).toBeTruthy();
|
||||
expect(getByText(/Hermes/)).toBeTruthy();
|
||||
});
|
||||
```
|
||||
|
||||
**RED:** ChatView renders empty area.
|
||||
|
||||
---
|
||||
|
||||
### T8.20 — Empty state for ChatView (GREEN)
|
||||
|
||||
**What:** Add a centered placeholder in the message area:
|
||||
|
||||
```tsx
|
||||
<Show when={props.messages.length === 0}>
|
||||
<div class="flex-1 flex items-center justify-center text-gray-500">
|
||||
<div class="text-center">
|
||||
<p class="text-lg">Start a conversation with {props.agentName}</p>
|
||||
<p class="text-sm mt-2">Type a message below to begin</p>
|
||||
</div>
|
||||
</div>
|
||||
</Show>
|
||||
```
|
||||
|
||||
**Green check:** Test passes.
|
||||
|
||||
---
|
||||
|
||||
### T8.21 — Send error toast (RED)
|
||||
|
||||
**What:** Test that when `sendMessage` fails, an error toast appears momentarily.
|
||||
|
||||
```ts
|
||||
it("shows error toast when message send fails", async () => {
|
||||
// Mock sendMessage to reject
|
||||
server.use(
|
||||
http.post("/api/send", () => HttpResponse.error())
|
||||
);
|
||||
|
||||
const { container } = render(() => (
|
||||
<ChatView messages={[]} onSend={vi.fn()} agentId="a1" />
|
||||
));
|
||||
|
||||
const input = container.querySelector("textarea")!;
|
||||
fireEvent.input(input, { target: { value: "hello" } });
|
||||
fireEvent.click(container.querySelector("button[aria-label='Send']")!);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText(/failed to send/i)).toBeTruthy();
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
**RED:** No send error handling.
|
||||
|
||||
---
|
||||
|
||||
### T8.22 — Send error toast (GREEN)
|
||||
|
||||
**What:** Wrap `onSend` call in try/catch inside ChatView. On error, show a toast notification at the bottom: "Failed to send message. Retry?" with a retry button. Auto-dismiss after 5 seconds.
|
||||
|
||||
```tsx
|
||||
const [sendError, setSendError] = createSignal<string | null>(null);
|
||||
|
||||
async function handleSend() {
|
||||
try {
|
||||
await props.onSend(props.agentId, content);
|
||||
setContent("");
|
||||
setSendError(null);
|
||||
} catch (e) {
|
||||
setSendError("Failed to send message. Retry?");
|
||||
}
|
||||
}
|
||||
|
||||
// Toast:
|
||||
<Show when={sendError()}>
|
||||
<div class="bg-red-900/50 border border-red-500/50 text-red-200 p-3 rounded-md mx-4 mb-2 flex justify-between items-center">
|
||||
<span>{sendError()}</span>
|
||||
<button onClick={handleSend} class="underline">Retry</button>
|
||||
</div>
|
||||
</Show>
|
||||
```
|
||||
|
||||
**Green check:** Test passes.
|
||||
|
||||
---
|
||||
|
||||
### T8.23 — Console error-free render (RED)
|
||||
|
||||
**What:** Integration smoke test that renders the full App and asserts no console errors or warnings (within reason — suppress known dev-mode warnings).
|
||||
|
||||
```ts
|
||||
it("renders without console errors", () => {
|
||||
const errors: string[] = [];
|
||||
const warn = vi.spyOn(console, "error").mockImplementation((msg) => errors.push(msg));
|
||||
|
||||
render(() => <App />);
|
||||
|
||||
// Filter out expected Solid dev warnings
|
||||
const unexpected = errors.filter((e) => !e.includes("createRoot"));
|
||||
expect(unexpected).toHaveLength(0);
|
||||
|
||||
warn.mockRestore();
|
||||
});
|
||||
```
|
||||
|
||||
**RED:** Test fails — may have unhandled promise rejections, missing key props, etc.
|
||||
|
||||
---
|
||||
|
||||
### T8.24 — Console error-free render (GREEN)
|
||||
|
||||
**What:** Fix:
|
||||
1. Add default prop values to all components
|
||||
2. Ensure `createResource` sources default to `() => ""` if `agentId` might be undefined
|
||||
3. Handle all promise rejections in API calls
|
||||
4. Add `onError` handler to `createResource` calls
|
||||
|
||||
**Green check:** Test passes with zero unexpected console errors.
|
||||
|
||||
---
|
||||
|
||||
## Phase 3 completion checklist
|
||||
|
||||
### Responsive layout
|
||||
- [ ] Mobile hamburger toggle opens/closes sidebar
|
||||
- [ ] Backdrop overlay on mobile, click to dismiss
|
||||
- [ ] Sidebar inline on desktop (`md:`+), overlaid on mobile
|
||||
- [ ] Main content area fills available space on all breakpoints
|
||||
- [ ] Chat input is usable on mobile (no overflow, comfortable tap target)
|
||||
|
||||
### Streaming UX
|
||||
- [ ] Pulsing cursor indicator on in-flight streaming messages
|
||||
- [ ] Auto-scroll continues working during streaming content updates
|
||||
- [ ] Streaming → error transition removes indicator and updates styling
|
||||
|
||||
### Error states
|
||||
- [ ] Global ErrorBoundary catches crashes, shows reload button
|
||||
- [ ] Offline banner after N consecutive health check failures, with retry
|
||||
- [ ] AgentList empty state ("No agents connected")
|
||||
- [ ] ChatView empty state (welcome message with agent name)
|
||||
- [ ] Send error toast with retry button, auto-dismiss
|
||||
- [ ] Console error-free full-app render
|
||||
|
||||
### Cross-cutting
|
||||
- [ ] All components have `aria-label` on interactive elements
|
||||
- [ ] Keyboard navigation works throughout (tab order, Escape to close panels)
|
||||
- [ ] Touch targets are ≥ 44px on mobile (send button, agent list items, toggle)
|
||||
1
node_modules/.bin/autoprefixer
generated
vendored
Symbolic link
1
node_modules/.bin/autoprefixer
generated
vendored
Symbolic link
@@ -0,0 +1 @@
|
||||
../autoprefixer/bin/autoprefixer
|
||||
1
node_modules/.bin/baseline-browser-mapping
generated
vendored
Symbolic link
1
node_modules/.bin/baseline-browser-mapping
generated
vendored
Symbolic link
@@ -0,0 +1 @@
|
||||
../baseline-browser-mapping/dist/cli.cjs
|
||||
1
node_modules/.bin/browserslist
generated
vendored
Symbolic link
1
node_modules/.bin/browserslist
generated
vendored
Symbolic link
@@ -0,0 +1 @@
|
||||
../browserslist/cli.js
|
||||
1
node_modules/.bin/jiti
generated
vendored
Symbolic link
1
node_modules/.bin/jiti
generated
vendored
Symbolic link
@@ -0,0 +1 @@
|
||||
../jiti/lib/jiti-cli.mjs
|
||||
1
node_modules/.bin/jsesc
generated
vendored
Symbolic link
1
node_modules/.bin/jsesc
generated
vendored
Symbolic link
@@ -0,0 +1 @@
|
||||
../jsesc/bin/jsesc
|
||||
1
node_modules/.bin/json5
generated
vendored
Symbolic link
1
node_modules/.bin/json5
generated
vendored
Symbolic link
@@ -0,0 +1 @@
|
||||
../json5/lib/cli.js
|
||||
1
node_modules/.bin/lz-string
generated
vendored
Symbolic link
1
node_modules/.bin/lz-string
generated
vendored
Symbolic link
@@ -0,0 +1 @@
|
||||
../lz-string/bin/bin.js
|
||||
1
node_modules/.bin/msw
generated
vendored
Symbolic link
1
node_modules/.bin/msw
generated
vendored
Symbolic link
@@ -0,0 +1 @@
|
||||
../msw/cli/index.js
|
||||
1
node_modules/.bin/nanoid
generated
vendored
Symbolic link
1
node_modules/.bin/nanoid
generated
vendored
Symbolic link
@@ -0,0 +1 @@
|
||||
../nanoid/bin/nanoid.cjs
|
||||
1
node_modules/.bin/parser
generated
vendored
Symbolic link
1
node_modules/.bin/parser
generated
vendored
Symbolic link
@@ -0,0 +1 @@
|
||||
../@babel/parser/bin/babel-parser.js
|
||||
1
node_modules/.bin/rolldown
generated
vendored
Symbolic link
1
node_modules/.bin/rolldown
generated
vendored
Symbolic link
@@ -0,0 +1 @@
|
||||
../rolldown/bin/cli.mjs
|
||||
1
node_modules/.bin/semver
generated
vendored
Symbolic link
1
node_modules/.bin/semver
generated
vendored
Symbolic link
@@ -0,0 +1 @@
|
||||
../semver/bin/semver.js
|
||||
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
|
||||
1
node_modules/.bin/tsc
generated
vendored
Symbolic link
1
node_modules/.bin/tsc
generated
vendored
Symbolic link
@@ -0,0 +1 @@
|
||||
../typescript/bin/tsc
|
||||
1
node_modules/.bin/tsserver
generated
vendored
Symbolic link
1
node_modules/.bin/tsserver
generated
vendored
Symbolic link
@@ -0,0 +1 @@
|
||||
../typescript/bin/tsserver
|
||||
1
node_modules/.bin/update-browserslist-db
generated
vendored
Symbolic link
1
node_modules/.bin/update-browserslist-db
generated
vendored
Symbolic link
@@ -0,0 +1 @@
|
||||
../update-browserslist-db/cli.js
|
||||
1
node_modules/.bin/vite
generated
vendored
Symbolic link
1
node_modules/.bin/vite
generated
vendored
Symbolic link
@@ -0,0 +1 @@
|
||||
../vite/bin/vite.js
|
||||
1
node_modules/.bin/vitest
generated
vendored
Symbolic link
1
node_modules/.bin/vitest
generated
vendored
Symbolic link
@@ -0,0 +1 @@
|
||||
../vitest/vitest.mjs
|
||||
1
node_modules/.bin/why-is-node-running
generated
vendored
Symbolic link
1
node_modules/.bin/why-is-node-running
generated
vendored
Symbolic link
@@ -0,0 +1 @@
|
||||
../why-is-node-running/cli.js
|
||||
3055
node_modules/.package-lock.json
generated
vendored
Normal file
3055
node_modules/.package-lock.json
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
1
node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json
generated
vendored
Normal file
1
node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json
generated
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"version":"4.1.7","results":[[":src/__tests__/smoke.test.tsx",{"duration":22.74891200000002,"failed":false}],[":src/__tests__/tailwind.test.tsx",{"duration":20.798025000000052,"failed":false}],[":src/__tests__/theme.test.tsx",{"duration":19.605476000000067,"failed":false}],[":src/__tests__/api.test.ts",{"duration":59.36179100000015,"failed":false}],[":src/__tests__/AgentList.test.tsx",{"duration":27.485447999999906,"failed":false}],[":src/__tests__/useAgentPolling.test.ts",{"duration":6.0264720000000125,"failed":false}],[":src/__tests__/ChatView.test.tsx",{"duration":36.09163600000011,"failed":false}]]}
|
||||
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"}
|
||||
38
node_modules/@asamuzakjp/css-color/dist/esm/js/cache.d.ts
generated
vendored
Normal file
38
node_modules/@asamuzakjp/css-color/dist/esm/js/cache.d.ts
generated
vendored
Normal file
@@ -0,0 +1,38 @@
|
||||
import { GenerationalCache } from '@asamuzakjp/generational-cache';
|
||||
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();
|
||||
}
|
||||
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;
|
||||
90
node_modules/@asamuzakjp/css-color/dist/esm/js/cache.js
generated
vendored
Normal file
90
node_modules/@asamuzakjp/css-color/dist/esm/js/cache.js
generated
vendored
Normal file
@@ -0,0 +1,90 @@
|
||||
import { GenerationalCache } from "@asamuzakjp/generational-cache";
|
||||
//#region src/js/cache.ts
|
||||
/**
|
||||
* cache
|
||||
*/
|
||||
var CACHE_SIZE = 2048;
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
};
|
||||
var genCache = new GenerationalCache(CACHE_SIZE);
|
||||
/**
|
||||
* 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
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"cache.js","names":["#item","#isNull"],"sources":["../../../src/js/cache.ts"],"sourcesContent":["/**\n * cache\n */\n\nimport { GenerationalCache } from '@asamuzakjp/generational-cache';\nimport { Options } from './typedef';\n\n/* constants */\nconst CACHE_SIZE = 2048;\n\n/**\n * CacheItem\n */\nexport class CacheItem {\n /* private */\n #isNull: boolean;\n #item: unknown;\n\n constructor(item: unknown, isNull: boolean = false) {\n this.#item = item;\n this.#isNull = !!isNull;\n }\n\n get item() {\n return this.#item;\n }\n\n get isNull() {\n return this.#isNull;\n }\n}\n\n/**\n * NullObject\n */\nexport class NullObject extends CacheItem {\n constructor() {\n super(Symbol('null'), true);\n }\n}\n\n/*\n * generational cache instance\n */\nexport const genCache = new GenerationalCache<string, CacheItem>(CACHE_SIZE);\n\n/**\n * shared null object\n */\nconst sharedNullObject = new NullObject();\n\n/**\n * set cache\n * @param key - cache key\n * @param value - value to cache\n * @returns void\n */\nexport const setCache = (key: string, value: unknown): void => {\n if (!key) {\n return;\n }\n if (value === null) {\n genCache.set(key, sharedNullObject);\n } else if (value instanceof CacheItem) {\n genCache.set(key, value);\n } else {\n genCache.set(key, new CacheItem(value));\n }\n};\n\n/**\n * get cache\n * @param key - cache key\n * @returns cached item or false otherwise\n */\nexport const getCache = (key: string): CacheItem | false => {\n if (!key) {\n return false;\n }\n const item = genCache.get(key);\n if (item !== undefined) {\n return item as CacheItem;\n }\n return false;\n};\n\n/**\n * helper function to sort object keys alphabetically\n * @param obj - Object\n * @returns stringified JSON\n */\nconst stringifySorted = (obj: Record<string, unknown>): string => {\n const keys = Object.keys(obj);\n if (keys.length === 0) {\n return '';\n }\n keys.sort();\n let result = '';\n for (const key of keys) {\n result += `${key}:${JSON.stringify(obj[key])};`;\n }\n return result;\n};\n\n/**\n * create cache key\n * @param keyData - key data\n * @param [opt] - options\n * @returns cache key\n */\nexport const createCacheKey = (\n keyData: Record<string, string>,\n opt: Options = {}\n): string => {\n if (\n !keyData ||\n (opt.customProperty && typeof opt.customProperty.callback === 'function') ||\n (opt.dimension && typeof opt.dimension.callback === 'function')\n ) {\n return '';\n }\n const namespace = keyData.namespace || '';\n const name = keyData.name || '';\n const value = keyData.value || '';\n if (!namespace && !name && !value) {\n return '';\n }\n const baseKey = `${namespace}:${name}:${value}`;\n const optStr = `${opt.format || ''}|${opt.colorSpace || ''}|${opt.colorScheme || ''}|${opt.currentColor || ''}|${opt.d50 ? '1' : '0'}|${opt.nullable ? '1' : '0'}|${opt.preserveComment ? '1' : '0'}|${opt.delimiter || ''}`;\n const customPropStr = opt.customProperty\n ? stringifySorted(opt.customProperty as Record<string, unknown>)\n : '';\n const dimStr = opt.dimension\n ? stringifySorted(opt.dimension as Record<string, unknown>)\n : '';\n return `${baseKey}::${optStr}::${customPropStr}::${dimStr}`;\n};\n"],"mappings":";;;;;AAQA,IAAM,aAAa;;;;AAKnB,IAAa,YAAb,MAAuB;CAErB;CACA;CAEA,YAAY,MAAe,SAAkB,OAAO;AAClD,QAAA,OAAa;AACb,QAAA,SAAe,CAAC,CAAC;;CAGnB,IAAI,OAAO;AACT,SAAO,MAAA;;CAGT,IAAI,SAAS;AACX,SAAO,MAAA;;;;;;AAOX,IAAa,aAAb,cAAgC,UAAU;CACxC,cAAc;AACZ,QAAM,OAAO,OAAO,EAAE,KAAK;;;AAO/B,IAAa,WAAW,IAAI,kBAAqC,WAAW;;;;AAK5E,IAAM,mBAAmB,IAAI,YAAY;;;;;;;AAQzC,IAAa,YAAY,KAAa,UAAyB;AAC7D,KAAI,CAAC,IACH;AAEF,KAAI,UAAU,KACZ,UAAS,IAAI,KAAK,iBAAiB;UAC1B,iBAAiB,UAC1B,UAAS,IAAI,KAAK,MAAM;KAExB,UAAS,IAAI,KAAK,IAAI,UAAU,MAAM,CAAC;;;;;;;AAS3C,IAAa,YAAY,QAAmC;AAC1D,KAAI,CAAC,IACH,QAAO;CAET,MAAM,OAAO,SAAS,IAAI,IAAI;AAC9B,KAAI,SAAS,KAAA,EACX,QAAO;AAET,QAAO;;;;;;;AAQT,IAAM,mBAAmB,QAAyC;CAChE,MAAM,OAAO,OAAO,KAAK,IAAI;AAC7B,KAAI,KAAK,WAAW,EAClB,QAAO;AAET,MAAK,MAAM;CACX,IAAI,SAAS;AACb,MAAK,MAAM,OAAO,KAChB,WAAU,GAAG,IAAI,GAAG,KAAK,UAAU,IAAI,KAAK,CAAC;AAE/C,QAAO;;;;;;;;AAST,IAAa,kBACX,SACA,MAAe,EAAE,KACN;AACX,KACE,CAAC,WACA,IAAI,kBAAkB,OAAO,IAAI,eAAe,aAAa,cAC7D,IAAI,aAAa,OAAO,IAAI,UAAU,aAAa,WAEpD,QAAO;CAET,MAAM,YAAY,QAAQ,aAAa;CACvC,MAAM,OAAO,QAAQ,QAAQ;CAC7B,MAAM,QAAQ,QAAQ,SAAS;AAC/B,KAAI,CAAC,aAAa,CAAC,QAAQ,CAAC,MAC1B,QAAO;AAUT,QAAO,GARS,GAAG,UAAU,GAAG,KAAK,GAAG,QAQtB,IAPH,GAAG,IAAI,UAAU,GAAG,GAAG,IAAI,cAAc,GAAG,GAAG,IAAI,eAAe,GAAG,GAAG,IAAI,gBAAgB,GAAG,GAAG,IAAI,MAAM,MAAM,IAAI,GAAG,IAAI,WAAW,MAAM,IAAI,GAAG,IAAI,kBAAkB,MAAM,IAAI,GAAG,IAAI,aAAa,KAO3L,IANP,IAAI,iBACtB,gBAAgB,IAAI,eAA0C,GAC9D,GAI2C,IAHhC,IAAI,YACf,gBAAgB,IAAI,UAAqC,GACzD"}
|
||||
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
89
node_modules/@asamuzakjp/css-color/package.json
generated
vendored
Normal file
89
node_modules/@asamuzakjp/css-color/package.json
generated
vendored
Normal file
@@ -0,0 +1,89 @@
|
||||
{
|
||||
"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": {
|
||||
"@asamuzakjp/generational-cache": "^1.0.1",
|
||||
"@csstools/css-calc": "^3.2.0",
|
||||
"@csstools/css-color-parser": "^4.1.0",
|
||||
"@csstools/css-parser-algorithms": "^4.0.0",
|
||||
"@csstools/css-tokenizer": "^4.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tanstack/vite-config": "^0.5.2",
|
||||
"@types/node": "^25.6.0",
|
||||
"@vitest/coverage-istanbul": "^4.1.4",
|
||||
"esbuild": "^0.27.7",
|
||||
"eslint": "^9.39.4",
|
||||
"eslint-plugin-regexp": "^3.1.0",
|
||||
"globals": "^17.5.0",
|
||||
"knip": "^6.4.1",
|
||||
"mitata": "^1.0.34",
|
||||
"neostandard": "^0.13.0",
|
||||
"prettier": "^3.8.3",
|
||||
"publint": "^0.3.18",
|
||||
"rimraf": "^6.1.3",
|
||||
"tsx": "^4.21.0",
|
||||
"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": {
|
||||
"bench:color": "npx tsx --expose-gc ./benchmark/bench-color.ts",
|
||||
"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.11"
|
||||
}
|
||||
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
|
||||
};
|
||||
137
node_modules/@asamuzakjp/css-color/src/js/cache.ts
generated
vendored
Normal file
137
node_modules/@asamuzakjp/css-color/src/js/cache.ts
generated
vendored
Normal file
@@ -0,0 +1,137 @@
|
||||
/**
|
||||
* cache
|
||||
*/
|
||||
|
||||
import { GenerationalCache } from '@asamuzakjp/generational-cache';
|
||||
import { Options } from './typedef';
|
||||
|
||||
/* constants */
|
||||
const CACHE_SIZE = 2048;
|
||||
|
||||
/**
|
||||
* 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 instance
|
||||
*/
|
||||
export const genCache = new GenerationalCache<string, CacheItem>(CACHE_SIZE);
|
||||
|
||||
/**
|
||||
* 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
|
||||
79
node_modules/@asamuzakjp/dom-selector/package.json
generated
vendored
Normal file
79
node_modules/@asamuzakjp/dom-selector/package.json
generated
vendored
Normal file
@@ -0,0 +1,79 @@
|
||||
{
|
||||
"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/generational-cache": "^1.0.1",
|
||||
"@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.6.0",
|
||||
"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.5.0",
|
||||
"jsdom": "^29.0.2",
|
||||
"mitata": "^1.0.34",
|
||||
"mocha": "^11.7.5",
|
||||
"neostandard": "^0.13.0",
|
||||
"prettier": "^3.8.3",
|
||||
"sinon": "^21.1.2",
|
||||
"typescript": "^6.0.3",
|
||||
"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:cache": "node benchmark/bench-cache.js",
|
||||
"bench:cacheMonster": "node benchmark/bench-cache-monster.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 --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.1.1"
|
||||
}
|
||||
393
node_modules/@asamuzakjp/dom-selector/src/index.js
generated
vendored
Normal file
393
node_modules/@asamuzakjp/dom-selector/src/index.js
generated
vendored
Normal file
@@ -0,0 +1,393 @@
|
||||
/*!
|
||||
* 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 '@asamuzakjp/generational-cache';
|
||||
import { Finder } from './js/finder.js';
|
||||
import { unescapeSelector, parseAstName } from './js/parser.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,
|
||||
COMBINATOR,
|
||||
ID_SELECTOR,
|
||||
CLASS_SELECTOR,
|
||||
TYPE_SELECTOR
|
||||
} from './js/constant.js';
|
||||
const CACHE_SIZE = 2048;
|
||||
|
||||
/**
|
||||
* @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 { cacheSize, 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(cacheSize ?? CACHE_SIZE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears the internal cache of finder results.
|
||||
* @returns {void}
|
||||
*/
|
||||
clear = () => {
|
||||
this.#finder.clearResults(true);
|
||||
};
|
||||
|
||||
/**
|
||||
* Parses a selector and extracts the rightmost subject keys (Id, Class, Tag).
|
||||
* @param {string} selector - The CSS selector to parse.
|
||||
* @returns {Array<{id: string|null, className: string|null, tag: string|null}>} The list of extracted keys for each selector group.
|
||||
*/
|
||||
extractSubjects = selector => {
|
||||
if (!selector || typeof selector !== 'string') {
|
||||
return [{ id: null, className: null, tag: null }];
|
||||
}
|
||||
const cacheKey = `extract_${selector}`;
|
||||
let subjects = this.#cache.get(cacheKey);
|
||||
if (subjects !== undefined) {
|
||||
return subjects;
|
||||
}
|
||||
subjects = [];
|
||||
try {
|
||||
const ast = this.#finder.getAST(selector);
|
||||
if (ast?.type === 'SelectorList') {
|
||||
for (const selectorNode of ast.children) {
|
||||
let idKey = null;
|
||||
let classKey = null;
|
||||
let tagKey = null;
|
||||
let current = selectorNode.children.tail;
|
||||
while (current) {
|
||||
const node = current.data;
|
||||
if (node.type === COMBINATOR) {
|
||||
break;
|
||||
}
|
||||
if (node.type === ID_SELECTOR) {
|
||||
idKey = idKey ?? unescapeSelector(node.name);
|
||||
} else if (node.type === CLASS_SELECTOR) {
|
||||
classKey = classKey ?? unescapeSelector(node.name);
|
||||
} else if (node.type === TYPE_SELECTOR) {
|
||||
const { localName } = parseAstName(unescapeSelector(node.name));
|
||||
if (localName !== '*') {
|
||||
tagKey = tagKey ?? localName.toLowerCase();
|
||||
}
|
||||
}
|
||||
current = current.prev;
|
||||
}
|
||||
subjects.push({ id: idKey, className: classKey, tag: tagKey });
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
// fall through
|
||||
}
|
||||
if (!subjects.length) {
|
||||
subjects.push({ id: null, className: null, tag: null });
|
||||
}
|
||||
this.#cache.set(cacheKey, subjects);
|
||||
return subjects;
|
||||
};
|
||||
|
||||
/**
|
||||
* 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 ?? [];
|
||||
};
|
||||
}
|
||||
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']);
|
||||
3150
node_modules/@asamuzakjp/dom-selector/src/js/finder.js
generated
vendored
Normal file
3150
node_modules/@asamuzakjp/dom-selector/src/js/finder.js
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
612
node_modules/@asamuzakjp/dom-selector/src/js/matcher.js
generated
vendored
Normal file
612
node_modules/@asamuzakjp/dom-selector/src/js/matcher.js
generated
vendored
Normal file
@@ -0,0 +1,612 @@
|
||||
/**
|
||||
* 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) {
|
||||
// Ident values are not unescaped by css-tree, so we must unescape them
|
||||
// (e.g. `\5c` hex escapes, `\n` character escapes).
|
||||
if (caseInsensitive) {
|
||||
attrValue = unescapeSelector(astIdentValue).toLowerCase();
|
||||
} else {
|
||||
attrValue = unescapeSelector(astIdentValue);
|
||||
}
|
||||
} else if (astStringValue) {
|
||||
// String values (quoted) are already unescaped by css-tree, so use as-is.
|
||||
if (caseInsensitive) {
|
||||
attrValue = astStringValue.toLowerCase();
|
||||
} else {
|
||||
attrValue = astStringValue;
|
||||
}
|
||||
} 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;
|
||||
}
|
||||
}
|
||||
};
|
||||
441
node_modules/@asamuzakjp/dom-selector/src/js/parser.js
generated
vendored
Normal file
441
node_modules/@asamuzakjp/dom-selector/src/js/parser.js
generated
vendored
Normal file
@@ -0,0 +1,441 @@
|
||||
/**
|
||||
* 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 if (item === '') {
|
||||
// Empty segment at non-last position means \\ (escaped backslash)
|
||||
selectorItems.push('\\');
|
||||
i++; // skip the next segment which is the remainder after \\
|
||||
if (i < l) {
|
||||
selectorItems.push(arr[i]);
|
||||
}
|
||||
} 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';
|
||||
1109
node_modules/@asamuzakjp/dom-selector/src/js/utility.js
generated
vendored
Normal file
1109
node_modules/@asamuzakjp/dom-selector/src/js/utility.js
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
20
node_modules/@asamuzakjp/dom-selector/types/index.d.ts
generated
vendored
Normal file
20
node_modules/@asamuzakjp/dom-selector/types/index.d.ts
generated
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
export class DOMSelector {
|
||||
constructor(window: Window, document: Document, opt?: object);
|
||||
clear: () => void;
|
||||
extractSubjects: (selector: string) => Array<{
|
||||
id: string | null;
|
||||
className: string | null;
|
||||
tag: string | null;
|
||||
}>;
|
||||
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;
|
||||
};
|
||||
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;
|
||||
21
node_modules/@asamuzakjp/generational-cache/LICENSE
generated
vendored
Normal file
21
node_modules/@asamuzakjp/generational-cache/LICENSE
generated
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2026 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.
|
||||
125
node_modules/@asamuzakjp/generational-cache/README.md
generated
vendored
Normal file
125
node_modules/@asamuzakjp/generational-cache/README.md
generated
vendored
Normal file
@@ -0,0 +1,125 @@
|
||||
# generational-cache
|
||||
|
||||
[](https://github.com/asamuzaK/generationalCache/actions/workflows/ci.yaml)
|
||||
[](https://github.com/asamuzaK/generationalCache/actions/workflows/github-code-scanning/codeql)
|
||||
[](https://www.npmjs.com/package/@asamuzakjp/generational-cache)
|
||||
|
||||
A lightweight, **generational pseudo-LRU (Least Recently Used) cache** with strict maximum size limits.
|
||||
|
||||
## How it Works
|
||||
|
||||
`GenerationalCache` maintains two internal `Map` objects: `current` and `old`.
|
||||
|
||||
1. **Insertion**: New items are always added to the `current` generation.
|
||||
2. **Promotion**: If you `get` an item that exists in the `old` generation, it is promoted to the `current` generation to ensure it stays in the cache longer.
|
||||
3. **Generation Swapping**: Once the `current` generation reaches the boundary size ($max / 2$), the `old` generation is discarded, the `current` generation becomes the `old` generation, and a new empty `current` generation is created.
|
||||
|
||||
This "pseudo-LRU" approach avoids the overhead of updating timestamps or complex linked list pointers on every single access.
|
||||
|
||||
## Installation
|
||||
```bash
|
||||
npm i @asamuzakjp/generational-cache
|
||||
```
|
||||
|
||||
## Usage
|
||||
```javascript
|
||||
import { GenerationalCache } from '@asamuzakjp/generational-cache';
|
||||
|
||||
// Initialize with a max capacity of 1024 items
|
||||
const cache = new GenerationalCache(1024);
|
||||
```
|
||||
|
||||
## API
|
||||
|
||||
### `new GenerationalCache(max)`
|
||||
|
||||
Creates a new cache instance.
|
||||
|
||||
* **`max`** *(number)*: The maximum number of items the cache can hold.
|
||||
If the specified value is less than 4, or if an invalid value is specified, the default value of 4 will be used.
|
||||
|
||||
### Properties
|
||||
|
||||
* **`cache.size`** *(number, read-only)*: Returns the total number of *entries* currently in the cache.
|
||||
**Note:** To optimize for write speed, this library allows temporary key duplication between generations.
|
||||
Therefore, this value may not always reflect the exact count of unique *keys*.
|
||||
* **`cache.max`** *(number)*: Gets or sets the maximum capacity.
|
||||
**Note:** Updating this property dynamically will invoke `cache.clear()` to safely recalculate boundaries.
|
||||
|
||||
### Methods
|
||||
|
||||
* **`cache.get(key)`**
|
||||
Retrieves an item.
|
||||
If the item is found in the older generation, it is automatically promoted to the current generation to prevent it from being evicted during the next swap.
|
||||
* **Returns:** The value associated with the key, or `undefined`.
|
||||
* **`cache.set(key, value)`**
|
||||
Adds or updates an item. If adding this item pushes the current generation's size to the boundary threshold (`max / 2`), a generation swap is triggered, and the old generation is discarded.
|
||||
* **Returns:** The cache instance itself (allows chaining).
|
||||
* **`cache.has(key)`**
|
||||
Checks if a key exists in the cache (in either generation).
|
||||
* **Returns:** `true` if the key exists, otherwise `false`.
|
||||
* **`cache.delete(key)`**
|
||||
Removes an item from the cache.
|
||||
* **Returns:** `true` if the item existed and was removed, otherwise `false`.
|
||||
* **`cache.clear()`**
|
||||
Empties all items from the cache by dropping references to the internal Maps.
|
||||
|
||||
## Performance
|
||||
|
||||
Benchmarks are divided into two states to simulate real-world conditions:
|
||||
- **Cold State**: Measured with aggressive internal Garbage Collection to observe performance before full V8 TurboFan optimizations.
|
||||
- **Warm State**: Measured after sufficient warmup, representing sustained throughput under optimal JIT compilation.
|
||||
|
||||
*The results below reflect the sustained operations per second (ops/sec), calculated from the average latency (`ns/iter`). Higher values indicate better performance.*
|
||||
|
||||
### Benchmark Environment
|
||||
- **Engine:** Node.js v24.x (V8)
|
||||
- **Measurement:** [mitata](https://github.com/evanwashere/mitata).
|
||||
- **Comparison:** [LRUCache](https://www.npmjs.com/package/lru-cache) (v11.x), [QuickLRU](https://www.npmjs.com/package/quick-lru) (v7.x), [Mnemonist](https://www.npmjs.com/package/mnemonist) (v0.40.x)
|
||||
|
||||
### 1. Small Cache (Max Size = 512)
|
||||
| Scenario | State | **GenerationalCache** | LRUCache | QuickLRU | Mnemonist |
|
||||
| :--- | :--- | :--- | :--- | :--- | :--- |
|
||||
| **Set** | Cold | **17,733,640 ops/sec** | 4,933,885 ops/sec | 13,506,212 ops/sec | **17,229,496 ops/sec** |
|
||||
| | Warm | **23,030,861 ops/sec** | 15,216,068 ops/sec | 18,175,209 ops/sec | 19,409,937 ops/sec |
|
||||
| **Get** | Cold | 17,717,930 ops/sec | 7,633,587 ops/sec | 13,734,377 ops/sec | **30,731,407 ops/sec** |
|
||||
| | Warm | 21,724,961 ops/sec | 24,148,756 ops/sec | 16,385,384 ops/sec | **35,688,793 ops/sec** |
|
||||
| **Eviction** | Cold | **16,700,066 ops/sec** | 6,953,619 ops/sec | 13,285,505 ops/sec | 4,925,865 ops/sec |
|
||||
| | Warm | **23,148,148 ops/sec** | 9,040,773 ops/sec | 16,903,313 ops/sec | 8,037,293 ops/sec |
|
||||
|
||||
### 2. Medium Cache (Max Size = 2,048)
|
||||
| Scenario | State | **GenerationalCache** | LRUCache | QuickLRU | Mnemonist |
|
||||
| :--- | :--- | :--- | :--- | :--- | :--- |
|
||||
| **Set** | Cold | **15,987,210 ops/sec** | 4,874,957 ops/sec | 11,849,745 ops/sec | **15,309,246 ops/sec** |
|
||||
| | Warm | **19,716,088 ops/sec** | 13,345,789 ops/sec | 14,755,791 ops/sec | 17,325,017 ops/sec |
|
||||
| **Get** | Cold | 14,994,751 ops/sec | 7,950,389 ops/sec | 11,503,508 ops/sec | **23,651,844 ops/sec** |
|
||||
| | Warm | 17,825,311 ops/sec | 18,789,928 ops/sec | 13,838,915 ops/sec | **31,289,111 ops/sec** |
|
||||
| **Eviction** | Cold | **16,355,904 ops/sec** | 6,757,669 ops/sec | 12,074,378 ops/sec | 5,175,983 ops/sec |
|
||||
| | Warm | **21,982,853 ops/sec** | 8,089,305 ops/sec | 15,309,246 ops/sec | 7,132,158 ops/sec |
|
||||
|
||||
### 3. Large Cache (Max Size = 8,192)
|
||||
| Scenario | State | **GenerationalCache** | LRUCache | QuickLRU | Mnemonist |
|
||||
| :--- | :--- | :--- | :--- | :--- | :--- |
|
||||
| **Set** | Cold | **13,679,890 ops/sec** | 3,954,288 ops/sec | 8,126,777 ops/sec | 10,972,130 ops/sec |
|
||||
| | Warm | **20,593,080 ops/sec** | 12,054,001 ops/sec | 12,995,451 ops/sec | 15,600,624 ops/sec |
|
||||
| **Get** | Cold | 11,918,951 ops/sec | 5,785,363 ops/sec | 9,067,827 ops/sec | **16,784,155 ops/sec** |
|
||||
| | Warm | 16,781,339 ops/sec | 17,247,326 ops/sec | 12,733,987 ops/sec | **31,436,655 ops/sec** |
|
||||
| **Eviction** | Cold | **13,561,160 ops/sec** | 5,510,249 ops/sec | 9,642,271 ops/sec | 4,040,404 ops/sec |
|
||||
| | Warm | **21,128,248 ops/sec** | 7,082,152 ops/sec | 13,208,294 ops/sec | 6,023,007 ops/sec |
|
||||
|
||||
### 4. Cyclic Access (Max Size = 8,192 / Working Set = 5,000)
|
||||
| Metric | **GenerationalCache** | LRUCache | QuickLRU | Mnemonist |
|
||||
| :--- | :--- | :--- | :--- | :--- |
|
||||
| **Hit Rate** | 78.30% | **100.00%** | **100.00%** | **100.00%** |
|
||||
| **Throughput** | 10,365,916 ops/sec | 40,832,993 ops/sec | 40,950,040 ops/sec | **48,426,150 ops/sec** |
|
||||
|
||||
## Key Characteristics
|
||||
|
||||
* **High Eviction Efficiency**: `GenerationalCache` demonstrates strong throughput during high-turnover workloads, maintaining a performance margin compared to standard LRU designs in large-scale eviction scenarios.
|
||||
* **Predictable Scalability**: While other libraries may experience performance degradation as cache size increases, `GenerationalCache` maintains consistent throughput due to its generational swap mechanism.
|
||||
* **Balanced Read/Write**: It provides stable and competitive performance across all basic operations (`get`, `set`), making it suitable for both read-heavy and write-heavy environments.
|
||||
* **Trade-offs**: In cyclic access patterns where the working set is greater than `max / 2` but smaller than `max`, `GenerationalCache` will experience frequent generation swaps and cache misses. To maximize the performance benefits of `GenerationalCache`, it is often better to keep the `max` size small enough to allow some evictions, rather than trying to fit the entire working set.
|
||||
|
||||
## License
|
||||
|
||||
MIT
|
||||
65
node_modules/@asamuzakjp/generational-cache/package.json
generated
vendored
Normal file
65
node_modules/@asamuzakjp/generational-cache/package.json
generated
vendored
Normal file
@@ -0,0 +1,65 @@
|
||||
{
|
||||
"name": "@asamuzakjp/generational-cache",
|
||||
"description": "A generational pseudo-LRU cache with strict maximum size limits.",
|
||||
"author": "asamuzaK",
|
||||
"license": "MIT",
|
||||
"homepage": "https://github.com/asamuzaK/generationalCache",
|
||||
"bugs": "https://github.com/asamuzaK/generationalCache/issues",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/asamuzaK/generationalCache.git"
|
||||
},
|
||||
"files": [
|
||||
"src",
|
||||
"types"
|
||||
],
|
||||
"type": "module",
|
||||
"exports": {
|
||||
".": {
|
||||
"types": "./types/index.d.ts",
|
||||
"default": "./src/index.js"
|
||||
},
|
||||
"./package.json": "./package.json"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^25.6.0",
|
||||
"c8": "^11.0.0",
|
||||
"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.5.0",
|
||||
"lru-cache": "^11.3.5",
|
||||
"mitata": "^1.0.34",
|
||||
"mnemonist": "^0.40.3",
|
||||
"mocha": "^11.7.5",
|
||||
"neostandard": "^0.13.0",
|
||||
"prettier": "^3.8.2",
|
||||
"quick-lru": "^7.3.0",
|
||||
"typescript": "^6.0.2"
|
||||
},
|
||||
"overrides": {
|
||||
"c8": {
|
||||
"yargs": "^18.0.0"
|
||||
},
|
||||
"eslint": {
|
||||
"brace-expansion": "^1.1.13"
|
||||
},
|
||||
"serialize-javascript": "^7.0.4"
|
||||
},
|
||||
"scripts": {
|
||||
"bench": "node --expose-gc ./benchmark/benchmark.js",
|
||||
"bench:worst": "node --expose-gc ./benchmark/worst-case-benchmark.js",
|
||||
"build": "npm run tsc && npm run lint && npm test",
|
||||
"lint": "eslint --fix .",
|
||||
"test": "c8 --reporter=text mocha --exit test/*.test.js",
|
||||
"tsc": "node scripts/index clean --dir=types -i && npx tsc"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^20.19.0 || ^22.12.0 || >=24.0.0"
|
||||
},
|
||||
"version": "1.0.1"
|
||||
}
|
||||
125
node_modules/@asamuzakjp/generational-cache/src/index.js
generated
vendored
Normal file
125
node_modules/@asamuzakjp/generational-cache/src/index.js
generated
vendored
Normal file
@@ -0,0 +1,125 @@
|
||||
/**
|
||||
* @file generational-cache.js
|
||||
* A generational pseudo-LRU cache with strict maximum size limits.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @template K, V
|
||||
*/
|
||||
export class GenerationalCache {
|
||||
#max;
|
||||
#boundary;
|
||||
#current = new Map();
|
||||
#old = new Map();
|
||||
|
||||
/**
|
||||
* Initializes a new instance of the GenerationalCache class.
|
||||
* @param {number} max - The maximum number of items the cache can hold.
|
||||
*/
|
||||
constructor(max) {
|
||||
this.max = max;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the total number of `entries` currently in the cache.
|
||||
* @note To optimize for write speed, this library allows temporary key
|
||||
* duplication between generations. Therefore, this value may not always
|
||||
* reflect the exact count of unique `keys`.
|
||||
* @returns {number} The total entry count.
|
||||
*/
|
||||
get size() {
|
||||
return this.#current.size + this.#old.size;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the maximum capacity of the cache.
|
||||
* @returns {number} The maximum size limit.
|
||||
*/
|
||||
get max() {
|
||||
return this.#max;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the maximum capacity of the cache and recalculates the boundary.
|
||||
* Clears the cache when updated.
|
||||
* @param {number} value - The new maximum capacity to set.
|
||||
*/
|
||||
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();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves an item from the cache.
|
||||
* If the item is in the older generation, it gets promoted to the current
|
||||
* generation.
|
||||
* @param {K} key - The key of the element to return.
|
||||
* @returns {V | undefined} The element associated with the specified key, or
|
||||
* undefined if the key cannot be found.
|
||||
*/
|
||||
get(key) {
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds or updates an element with a specified key and a value to the cache.
|
||||
* @param {K} key - The key of the element to add.
|
||||
* @param {V} value - The value of the element to add.
|
||||
* @returns {GenerationalCache} The cache object itself.
|
||||
*/
|
||||
set(key, value) {
|
||||
this.#current.set(key, value);
|
||||
// Swap generations if the current map reaches the boundary
|
||||
if (this.#current.size >= this.#boundary) {
|
||||
this.#old = this.#current;
|
||||
this.#current = new Map();
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a boolean indicating whether an element with the specified key
|
||||
* exists or not.
|
||||
* @param {K} key - The key of the element to test for presence.
|
||||
* @returns {boolean} true if an element with the specified key exists in the
|
||||
* cache; otherwise false.
|
||||
*/
|
||||
has(key) {
|
||||
return this.#current.has(key) || this.#old.has(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the specified element from the cache.
|
||||
* @param {K} key - The key of the element to remove.
|
||||
* @returns {boolean} true if an element in the cache existed and has been
|
||||
* removed, or false if the element does not exist.
|
||||
*/
|
||||
delete(key) {
|
||||
const deletedFromCurrent = this.#current.delete(key);
|
||||
const deletedFromOld = this.#old.delete(key);
|
||||
return deletedFromCurrent || deletedFromOld;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all elements from the cache.
|
||||
*/
|
||||
clear() {
|
||||
this.#current.clear();
|
||||
this.#old.clear();
|
||||
}
|
||||
}
|
||||
12
node_modules/@asamuzakjp/generational-cache/types/index.d.ts
generated
vendored
Normal file
12
node_modules/@asamuzakjp/generational-cache/types/index.d.ts
generated
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
export class GenerationalCache<K, V> {
|
||||
constructor(max: number);
|
||||
set max(value: number);
|
||||
get max(): number;
|
||||
get size(): number;
|
||||
get(key: K): V | undefined;
|
||||
set(key: K, value: V): GenerationalCache<any, any>;
|
||||
has(key: K): boolean;
|
||||
delete(key: K): boolean;
|
||||
clear(): void;
|
||||
#private;
|
||||
}
|
||||
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));
|
||||
```
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user