diff --git a/.gitignore b/.gitignore index 98a11760bf..3d300c46e5 100644 --- a/.gitignore +++ b/.gitignore @@ -34,3 +34,5 @@ kms.db *.db target/ !.gitkeep # keep the empty dir in repo + +node_modules/ diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000000..2231937d06 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,6 @@ +{ + "name": "swarmauri-sdk", + "lockfileVersion": 3, + "requires": true, + "packages": {} +} diff --git a/pkgs/experimental/layout_engine/README.md b/pkgs/experimental/layout_engine/README.md index 6352467dcb..a15a45c628 100644 --- a/pkgs/experimental/layout_engine/README.md +++ b/pkgs/experimental/layout_engine/README.md @@ -115,6 +115,79 @@ artifacts directly from the repository. - **Site-aware routing helpers** – `layout_engine.targets.SiteRouter` produces SSR HTML shells, manifest JSON payloads, and ESM import maps you can bind directly to FastAPI, Starlette, or any HTTP framework route handlers. +### Unified manifest schema + +Every manifest emitted by Layout Engine adheres to the following structure: + +```json +{ + "kind": "layout_manifest", + "version": "2025.10", + "viewport": {"width": 1280, "height": 720}, + "grid": { + "row_height": 160, + "gap_x": 24, + "gap_y": 24, + "columns": [{"size": {"value": 1, "unit": "fr", "min_px": 72}}], + "baseline_unit": 12, + "tokens": {"columns": "sgd:columns:12", "baseline": "sgd:baseline:12"} + }, + "tiles": [ + { + "id": "hero", + "role": "swarmakit:vue:hero-card", + "frame": {"x": 0, "y": 0, "w": 1280, "h": 320}, + "props": {"title": "Quarterly Highlights", "accent": "indigo", "size": "lg"}, + "atom": { + "role": "swarmakit:vue:hero-card", + "module": "@swarmakit/vue", + "export": "HeroCard", + "framework": "vue", + "package": "@swarmakit/vue", + "family": "swarmakit", + "version": "0.0.22", + "defaults": {"size": "md"}, + "tokens": {"variant": "hero"}, + "registry": { + "name": "swarmakit", + "framework": "vue", + "version": "0.0.22" + } + } + } + ], + "site": { + "active_page": "dashboard", + "navigation": {"base_path": "/"}, + "pages": [ + {"id": "dashboard", "route": "/", "title": "Dashboard"}, + {"id": "catalog", "route": "/catalog", "title": "Catalog"} + ] + }, + "channels": [ + { + "id": "ui.events", + "scope": "page", + "topic": "page:{page_id}:ui", + "description": "UI event bus", + "payload_schema": {"type": "object"}, + "meta": {"transport": "ws"} + } + ], + "ws_routes": [ + {"path": "/ws/ui", "channels": ["ui.events"], "description": "Multiplexed UI events"} + ], + "etag": "61d6d0d0b6f3c5f5..." +} +``` + +Key highlights: + +- **Grid tokens** capture Swiss-grid presets so wrappers can align columns/gaps/baselines without recomputing measurements. +- **Atom metadata** preserves registry origin and package details, enabling thin wrappers (Vue, Svelte, React) to auto-import `@swarmakit/{framework}` modules with no bespoke glue. +- **Site block** embeds page navigation so multi-page experiences hydrate from a single manifest. +- **Channels & websocket routes** describe mux topics so runtime shells subscribe automatically. + ### Serving manifests and import maps `SiteRouter` centralizes the endpoints required by single-page and multi-page applications. Use @@ -136,6 +209,24 @@ that powers the import map consumed by Vue's module loader. Defaults defined on merged with tile props every time a manifest is built, so downstream atoms always receive a complete prop payload. +#### Registering SwarmaKit atoms + +`layout-engine` does not automatically install SwarmaKit presets. When `layout_engine_atoms` is +available you can populate an `AtomRegistry` using the helper introduced in v0.1.0: + +```python +from layout_engine import AtomRegistry, register_swarma_atoms + +atoms = register_swarma_atoms(AtomRegistry(), catalog="vue") + +# optional overrides — merged instead of replaced +atoms.override("swarmakit:vue:button", defaults={"size": "lg"}) +``` + +The helper lazily imports `layout_engine_atoms.catalog` so projects without SwarmaKit remain +functional. Overrides use additive merges (defaults/tokens/registry) to avoid clobbering preset +values. + ```python from layout_engine import AtomRegistry, AtomSpec from layout_engine.mfe.default import RemoteRegistry @@ -437,6 +528,11 @@ MPA pages perform the same fetch using their page-specific manifest endpoint, so call `loadManifest(`/catalog/${itemId}/manifest.json`, new URLSearchParams(location.search))` and the server will deliver props for the requested `itemId`. +Thin wrappers for React or Svelte follow identical steps: fetch the manifest, honour `manifest.grid.tokens` +when computing breakpoints, and import atoms using the `module`/`export` values stored in each tile's +`atom` block. Because registry metadata travels with the manifest, no framework-specific glue or +per-role switch statements are necessary. + ### 5. Event handling with multiplexed WebSockets Install the optional server extras (`layout-engine[server]`) plus the WebSocket JSON-RPC transport diff --git a/pkgs/experimental/layout_engine/examples/vue-example/README.md b/pkgs/experimental/layout_engine/examples/vue-example/README.md deleted file mode 100644 index 217957781c..0000000000 --- a/pkgs/experimental/layout_engine/examples/vue-example/README.md +++ /dev/null @@ -1,33 +0,0 @@ -# Vue Dashboard Example - -This example shows how to consume a `layout-engine` manifest from a Vue 3 -single-page application. It pairs the core DSL with the curated presets shipped -by `layout-engine-atoms`, then renders the resulting dashboard with Vue -components that understand each atom role. - -## Files - -- `generate_manifest.py` — builds `dashboard.manifest.json` using - `layout-engine` + `layout-engine-atoms`. -- `dashboard.manifest.json` — sample manifest checked into the repo so the app - works out of the box. -- `index.html` & `src/` — Vue front-end that fetches the manifest at runtime. - -## Run the example - -1. Regenerate the manifest (optional after edits): - - ```bash - uv run --directory pkgs/experimental/layout_engine --package layout-engine python examples/vue-example/generate_manifest.py - ``` - -2. Serve the folder locally (any static server works): - - ```bash - cd pkgs/experimental/layout_engine/examples/vue-example - python -m http.server 9000 - ``` - -3. Open in your browser. Vue bootstraps the dashboard, - maps atom roles to Vue components, and lays out the tiles using the manifest's - grid metadata. diff --git a/pkgs/experimental/layout_engine/examples/vue-example/dashboard.manifest.json b/pkgs/experimental/layout_engine/examples/vue-example/dashboard.manifest.json deleted file mode 100644 index 50d3b3d6d2..0000000000 --- a/pkgs/experimental/layout_engine/examples/vue-example/dashboard.manifest.json +++ /dev/null @@ -1,287 +0,0 @@ -{ - "etag": "2302c45690a68582bbdbc59b41c5e2ce7a5bc7054bc96dea69a843e92d2002a6", - "grid": { - "breakpoints": [], - "columns": [ - { - "size": { - "max_px": null, - "min_px": 200, - "unit": "fr", - "value": 0.3333333333333333 - } - }, - { - "size": { - "max_px": null, - "min_px": 200, - "unit": "fr", - "value": 0.3333333333333333 - } - }, - { - "size": { - "max_px": null, - "min_px": 200, - "unit": "fr", - "value": 0.3333333333333333 - } - } - ], - "gap_x": 12, - "gap_y": 12, - "row_height": 180 - }, - "kind": "layout_manifest", - "tiles": [ - { - "atom": { - "defaults": { - "format": "currency_compact", - "sparkline": true - }, - "export": "KpiCard", - "module": "@swarmauri/atoms/Metrics", - "version": "1.0.0" - }, - "frame": { - "h": 180, - "w": 418, - "x": 0, - "y": 0 - }, - "id": "tile_revenue", - "props": { - "delta": 0.18, - "format": "currency_compact", - "label": "Net Revenue", - "period": "QTD", - "sparkline": true, - "trend": "up", - "value": 12400000 - }, - "role": "viz:metric:kpi" - }, - { - "atom": { - "defaults": { - "format": "currency_compact", - "sparkline": true - }, - "export": "KpiCard", - "module": "@swarmauri/atoms/Metrics", - "version": "1.0.0" - }, - "frame": { - "h": 180, - "w": 419, - "x": 430, - "y": 0 - }, - "id": "tile_users", - "props": { - "delta": 0.12, - "format": "currency_compact", - "label": "Active Workspaces", - "period": "Trailing 30d", - "sparkline": true, - "trend": "up", - "value": 48200 - }, - "role": "viz:metric:kpi" - }, - { - "atom": { - "defaults": { - "format": "currency_compact", - "sparkline": true - }, - "export": "KpiCard", - "module": "@swarmauri/atoms/Metrics", - "version": "1.0.0" - }, - "frame": { - "h": 180, - "w": 419, - "x": 861, - "y": 0 - }, - "id": "tile_retention", - "props": { - "delta": 0.02, - "format": "currency_compact", - "label": "Logo Retention", - "period": "Trailing 12m", - "sparkline": true, - "trend": "up", - "value": 0.931 - }, - "role": "viz:metric:kpi" - }, - { - "atom": { - "defaults": { - "curve": "linear", - "legend": false - }, - "export": "LineChart", - "module": "@swarmauri/atoms/charts/Timeseries", - "version": "1.0.0" - }, - "frame": { - "h": 180, - "w": 1280, - "x": 0, - "y": 192 - }, - "id": "tile_trend", - "props": { - "curve": "linear", - "label": "Revenue trend", - "legend": false, - "series": [ - { - "label": "New ARR", - "points": [ - { - "x": "2024-09-01", - "y": 8.2 - }, - { - "x": "2024-10-01", - "y": 8.5 - }, - { - "x": "2024-11-01", - "y": 9.2 - }, - { - "x": "2024-12-01", - "y": 10.4 - }, - { - "x": "2025-01-01", - "y": 11.05 - }, - { - "x": "2025-02-01", - "y": 11.6 - }, - { - "x": "2025-03-01", - "y": 12.4 - } - ] - }, - { - "label": "Expansion ARR", - "points": [ - { - "x": "2024-09-01", - "y": 2.7 - }, - { - "x": "2024-10-01", - "y": 2.9 - }, - { - "x": "2024-11-01", - "y": 3.2 - }, - { - "x": "2024-12-01", - "y": 3.7 - }, - { - "x": "2025-01-01", - "y": 4.0 - }, - { - "x": "2025-02-01", - "y": 4.4 - }, - { - "x": "2025-03-01", - "y": 4.8 - } - ] - } - ] - }, - "role": "viz:timeseries:line" - }, - { - "atom": { - "defaults": { - "dense": false, - "striped": true - }, - "export": "DataTable", - "module": "@swarmauri/atoms/Table", - "version": "1.0.0" - }, - "frame": { - "h": 180, - "w": 1280, - "x": 0, - "y": 384 - }, - "id": "tile_ops", - "props": { - "columns": [ - { - "key": "account", - "name": "Account" - }, - { - "key": "stage", - "name": "Stage" - }, - { - "key": "owner", - "name": "Owner" - }, - { - "key": "value", - "name": "ARR ($k)" - } - ], - "dense": false, - "label": "Top expansion opportunities", - "rows": [ - { - "account": "Globex Retail", - "owner": "Varma", - "stage": "Contracting", - "value": 240 - }, - { - "account": "Initech", - "owner": "Garcia", - "stage": "Proposal", - "value": 190 - }, - { - "account": "Aperture Labs", - "owner": "Chen", - "stage": "Discovery", - "value": 160 - }, - { - "account": "Wonka Foods", - "owner": "Singh", - "stage": "Demo", - "value": 130 - } - ], - "striped": true - }, - "role": "viz:table:basic" - } - ], - "version": "2025.10", - "viewport": { - "height": 960, - "width": 1280 - } -} diff --git a/pkgs/experimental/layout_engine/examples/vue-example/generate_manifest.py b/pkgs/experimental/layout_engine/examples/vue-example/generate_manifest.py deleted file mode 100644 index e83d574f9c..0000000000 --- a/pkgs/experimental/layout_engine/examples/vue-example/generate_manifest.py +++ /dev/null @@ -1,195 +0,0 @@ -"""Build the dashboard manifest consumed by the Vue example. - -Run with: - uv run --directory pkgs/experimental/layout_engine --package layout-engine \\ - python examples/vue-example/generate_manifest.py -""" - -from __future__ import annotations - -import sys -from pathlib import Path - - -CURRENT_DIR = Path(__file__).resolve().parent -REPO_ROOT = CURRENT_DIR.parents[5] - -# Ensure local packages are importable when running the script directly. -sys.path.insert(0, str(REPO_ROOT / "pkgs/experimental/layout_engine/src")) -sys.path.insert(0, str(REPO_ROOT / "pkgs/experimental/layout_engine_atoms/src")) - -from layout_engine import ( # noqa: E402 (deferred import) - SizeToken, - TileSpec, - Viewport, - block, - col, - manifest_to_json, - quick_manifest_from_table, - row, - table, -) -from layout_engine_atoms import build_default_registry # noqa: E402 - - -def build_manifest_path() -> Path: - return CURRENT_DIR / "dashboard.manifest.json" - - -def build_dashboard_manifest() -> str: - """Compose a simple revenue dashboard manifest.""" - registry = build_default_registry() - - # Adjust a few presets to match our demo needs. - registry.override( - "viz:metric:kpi", - defaults={"format": "currency_compact", "sparkline": True}, - ) - registry.override( - "viz:timeseries:line", - defaults={"legend": False, "curve": "linear"}, - ) - - layout = table( - row( - col(block("tile_revenue"), size=SizeToken.m), - col(block("tile_users"), size=SizeToken.m), - col(block("tile_retention"), size=SizeToken.m), - ), - row( - col(block("tile_trend"), size=SizeToken.xl), - ), - row( - col(block("tile_ops"), size=SizeToken.xl), - ), - ) - - tiles = [ - TileSpec( - id="tile_revenue", - role="viz:metric:kpi", - props={ - "label": "Net Revenue", - "value": 12400000, - "delta": 0.18, - "trend": "up", - "period": "QTD", - }, - ), - TileSpec( - id="tile_users", - role="viz:metric:kpi", - props={ - "label": "Active Workspaces", - "value": 48200, - "delta": 0.12, - "trend": "up", - "period": "Trailing 30d", - }, - ), - TileSpec( - id="tile_retention", - role="viz:metric:kpi", - props={ - "label": "Logo Retention", - "value": 0.931, - "delta": 0.02, - "trend": "up", - "period": "Trailing 12m", - }, - ), - TileSpec( - id="tile_trend", - role="viz:timeseries:line", - props={ - "label": "Revenue trend", - "series": [ - { - "label": "New ARR", - "points": [ - {"x": "2024-09-01", "y": 8.2}, - {"x": "2024-10-01", "y": 8.5}, - {"x": "2024-11-01", "y": 9.2}, - {"x": "2024-12-01", "y": 10.4}, - {"x": "2025-01-01", "y": 11.05}, - {"x": "2025-02-01", "y": 11.6}, - {"x": "2025-03-01", "y": 12.4}, - ], - }, - { - "label": "Expansion ARR", - "points": [ - {"x": "2024-09-01", "y": 2.7}, - {"x": "2024-10-01", "y": 2.9}, - {"x": "2024-11-01", "y": 3.2}, - {"x": "2024-12-01", "y": 3.7}, - {"x": "2025-01-01", "y": 4.0}, - {"x": "2025-02-01", "y": 4.4}, - {"x": "2025-03-01", "y": 4.8}, - ], - }, - ], - }, - ), - TileSpec( - id="tile_ops", - role="viz:table:basic", - props={ - "label": "Top expansion opportunities", - "columns": [ - {"key": "account", "name": "Account"}, - {"key": "stage", "name": "Stage"}, - {"key": "owner", "name": "Owner"}, - {"key": "value", "name": "ARR ($k)"}, - ], - "rows": [ - { - "account": "Globex Retail", - "stage": "Contracting", - "owner": "Varma", - "value": 240, - }, - { - "account": "Initech", - "stage": "Proposal", - "owner": "Garcia", - "value": 190, - }, - { - "account": "Aperture Labs", - "stage": "Discovery", - "owner": "Chen", - "value": 160, - }, - { - "account": "Wonka Foods", - "stage": "Demo", - "owner": "Singh", - "value": 130, - }, - ], - }, - ), - ] - - manifest = quick_manifest_from_table( - layout, - Viewport(width=1280, height=960), - tiles, - atoms_registry=registry, - version="2025.10", - ) - - return manifest_to_json(manifest, indent=2) - - -def main() -> None: - manifest_json = build_dashboard_manifest() - output_path = build_manifest_path() - output_path.write_text(manifest_json + "\n", encoding="utf-8") - relative_output = output_path.relative_to(REPO_ROOT) - print(f"Dashboard manifest written to {relative_output}") - - -if __name__ == "__main__": - main() diff --git a/pkgs/experimental/layout_engine/examples/vue-example/index.html b/pkgs/experimental/layout_engine/examples/vue-example/index.html deleted file mode 100644 index eede8d7dbc..0000000000 --- a/pkgs/experimental/layout_engine/examples/vue-example/index.html +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - Swarmauri Layout Engine × Vue Dashboard - - - -
Loading dashboard…
- - - diff --git a/pkgs/experimental/layout_engine/examples/vue-example/src/atoms.js b/pkgs/experimental/layout_engine/examples/vue-example/src/atoms.js deleted file mode 100644 index 82480bebff..0000000000 --- a/pkgs/experimental/layout_engine/examples/vue-example/src/atoms.js +++ /dev/null @@ -1,240 +0,0 @@ -import { - computed, - defineComponent, - h, -} from "https://unpkg.com/vue@3/dist/vue.esm-browser.js"; - -const palette = ["#57b3ff", "#a385ff", "#59d6a4", "#f6c177"]; - -function formatPrimaryValue(value, format) { - if (format === "currency_compact" && typeof value === "number") { - return new Intl.NumberFormat("en-US", { - style: "currency", - currency: "USD", - notation: "compact", - maximumFractionDigits: value >= 100 ? 1 : 2, - }).format(value); - } - - if (typeof value === "number" && value < 1) { - return `${(value * 100).toFixed(1)}%`; - } - - if (typeof value === "number") { - return new Intl.NumberFormat("en-US", { - maximumFractionDigits: 1, - }).format(value); - } - - return value ?? "—"; -} - -function formatDelta(delta) { - if (delta === null || delta === undefined) { - return "—"; - } - const asPercent = (delta * 100).toFixed(Math.abs(delta) >= 0.1 ? 1 : 2); - const prefix = delta > 0 ? "+" : ""; - return `${prefix}${asPercent}%`; -} - -function pickColor(index) { - return palette[index % palette.length]; -} - -export const MetricCard = defineComponent({ - name: "MetricCard", - props: { - tile: { type: Object, required: true }, - }, - setup(props) { - const formattedValue = computed(() => - formatPrimaryValue(props.tile?.props?.value, props.tile?.props?.format), - ); - const deltaLabel = computed(() => formatDelta(props.tile?.props?.delta)); - const isNegative = computed(() => (props.tile?.props?.delta ?? 0) < 0); - const trendIcon = computed(() => { - const trend = props.tile?.props?.trend; - if (trend === "down") { - return "▼"; - } - if (trend === "flat") { - return "◆"; - } - return "▲"; - }); - - return { - formattedValue, - deltaLabel, - isNegative, - trendIcon, - }; - }, - template: ` -
-
{{ tile.props.label }}
-
{{ formattedValue }}
-
- {{ trendIcon }} - {{ deltaLabel }} -
-
vs {{ tile.props.period }}
-
- `, -}); - -function normalizeSeries(series) { - const allPoints = series.flatMap((s) => s.points || []); - if (!allPoints.length) { - return { series: [], domain: { min: 0, max: 1 } }; - } - - const min = Math.min(...allPoints.map((p) => p.y)); - const max = Math.max(...allPoints.map((p) => p.y)); - const range = max - min || 1; - - const normalized = series.map((entry, idx) => { - const points = entry.points || []; - const normalizedPoints = points.map((point, pointIdx) => { - const step = - points.length > 1 ? pointIdx / (points.length - 1) : pointIdx; - const x = step * 100; - const y = 90 - ((point.y - min) / range) * 80; - return { x, y, rawX: point.x, rawY: point.y }; - }); - return { - label: entry.label ?? `Series ${idx + 1}`, - color: pickColor(idx), - points: normalizedPoints, - }; - }); - - return { series: normalized, domain: { min, max } }; -} - -export const LineChartCard = defineComponent({ - name: "LineChartCard", - props: { - tile: { type: Object, required: true }, - }, - setup(props) { - const normalized = computed(() => - normalizeSeries(props.tile?.props?.series ?? []), - ); - - const polylineData = computed(() => - normalized.value.series.map((entry) => ({ - color: entry.color, - points: entry.points.map((p) => `${p.x},${p.y}`).join(" "), - })), - ); - - const latestSnapshot = computed(() => - normalized.value.series.map((entry) => { - const last = entry.points.at(-1); - return { - label: entry.label, - color: entry.color, - value: last?.rawY ?? "—", - }; - }), - ); - - return { - normalized, - polylineData, - latestSnapshot, - formatPrimaryValue, - }; - }, - template: ` -
-
-
-

{{ tile.props.label }}

-
- - {{ snapshot.label }}: - {{ formatPrimaryValue(snapshot.value) }} -
-
-
-
- - - - - - - - - - -
-
- `, -}); - -export const TableCard = defineComponent({ - name: "TableCard", - props: { - tile: { type: Object, required: true }, - }, - setup(props) { - const columns = computed(() => props.tile?.props?.columns ?? []); - const rows = computed(() => props.tile?.props?.rows ?? []); - - return { columns, rows }; - }, - template: ` -
-

{{ tile.props.label }}

- - - - - - - - - - - -
{{ column.name }}
{{ row[column.key] }}
-
- `, -}); - -export const PlaceholderCard = defineComponent({ - name: "PlaceholderCard", - props: { - tile: { type: Object, required: true }, - }, - setup(props) { - return () => - h("div", { class: "placeholder-card" }, [ - h("h3", { class: "table-card__title" }, [ - "Missing renderer for ", - props.tile.role, - ]), - h("pre", null, JSON.stringify(props.tile.props, null, 2)), - ]); - }, -}); - -export const atomRenderers = { - "viz:metric:kpi": MetricCard, - "viz:timeseries:line": LineChartCard, - "viz:table:basic": TableCard, - default: PlaceholderCard, -}; diff --git a/pkgs/experimental/layout_engine/examples/vue-example/src/main.js b/pkgs/experimental/layout_engine/examples/vue-example/src/main.js deleted file mode 100644 index 766a1eeb07..0000000000 --- a/pkgs/experimental/layout_engine/examples/vue-example/src/main.js +++ /dev/null @@ -1,168 +0,0 @@ -import { - computed, - createApp, - defineComponent, - onMounted, - reactive, -} from "https://unpkg.com/vue@3/dist/vue.esm-browser.js"; - -import { atomRenderers } from "./atoms.js"; - -function computeGridPlacement(frame, grid, viewport) { - if (!grid || !frame) { - return {}; - } - - const columnCount = grid.columns?.length ?? 1; - const totalGap = grid.gap_x * Math.max(columnCount - 1, 0); - const averageColumnWidth = - columnCount > 0 - ? (viewport.width - totalGap) / columnCount - : viewport.width; - const stepX = averageColumnWidth + grid.gap_x; - const stepY = grid.row_height + grid.gap_y; - - const columnStart = Math.floor(frame.x / stepX) + 1; - const rowStart = Math.floor(frame.y / stepY) + 1; - const columnSpan = Math.max( - 1, - Math.min( - columnCount, - Math.round(frame.w / stepX) || 1, - columnCount - columnStart + 1, - ), - ); - const rowSpan = Math.max(1, Math.round(frame.h / grid.row_height) || 1); - - return { - gridColumn: `${columnStart} / span ${columnSpan}`, - gridRow: `${rowStart} / span ${rowSpan}`, - }; -} - -const TileHost = defineComponent({ - name: "TileHost", - props: { - tile: { type: Object, required: true }, - grid: { type: Object, required: true }, - viewport: { type: Object, required: true }, - }, - setup(props) { - const renderer = computed( - () => atomRenderers[props.tile.role] ?? atomRenderers.default, - ); - - const placement = computed(() => - computeGridPlacement(props.tile.frame, props.grid, props.viewport), - ); - - return { - renderer, - placement, - }; - }, - template: ` -
- -
- `, -}); - -const DashboardApp = defineComponent({ - name: "DashboardApp", - components: { TileHost }, - setup() { - const state = reactive({ - manifest: null, - loading: true, - error: null, - }); - - async function fetchManifest() { - try { - const response = await fetch("./dashboard.manifest.json"); - if (!response.ok) { - throw new Error(`Failed to load manifest: ${response.statusText}`); - } - - state.manifest = await response.json(); - } catch (err) { - state.error = err; - } finally { - state.loading = false; - } - } - - onMounted(fetchManifest); - - const gridStyle = computed(() => { - if (!state.manifest) { - return {}; - } - - const grid = state.manifest.grid; - const columnCount = grid.columns?.length ?? 1; - return { - gridTemplateColumns: `repeat(${columnCount}, minmax(200px, 1fr))`, - gap: `${grid.gap_y}px ${grid.gap_x}px`, - }; - }); - - const summary = computed(() => { - if (!state.manifest) { - return { tileCount: 0, roleCount: 0 }; - } - const tiles = state.manifest.tiles ?? []; - const roles = new Set(tiles.map((tile) => tile.role)); - return { tileCount: tiles.length, roleCount: roles.size }; - }); - - return { - state, - gridStyle, - summary, - }; - }, - template: ` -
-
-
-

Revenue Operations Overview

-

- Manifest v{{ state.manifest?.version ?? "—" }} - - Tiles: {{ summary.tileCount }} - - Roles: {{ summary.roleCount }} -

-
-
- Viewport: {{ state.manifest?.viewport?.width ?? "—" }}×{{ state.manifest?.viewport?.height ?? "—" }} - - Generated by layout-engine -
-
- -
Fetching manifest…
-
- Failed to load manifest: {{ state.error.message }} -
- -
- -
-
- `, -}); - -createApp(DashboardApp).mount("#app"); diff --git a/pkgs/experimental/layout_engine/examples/vue-example/src/styles.css b/pkgs/experimental/layout_engine/examples/vue-example/src/styles.css deleted file mode 100644 index 16dda6d4ee..0000000000 --- a/pkgs/experimental/layout_engine/examples/vue-example/src/styles.css +++ /dev/null @@ -1,186 +0,0 @@ -:root { - color-scheme: dark; - font-family: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; - background: radial-gradient(circle at top left, #1f2654, #070a1f 60%); - color: #f5f7ff; -} - -body { - margin: 0; -} - -a { - color: #57b3ff; -} - -#app { - min-height: 100vh; - display: flex; - align-items: stretch; - justify-content: center; - padding: 40px 24px; - box-sizing: border-box; -} - -.dashboard { - width: 100%; - max-width: 1280px; - display: flex; - flex-direction: column; - gap: 28px; -} - -.dashboard__header { - display: flex; - flex-wrap: wrap; - justify-content: space-between; - gap: 16px; -} - -.dashboard__title { - margin: 0; - font-size: 1.75rem; - font-weight: 600; -} - -.dashboard__meta { - display: flex; - align-items: center; - gap: 16px; - opacity: 0.8; - font-size: 0.95rem; -} - -.dashboard-grid { - display: grid; - grid-template-columns: repeat(3, minmax(200px, 1fr)); - grid-auto-rows: 180px; - gap: 12px; -} - -.tile { - background: rgba(18, 25, 58, 0.85); - border: 1px solid rgba(87, 179, 255, 0.12); - border-radius: 16px; - padding: 20px; - box-sizing: border-box; - backdrop-filter: blur(12px); - display: flex; - flex-direction: column; -} - -.metric-card__label { - font-size: 0.95rem; - opacity: 0.75; -} - -.metric-card__value { - margin-top: 8px; - font-size: 2rem; - font-weight: 600; -} - -.metric-card__delta { - margin-top: 12px; - font-size: 0.95rem; - display: inline-flex; - align-items: center; - gap: 6px; - padding: 4px 10px; - border-radius: 999px; - background: rgba(87, 179, 255, 0.15); -} - -.metric-card__delta.negative { - background: rgba(255, 102, 102, 0.18); -} - -.metric-card__footnote { - margin-top: auto; - font-size: 0.85rem; - opacity: 0.6; -} - -.sparkline { - margin-top: 12px; - width: 100%; - height: 60px; -} - -.line-chart__header { - display: flex; - justify-content: space-between; - align-items: baseline; - margin-bottom: 12px; - gap: 12px; -} - -.line-chart__legend { - display: inline-flex; - gap: 12px; -} - -.legend-swatch { - width: 12px; - height: 12px; - border-radius: 999px; -} - -.line-chart__plot { - flex: 1; - position: relative; -} - -.line-chart__svg { - width: 100%; - height: 100%; -} - -.table-card__title { - margin: 0 0 16px; - font-size: 1rem; - font-weight: 600; -} - -.table-card__table { - width: 100%; - border-collapse: collapse; - font-size: 0.9rem; -} - -.table-card__table th, -.table-card__table td { - text-align: left; - padding: 8px 12px; -} - -.table-card__table tbody tr:nth-child(odd) { - background: rgba(255, 255, 255, 0.04); -} - -.table-card__table tbody tr:hover { - background: rgba(87, 179, 255, 0.18); -} - -.placeholder-card pre { - font-size: 0.75rem; - white-space: pre-wrap; - word-break: break-word; - opacity: 0.7; -} - -@media (max-width: 900px) { - .dashboard-grid { - grid-template-columns: repeat(2, minmax(180px, 1fr)); - } -} - -@media (max-width: 640px) { - #app { - padding: 24px 12px; - } - - .dashboard-grid { - grid-template-columns: 1fr; - } -} diff --git a/pkgs/experimental/layout_engine/pyproject.toml b/pkgs/experimental/layout_engine/pyproject.toml index 455edce54d..944c71f2df 100644 --- a/pkgs/experimental/layout_engine/pyproject.toml +++ b/pkgs/experimental/layout_engine/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "layout-engine" -version = "0.1.1" +version = "0.1.1.dev1" authors = [ { name = "Jacob Stewart", email = "jacob@swarmauri.com" }, ] diff --git a/pkgs/experimental/layout_engine/src/layout_engine/__init__.py b/pkgs/experimental/layout_engine/src/layout_engine/__init__.py index 6322d22b1d..7cfa3cfe34 100644 --- a/pkgs/experimental/layout_engine/src/layout_engine/__init__.py +++ b/pkgs/experimental/layout_engine/src/layout_engine/__init__.py @@ -19,9 +19,18 @@ __version__ = "0.1.0" # ---- Core ---- -from .core.size import Size, SizeToken, DEFAULT_TOKEN_WEIGHTS -from .core.viewport import Viewport -from .core.frame import Frame +from .core import ( + Size, + SizeToken, + DEFAULT_TOKEN_WEIGHTS, + Viewport, + Frame, + SWISS_GRID_COLUMNS, + SWISS_GRID_GUTTERS, + SWISS_BASELINE_UNITS, + DEFAULT_BASELINE_MULTIPLIER, + resolve_grid_tokens, +) # ---- Tiles ---- from .tiles import TileSpec, Tile, tile_spec, make_tile @@ -64,6 +73,13 @@ gridify, ) +# ---- Authoring helpers ---- +from .authoring import ( + register_swarma_atoms, + grid_token_snapshot, + build_site_manifest, +) + # ---- Compile ---- from .compile import ( LayoutCompiler, @@ -76,6 +92,9 @@ # ---- Manifest ---- from .manifest import ( + ChannelManifest, + WsRouteManifest, + SiteManifest, Manifest, ManifestBuilder, build_manifest, @@ -193,6 +212,11 @@ def quick_manifest_from_table( "DEFAULT_TOKEN_WEIGHTS", "Viewport", "Frame", + "SWISS_GRID_COLUMNS", + "SWISS_GRID_GUTTERS", + "SWISS_BASELINE_UNITS", + "DEFAULT_BASELINE_MULTIPLIER", + "resolve_grid_tokens", # tiles "TileSpec", "Tile", @@ -227,6 +251,9 @@ def quick_manifest_from_table( "table", "build_grid", "gridify", + "register_swarma_atoms", + "grid_token_snapshot", + "build_site_manifest", # compile "LayoutCompiler", "frame_map_from_placements", @@ -235,6 +262,9 @@ def quick_manifest_from_table( "ordering_by_topleft", "ordering_diff", # manifest + "SiteManifest", + "ChannelManifest", + "WsRouteManifest", "Manifest", "ManifestBuilder", "build_manifest", diff --git a/pkgs/experimental/layout_engine/src/layout_engine/atoms/bindings.py b/pkgs/experimental/layout_engine/src/layout_engine/atoms/bindings.py index 7ac3af8d51..57cd8cf7cd 100644 --- a/pkgs/experimental/layout_engine/src/layout_engine/atoms/bindings.py +++ b/pkgs/experimental/layout_engine/src/layout_engine/atoms/bindings.py @@ -7,13 +7,24 @@ def atom_to_dict(spec: AtomSpec) -> dict: - return { + data = { "role": spec.role, "module": spec.module, "export": spec.export, "version": spec.version, "defaults": dict(spec.defaults), } + if spec.family is not None: + data["family"] = spec.family + if spec.framework is not None: + data["framework"] = spec.framework + if spec.package is not None: + data["package"] = spec.package + if spec.tokens: + data["tokens"] = dict(spec.tokens) + if spec.registry: + data["registry"] = dict(spec.registry) + return data def atom_from_dict(obj: Mapping[str, Any]) -> AtomSpec: @@ -23,6 +34,11 @@ def atom_from_dict(obj: Mapping[str, Any]) -> AtomSpec: export=str(obj.get("export", "default")), version=str(obj.get("version", "1.0.0")), defaults=dict(obj.get("defaults", {})), + family=obj.get("family"), + framework=obj.get("framework"), + package=obj.get("package"), + tokens=dict(obj.get("tokens", {})), + registry=dict(obj.get("registry", {})), ) diff --git a/pkgs/experimental/layout_engine/src/layout_engine/atoms/default.py b/pkgs/experimental/layout_engine/src/layout_engine/atoms/default.py index 7bd661cd99..385c581cdf 100644 --- a/pkgs/experimental/layout_engine/src/layout_engine/atoms/default.py +++ b/pkgs/experimental/layout_engine/src/layout_engine/atoms/default.py @@ -22,12 +22,34 @@ def register_many(self, specs: Iterable[AtomSpec]) -> None: for s in specs: self.register(s) - def override(self, role: str, **fields: Any) -> AtomSpec: + def override(self, role: str, /, **fields: Any) -> AtomSpec: try: base = self._by_role[role] except KeyError: raise KeyError(f"Unknown atom role: {role}") - new_spec = base.with_overrides(**fields) + + patch = dict(fields) + + merged_fields: dict[str, Any] = {} + + if "defaults" in patch: + merged = dict(base.defaults) + merged.update(dict(patch.pop("defaults") or {})) + merged_fields["defaults"] = merged + + if "tokens" in patch: + merged_tokens = dict(base.tokens) + merged_tokens.update(dict(patch.pop("tokens") or {})) + merged_fields["tokens"] = merged_tokens + + if "registry" in patch: + merged_registry = dict(base.registry) + merged_registry.update(dict(patch.pop("registry") or {})) + merged_fields["registry"] = merged_registry + + merged_fields.update(patch) + + new_spec = base.with_overrides(**merged_fields) self._by_role[role] = new_spec return new_spec diff --git a/pkgs/experimental/layout_engine/src/layout_engine/atoms/spec.py b/pkgs/experimental/layout_engine/src/layout_engine/atoms/spec.py index 70c96fc35a..9cfd512f4d 100644 --- a/pkgs/experimental/layout_engine/src/layout_engine/atoms/spec.py +++ b/pkgs/experimental/layout_engine/src/layout_engine/atoms/spec.py @@ -51,6 +51,11 @@ class AtomSpec(BaseModel): export: str = "default" version: str = "1.0.0" defaults: Mapping[str, Any] = Field(default_factory=dict) + family: str | None = None + framework: str | None = None + package: str | None = None + tokens: Mapping[str, Any] = Field(default_factory=dict) + registry: Mapping[str, Any] = Field(default_factory=dict) @field_validator("role") @classmethod @@ -67,5 +72,10 @@ def _validate_module(cls, value: str) -> str: def _coerce_defaults(cls, value: Mapping[str, Any] | None) -> Mapping[str, Any]: return dict(value or {}) + @field_validator("tokens", "registry", mode="before") + @classmethod + def _coerce_mapping(cls, value: Mapping[str, Any] | None) -> Mapping[str, Any]: + return dict(value or {}) + def with_overrides(self, **fields: Any) -> AtomSpec: return self.model_copy(update=fields) diff --git a/pkgs/experimental/layout_engine/src/layout_engine/authoring/__init__.py b/pkgs/experimental/layout_engine/src/layout_engine/authoring/__init__.py new file mode 100644 index 0000000000..570a0f6698 --- /dev/null +++ b/pkgs/experimental/layout_engine/src/layout_engine/authoring/__init__.py @@ -0,0 +1,13 @@ +"""Authoring helpers and utilities for layout-engine.""" + +from .helpers import ( + register_swarma_atoms, + grid_token_snapshot, + build_site_manifest, +) + +__all__ = [ + "register_swarma_atoms", + "grid_token_snapshot", + "build_site_manifest", +] diff --git a/pkgs/experimental/layout_engine/src/layout_engine/authoring/helpers.py b/pkgs/experimental/layout_engine/src/layout_engine/authoring/helpers.py new file mode 100644 index 0000000000..9c281268d8 --- /dev/null +++ b/pkgs/experimental/layout_engine/src/layout_engine/authoring/helpers.py @@ -0,0 +1,85 @@ +from __future__ import annotations + +import importlib +from typing import Callable, Iterable, Mapping, MutableMapping, Sequence + +from ..atoms import AtomRegistry +from ..grid.spec import GridSpec +from ..manifest.spec import ChannelManifest, WsRouteManifest +from ..manifest.default import ManifestBuilder +from ..site.spec import SiteSpec + + +def register_swarma_atoms( + registry: AtomRegistry, + *, + catalog: str = "vue", + extra_presets: Iterable[Mapping[str, object]] | None = None, +) -> AtomRegistry: + """Load SwarmaKit presets into the provided registry. + + Imports ``layout_engine_atoms`` lazily to keep the dependency optional. + """ + + try: + catalog_module = importlib.import_module("layout_engine_atoms.catalog") + except ImportError as exc: + raise RuntimeError( + "layout_engine_atoms is required to register SwarmaKit presets" + ) from exc + + builder = getattr(catalog_module, "build_catalog", None) + if builder is None: + raise RuntimeError("layout_engine_atoms.catalog missing build_catalog()") + + cat = builder(catalog) + for preset in cat.presets(): + registry.register(preset.to_spec()) + + if extra_presets: + for preset_data in extra_presets: + preset = cat.get(preset_data.get("role")) + registry.override(preset.role, **dict(preset_data)) + + return registry + + +def grid_token_snapshot(spec: GridSpec) -> MutableMapping[str, object]: + """Return a dict describing the grid token configuration for analytics.""" + + snapshot: MutableMapping[str, object] = {} + if getattr(spec, "tokens", None): + snapshot["tokens"] = dict(spec.tokens) + if getattr(spec, "baseline_unit", None) is not None: + snapshot["baseline_unit"] = int(spec.baseline_unit) + snapshot["columns"] = len(spec.columns) + snapshot["gap_x"] = spec.gap_x + snapshot["gap_y"] = spec.gap_y + return snapshot + + +def build_site_manifest( + site: SiteSpec, + page_compilers: Mapping[str, Callable[[], Mapping[str, object]]], + *, + channels: Sequence[ChannelManifest] | None = None, + ws_routes: Sequence[WsRouteManifest] | None = None, +) -> dict[str, object]: + """Compile a mapping of page ids to manifests with shared site metadata.""" + + manifests: dict[str, object] = {} + builder = ManifestBuilder() + for page in site.pages: + compiler = page_compilers.get(page.id) + if compiler is None: + continue + view_model = compiler() + manifest = builder.build( + view_model, + active_page=page.id, + site=site, + channels=channels, + ws_routes=ws_routes, + ) + manifests[page.id] = manifest + return manifests diff --git a/pkgs/experimental/layout_engine/src/layout_engine/compile/default.py b/pkgs/experimental/layout_engine/src/layout_engine/compile/default.py index 63ebdfb9d5..a62b6e19a4 100644 --- a/pkgs/experimental/layout_engine/src/layout_engine/compile/default.py +++ b/pkgs/experimental/layout_engine/src/layout_engine/compile/default.py @@ -1,5 +1,5 @@ from __future__ import annotations -from typing import Iterable, Literal +from typing import Any, Iterable, Literal, Mapping, Sequence from ..core.viewport import Viewport from ..core.frame import Frame @@ -8,6 +8,7 @@ from ..grid.spec import GridSpec, GridTile from ..tiles.spec import TileSpec from ..atoms.base import IAtomRegistry +from ..design.tokens import layout_tokens_from_grid # --------- Core compiler --------- @@ -53,6 +54,9 @@ def view_model( frames_map: dict[str, Frame], tiles: Iterable[TileSpec], atoms_registry: IAtomRegistry | None = None, + *, + channels: Sequence[Mapping[str, object]] | None = None, + ws_routes: Sequence[Mapping[str, object]] | None = None, ) -> dict: """Build a minimal view-model dict expected by `manifest.build_manifest`. @@ -61,6 +65,21 @@ def view_model( """ from ..grid.bindings import gridspec_to_dict + layout_bundle = layout_tokens_from_grid(gs) + layout_meta = layout_bundle.get("meta", {}) + swarma_layout = layout_bundle.get("swarma_props", {}) + + def _merge_missing(target: dict, addition: Mapping[str, Any]) -> None: + for key, value in addition.items(): + if isinstance(value, Mapping): + nested = target.setdefault(key, {}) + if isinstance(nested, Mapping): + _merge_missing(nested, value) + else: + target[key] = dict(value) + else: + target.setdefault(key, value) + tiles_payload: list[dict] = [] for t in tiles: if t.id not in frames_map: @@ -85,13 +104,30 @@ def view_model( merged_props = dict(defaults) merged_props.update(base_props) entry["atom"] = { + "role": atom.role, "module": atom.module, "export": atom.export, "version": atom.version, "defaults": defaults, } + if getattr(atom, "framework", None): + entry["atom"]["framework"] = atom.framework + if getattr(atom, "package", None): + entry["atom"]["package"] = atom.package + if getattr(atom, "family", None): + entry["atom"]["family"] = atom.family + if getattr(atom, "tokens", None): + tokens = dict(getattr(atom, "tokens", {})) + if tokens: + entry["atom"]["tokens"] = tokens + if getattr(atom, "registry", None): + registry_meta = dict(getattr(atom, "registry", {})) + if registry_meta: + entry["atom"]["registry"] = registry_meta # Defaults merge underneath author-specified props entry["props"] = merged_props + if swarma_layout and entry["atom"].get("family") == "swarmakit": + _merge_missing(entry["props"], swarma_layout) tiles_payload.append(entry) vm = { @@ -99,6 +135,25 @@ def view_model( "grid": gridspec_to_dict(gs), "tiles": tiles_payload, } + if getattr(gs, "tokens", None): + vm.setdefault("meta", {})["grid_tokens"] = dict(gs.tokens) + if getattr(gs, "baseline_unit", None) is not None: + vm.setdefault("meta", {}).setdefault("grid", {})["baseline_unit"] = int( + gs.baseline_unit + ) + if layout_meta: + vm.setdefault("meta", {}).setdefault("layout", {}).update(layout_meta) + if atoms_registry is not None: + try: + revision = getattr(atoms_registry, "revision", None) + except Exception: + revision = None + if revision is not None: + vm.setdefault("meta", {}).setdefault("atoms", {})["revision"] = revision + if channels: + vm["channels"] = [dict(ch) for ch in channels] + if ws_routes: + vm["ws_routes"] = [dict(route) for route in ws_routes] return vm def view_model_from_structure( @@ -109,12 +164,22 @@ def view_model_from_structure( *, row_height: int = 180, atoms_registry: IAtomRegistry | None = None, + channels: Sequence[Mapping[str, object]] | None = None, + ws_routes: Sequence[Mapping[str, object]] | None = None, ) -> dict: """End-to-end convenience: Table/Row/Col/Block → Frames → view-model dict.""" gs, placements, frames_map = self.frames_from_structure( tbl, vp, row_height=row_height ) - return self.view_model(gs, vp, frames_map, tiles, atoms_registry=atoms_registry) + return self.view_model( + gs, + vp, + frames_map, + tiles, + atoms_registry=atoms_registry, + channels=channels, + ws_routes=ws_routes, + ) # --------- Standalone helpers (pure functions) --------- diff --git a/pkgs/experimental/layout_engine/src/layout_engine/core/__init__.py b/pkgs/experimental/layout_engine/src/layout_engine/core/__init__.py index c551a30374..62d261cfb0 100644 --- a/pkgs/experimental/layout_engine/src/layout_engine/core/__init__.py +++ b/pkgs/experimental/layout_engine/src/layout_engine/core/__init__.py @@ -1,5 +1,23 @@ from .size import Size, SizeToken, DEFAULT_TOKEN_WEIGHTS from .viewport import Viewport from .frame import Frame +from .tokens import ( + DEFAULT_BASELINE_MULTIPLIER, + SWISS_BASELINE_UNITS, + SWISS_GRID_COLUMNS, + SWISS_GRID_GUTTERS, + resolve_grid_tokens, +) -__all__ = ["Size", "SizeToken", "DEFAULT_TOKEN_WEIGHTS", "Viewport", "Frame"] +__all__ = [ + "Size", + "SizeToken", + "DEFAULT_TOKEN_WEIGHTS", + "Viewport", + "Frame", + "SWISS_GRID_COLUMNS", + "SWISS_GRID_GUTTERS", + "SWISS_BASELINE_UNITS", + "DEFAULT_BASELINE_MULTIPLIER", + "resolve_grid_tokens", +] diff --git a/pkgs/experimental/layout_engine/src/layout_engine/core/tokens.py b/pkgs/experimental/layout_engine/src/layout_engine/core/tokens.py new file mode 100644 index 0000000000..6a1744efd1 --- /dev/null +++ b/pkgs/experimental/layout_engine/src/layout_engine/core/tokens.py @@ -0,0 +1,67 @@ +from __future__ import annotations + +from typing import Any, Mapping, Sequence + +from .size import Size, SizeToken, tokens_to_fr_sizes + +DEFAULT_BASELINE_MULTIPLIER = 8 + + +def _clone_sizes(sizes: Sequence[Size]) -> list[Size]: + return [ + Size(value=size.value, unit=size.unit, min_px=size.min_px, max_px=size.max_px) + for size in sizes + ] + + +SWISS_GRID_COLUMNS: dict[str, tuple[Size, ...]] = { + "sgd:columns:12": tuple(tokens_to_fr_sizes([SizeToken.s] * 12, min_px=64)), + "sgd:columns:8": tuple(tokens_to_fr_sizes([SizeToken.m] * 8, min_px=72)), + "sgd:columns:6": tuple(tokens_to_fr_sizes([SizeToken.m] * 6, min_px=88)), + "sgd:columns:4": tuple(tokens_to_fr_sizes([SizeToken.l] * 4, min_px=120)), +} + +SWISS_GRID_GUTTERS: dict[str, dict[str, int]] = { + "sgd:gutter:tight": {"gap_x": 16, "gap_y": 16}, + "sgd:gutter:standard": {"gap_x": 24, "gap_y": 24}, + "sgd:gutter:generous": {"gap_x": 32, "gap_y": 28}, +} + +SWISS_BASELINE_UNITS: dict[str, int] = { + "sgd:baseline:8": 8, + "sgd:baseline:10": 10, + "sgd:baseline:12": 12, +} + + +def resolve_grid_tokens(tokens: Mapping[str, str]) -> dict[str, Any]: + """Materialize Swiss-grid token selections into concrete numeric values.""" + + resolved: dict[str, Any] = {"tokens": dict(tokens)} + + column_key = tokens.get("columns") + if column_key: + try: + template = SWISS_GRID_COLUMNS[column_key] + except KeyError as exc: + raise KeyError(f"Unknown column token '{column_key}'") from exc + resolved["columns"] = _clone_sizes(template) + + gutter_key = tokens.get("gutter") + if gutter_key: + try: + gutter = SWISS_GRID_GUTTERS[gutter_key] + except KeyError as exc: + raise KeyError(f"Unknown gutter token '{gutter_key}'") from exc + resolved.update(gutter) + + baseline_key = tokens.get("baseline") + if baseline_key: + try: + baseline_px = SWISS_BASELINE_UNITS[baseline_key] + except KeyError as exc: + raise KeyError(f"Unknown baseline token '{baseline_key}'") from exc + resolved["baseline_unit"] = baseline_px + resolved["row_height"] = baseline_px * DEFAULT_BASELINE_MULTIPLIER + + return resolved diff --git a/pkgs/experimental/layout_engine/src/layout_engine/design/__init__.py b/pkgs/experimental/layout_engine/src/layout_engine/design/__init__.py new file mode 100644 index 0000000000..1bd0b085cb --- /dev/null +++ b/pkgs/experimental/layout_engine/src/layout_engine/design/__init__.py @@ -0,0 +1,5 @@ +"""Design token utilities for translating grid definitions into atom-friendly props.""" + +from .tokens import layout_tokens_from_grid + +__all__ = ["layout_tokens_from_grid"] diff --git a/pkgs/experimental/layout_engine/src/layout_engine/design/tokens.py b/pkgs/experimental/layout_engine/src/layout_engine/design/tokens.py new file mode 100644 index 0000000000..50907421b9 --- /dev/null +++ b/pkgs/experimental/layout_engine/src/layout_engine/design/tokens.py @@ -0,0 +1,53 @@ +from __future__ import annotations + +from typing import Any + +from ..core.tokens import DEFAULT_BASELINE_MULTIPLIER +from ..grid.spec import GridSpec + + +def _spacing_scale(baseline: int) -> dict[str, int]: + half = max(2, baseline // 2) + quarter = max(1, baseline // 4) + return { + "xxs": quarter, + "xs": max(2, (baseline * 3) // 8), + "sm": half, + "md": baseline, + "lg": int(baseline * 1.5), + "xl": baseline * 2, + } + + +def layout_tokens_from_grid(spec: GridSpec) -> dict[str, Any]: + """Derive layout tokens and SwarmaKit-friendly layout props from a grid spec.""" + + baseline = ( + int(spec.baseline_unit) + if spec.baseline_unit is not None + else max(4, spec.row_height // DEFAULT_BASELINE_MULTIPLIER) + ) + gap_x = int(spec.gap_x) + gap_y = int(spec.gap_y) + + layout_meta = { + "baseline": baseline, + "gap": {"x": gap_x, "y": gap_y}, + "columns": len(spec.columns), + "spacing_scale": _spacing_scale(baseline), + } + + padding_x = max(4, gap_x // 2) + padding_y = max(4, gap_y // 2) + + # SwarmaKit atoms expect a `layout` block describing spacing rhythm. + swarma_layout = { + "layout": { + "padding": {"x": padding_x, "y": padding_y}, + "gap": {"x": gap_x, "y": gap_y}, + "baseline": baseline, + "spacing": _spacing_scale(baseline), + } + } + + return {"meta": layout_meta, "swarma_props": swarma_layout} diff --git a/pkgs/experimental/layout_engine/src/layout_engine/events/__init__.py b/pkgs/experimental/layout_engine/src/layout_engine/events/__init__.py index 9b6d6c53cf..e3e7cd47bf 100644 --- a/pkgs/experimental/layout_engine/src/layout_engine/events/__init__.py +++ b/pkgs/experimental/layout_engine/src/layout_engine/events/__init__.py @@ -24,6 +24,9 @@ GRID, TILE, ATOM, + register_channels, + clear_channels, + get_channel, ) from .ws import InProcEventBus, EventRouter @@ -41,6 +44,9 @@ "GRID", "TILE", "ATOM", + "register_channels", + "clear_channels", + "get_channel", "InProcEventBus", "EventRouter", "utc_now_iso", diff --git a/pkgs/experimental/layout_engine/src/layout_engine/events/spec.py b/pkgs/experimental/layout_engine/src/layout_engine/events/spec.py index a7043f7237..55fb8dd4a7 100644 --- a/pkgs/experimental/layout_engine/src/layout_engine/events/spec.py +++ b/pkgs/experimental/layout_engine/src/layout_engine/events/spec.py @@ -23,6 +23,7 @@ class EventEnvelope(BaseModel): request_id: str target: dict[str, Any] | None payload: dict[str, Any] | None + channel: str | None = None # Optional server responses diff --git a/pkgs/experimental/layout_engine/src/layout_engine/events/validators.py b/pkgs/experimental/layout_engine/src/layout_engine/events/validators.py index d66287ec47..aed0aca57b 100644 --- a/pkgs/experimental/layout_engine/src/layout_engine/events/validators.py +++ b/pkgs/experimental/layout_engine/src/layout_engine/events/validators.py @@ -1,5 +1,5 @@ from __future__ import annotations -from typing import Any, Set +from typing import Any, Mapping, Set from .spec import EventEnvelope # ----- Allow lists (expanded) ----- @@ -135,6 +135,30 @@ "atom": ATOM, } +_CHANNEL_REGISTRY: dict[str, dict[str, Any]] = {} + + +def register_channels( + channels: Mapping[str, Mapping[str, Any]] | list[Mapping[str, Any]], +) -> None: + if isinstance(channels, Mapping): + iterator = channels.values() + else: + iterator = channels + for entry in iterator: + channel_id = entry.get("id") + if not channel_id: + continue + _CHANNEL_REGISTRY[str(channel_id)] = dict(entry) + + +def clear_channels() -> None: + _CHANNEL_REGISTRY.clear() + + +def get_channel(channel_id: str) -> dict[str, Any] | None: + return _CHANNEL_REGISTRY.get(channel_id) + # Wildcard families per scope def _wildcards(scope: str) -> list[str]: @@ -195,6 +219,17 @@ def validate_envelope(e: dict[str, Any]) -> EventEnvelope: raise ValidationError(f"unknown scope: {scope}") if not is_allowed(scope, etype): raise ValidationError(f"event '{etype}' not allowed for scope '{scope}'") + channel_id = e.get("channel") + if channel_id is not None: + channel_def = _CHANNEL_REGISTRY.get(str(channel_id)) + if channel_def is None: + raise ValidationError(f"unknown channel: {channel_id}") + expected_scope = channel_def.get("scope") + if expected_scope and expected_scope != scope: + raise ValidationError( + f"channel '{channel_id}' expects scope '{expected_scope}' but received '{scope}'" + ) + return EventEnvelope( scope=scope, type=etype, @@ -205,10 +240,30 @@ def validate_envelope(e: dict[str, Any]) -> EventEnvelope: request_id=e["request_id"], target=e.get("target") or {}, payload=e.get("payload") or {}, + channel=str(channel_id) if channel_id is not None else None, ) def route_topic(ev: EventEnvelope) -> str: + if ev.channel: + channel_def = _CHANNEL_REGISTRY.get(ev.channel) + if channel_def: + template = channel_def.get("topic") + if template: + context = { + "scope": ev.scope, + "type": ev.type, + "page_id": ev.page_id or "", + "slot": ev.slot or "", + "tile_id": ev.tile_id or "", + "channel": ev.channel, + } + context.update(ev.target or {}) + try: + return template.format(**context) + except Exception: + return template + if ev.scope == "site": return "site" if ev.scope == "page": diff --git a/pkgs/experimental/layout_engine/src/layout_engine/grid/bindings.py b/pkgs/experimental/layout_engine/src/layout_engine/grid/bindings.py index 89ae7fca5e..7b233212ea 100644 --- a/pkgs/experimental/layout_engine/src/layout_engine/grid/bindings.py +++ b/pkgs/experimental/layout_engine/src/layout_engine/grid/bindings.py @@ -25,7 +25,7 @@ def gridtrack_from_dict(d: Mapping[str, Any]) -> GridTrack: min_px=int(s.get("min_px", 0)), max_px=s.get("max_px"), ) - return GridTrack(size) + return GridTrack(size=size) # ----------- GridTile ----------- @@ -51,7 +51,7 @@ def gridtile_from_dict(d: Mapping[str, Any]) -> GridTile: # ----------- GridSpec ----------- def gridspec_to_dict(gs: GridSpec) -> dict: - return { + payload = { "row_height": gs.row_height, "gap_x": gs.gap_x, "gap_y": gs.gap_y, @@ -61,15 +61,41 @@ def gridspec_to_dict(gs: GridSpec) -> dict: for (max_w, tracks) in gs.breakpoints or [] ], } + if gs.baseline_unit is not None: + payload["baseline_unit"] = int(gs.baseline_unit) + if getattr(gs, "tokens", None): + payload["tokens"] = dict(gs.tokens) + return payload def gridspec_from_dict(d: Mapping[str, Any]) -> GridSpec: cols = [gridtrack_from_dict(x) for x in d.get("columns", [])] + tokens = dict(d.get("tokens") or {}) + if not cols and tokens: + overrides = { + "row_height": d.get("row_height"), + "gap_x": d.get("gap_x"), + "gap_y": d.get("gap_y"), + "baseline_unit": d.get("baseline_unit"), + } + overrides = {k: v for k, v in overrides.items() if v is not None} + breakpoints_payload = d.get("breakpoints") + if breakpoints_payload: + overrides["breakpoints"] = [ + (int(max_w), [gridtrack_from_dict(t) for t in tracks]) + for (max_w, tracks) in breakpoints_payload + ] + return GridSpec.from_tokens(tokens, overrides=overrides) + gs = GridSpec( columns=cols, row_height=int(d.get("row_height", 180)), gap_x=int(d.get("gap_x", 12)), gap_y=int(d.get("gap_y", 12)), + baseline_unit=( + int(d["baseline_unit"]) if d.get("baseline_unit") is not None else None + ), + tokens=tokens, ) bps: List[Tuple[int, list[GridTrack]]] = [] for entry in d.get("breakpoints", []): diff --git a/pkgs/experimental/layout_engine/src/layout_engine/grid/spec.py b/pkgs/experimental/layout_engine/src/layout_engine/grid/spec.py index 60b664f4e1..dff477a8be 100644 --- a/pkgs/experimental/layout_engine/src/layout_engine/grid/spec.py +++ b/pkgs/experimental/layout_engine/src/layout_engine/grid/spec.py @@ -1,8 +1,11 @@ from __future__ import annotations -from pydantic import BaseModel +from typing import Any, Mapping + +from pydantic import BaseModel, Field from ..core.size import Size +from ..core.tokens import resolve_grid_tokens class GridTrack(BaseModel): @@ -25,7 +28,55 @@ class GridSpec(BaseModel): row_height: int = 180 gap_x: int = 12 gap_y: int = 12 - breakpoints: list[tuple[int, list[GridTrack]]] = () + baseline_unit: int | None = None + tokens: Mapping[str, str] = Field(default_factory=dict) + breakpoints: list[tuple[int, list[GridTrack]]] = Field(default_factory=list) + + @classmethod + def from_tokens( + cls, + tokens: Mapping[str, str], + *, + overrides: Mapping[str, Any] | None = None, + ) -> "GridSpec": + resolved = resolve_grid_tokens(tokens) + data: dict[str, Any] = dict(resolved) + if overrides: + data.update(overrides) + + columns_data = data.pop("columns", None) + if columns_data is None: + raise ValueError("Grid tokens must include a column definition") + + columns: list[GridTrack] = [] + for entry in columns_data: + if isinstance(entry, GridTrack): + columns.append(entry) + elif isinstance(entry, Size): + columns.append(GridTrack(size=entry)) + else: + raise TypeError( + "columns derived from tokens must be Size or GridTrack instances" + ) + + row_height = int(data.pop("row_height", cls.model_fields["row_height"].default)) + gap_x = int(data.pop("gap_x", cls.model_fields["gap_x"].default)) + gap_y = int(data.pop("gap_y", cls.model_fields["gap_y"].default)) + baseline_unit = data.pop("baseline_unit", None) + breakpoints = data.pop("breakpoints", []) + data.pop("tokens", None) + + spec = cls( + columns=columns, + row_height=row_height, + gap_x=gap_x, + gap_y=gap_y, + baseline_unit=baseline_unit, + breakpoints=breakpoints, + tokens=dict(tokens), + **data, + ) + return spec class GridTile(BaseModel): diff --git a/pkgs/experimental/layout_engine/src/layout_engine/manifest/__init__.py b/pkgs/experimental/layout_engine/src/layout_engine/manifest/__init__.py index 0c5c7deef2..42db6da3b3 100644 --- a/pkgs/experimental/layout_engine/src/layout_engine/manifest/__init__.py +++ b/pkgs/experimental/layout_engine/src/layout_engine/manifest/__init__.py @@ -8,7 +8,7 @@ - Patching: `diff(old, new)`, `apply_patch(base, patch)` """ -from .spec import Manifest +from .spec import Manifest, SiteManifest, ChannelManifest, WsRouteManifest from .default import ManifestBuilder from .utils import ( build_manifest, @@ -32,6 +32,10 @@ __all__ = [ "Manifest", + "SiteManifest", + "ChannelManifest", + "WsRouteManifest", + "SiteManifest", "ManifestBuilder", "build_manifest", "etag_of", diff --git a/pkgs/experimental/layout_engine/src/layout_engine/manifest/default.py b/pkgs/experimental/layout_engine/src/layout_engine/manifest/default.py index a30bf86534..bc28e7d229 100644 --- a/pkgs/experimental/layout_engine/src/layout_engine/manifest/default.py +++ b/pkgs/experimental/layout_engine/src/layout_engine/manifest/default.py @@ -1,6 +1,13 @@ from __future__ import annotations -from typing import Mapping, Any -from .spec import Manifest + +from typing import Any, Mapping + +from collections.abc import Iterable +from typing import Sequence + +from ..site.spec import SiteSpec +from ..events.validators import register_channels as _register_channels +from .spec import ChannelManifest, Manifest, WsRouteManifest from .utils import ( build_manifest, manifest_to_json as _manifest_to_json, @@ -8,13 +15,88 @@ ) +def _site_to_payload( + site: SiteSpec | None, active_page: str | None = None +) -> dict | None: + if site is None: + return None + pages = [ + { + "id": page.id, + "route": page.route, + "title": page.title, + "slots": [slot.model_dump() for slot in page.slots], + "meta": dict(page.meta), + } + for page in site.pages + ] + return { + "pages": pages, + "active_page": active_page, + "navigation": {"base_path": site.base_path}, + } + + +def _channels_to_payload( + channels: Sequence[ChannelManifest] | Iterable[ChannelManifest] | None, +) -> list[dict] | None: + if not channels: + return None + payload: list[dict] = [] + for entry in channels: + if isinstance(entry, ChannelManifest): + payload.append(entry.model_dump()) + else: + payload.append(ChannelManifest.model_validate(entry).model_dump()) + return payload + + +def _routes_to_payload( + routes: Sequence[WsRouteManifest] | Iterable[WsRouteManifest] | None, +) -> list[dict] | None: + if not routes: + return None + payload: list[dict] = [] + for entry in routes: + if isinstance(entry, WsRouteManifest): + payload.append(entry.model_dump()) + else: + payload.append(WsRouteManifest.model_validate(entry).model_dump()) + return payload + + class ManifestBuilder: - """Default manifest builder; thin class wrapper over utils.build_manifest().""" + """Default manifest builder with optional site metadata injection.""" def build( - self, view_model: Mapping[str, Any], *, version: str = "2025.10" + self, + view_model: Mapping[str, Any], + *, + version: str = "2025.10", + site: SiteSpec | None = None, + active_page: str | None = None, + channels: Sequence[ChannelManifest] | Iterable[ChannelManifest] | None = None, + ws_routes: Sequence[WsRouteManifest] | Iterable[WsRouteManifest] | None = None, ) -> Manifest: - return build_manifest(view_model, version=version) + if site is not None and "site" not in view_model: + site_payload = _site_to_payload(site, active_page=active_page) + if site_payload is not None: + view_model = dict(view_model) + view_model["site"] = site_payload + if channels and "channels" not in view_model: + channel_payload = _channels_to_payload(channels) + if channel_payload: + view_model = dict(view_model) + view_model["channels"] = channel_payload + if ws_routes and "ws_routes" not in view_model: + route_payload = _routes_to_payload(ws_routes) + if route_payload: + view_model = dict(view_model) + view_model["ws_routes"] = route_payload + manifest = build_manifest(view_model, version=version) + if manifest.channels: + _register_channels([ch.model_dump() for ch in manifest.channels]) + return manifest # --- context manager support --- def __enter__(self): diff --git a/pkgs/experimental/layout_engine/src/layout_engine/manifest/spec.py b/pkgs/experimental/layout_engine/src/layout_engine/manifest/spec.py index 9eb2a34b44..b7e925a1e6 100644 --- a/pkgs/experimental/layout_engine/src/layout_engine/manifest/spec.py +++ b/pkgs/experimental/layout_engine/src/layout_engine/manifest/spec.py @@ -1,22 +1,42 @@ from __future__ import annotations -from typing import Any, Mapping +from typing import Any, Mapping, Sequence -from pydantic import BaseModel +from pydantic import BaseModel, Field + +from ..events.spec import Scope + + +class SiteManifest(BaseModel): + """Optional multi-page context embedded alongside the layout payload.""" + + pages: Sequence[Mapping[str, Any]] = Field(default_factory=tuple) + active_page: str | None = None + navigation: Mapping[str, Any] = Field(default_factory=dict) + + +class ChannelManifest(BaseModel): + """Declarative description of a websocket channel.""" + + id: str + scope: Scope + topic: str + description: str | None = None + payload_schema: Mapping[str, Any] = Field(default_factory=dict) + meta: Mapping[str, Any] = Field(default_factory=dict) + + +class WsRouteManifest(BaseModel): + """Mapping from websocket endpoints to channel identifiers.""" + + path: str + channels: Sequence[str] = Field(default_factory=tuple) + description: str | None = None + meta: Mapping[str, Any] = Field(default_factory=dict) class Manifest(BaseModel): - """Canonical page manifest. - - Fields: - - kind: literal "layout_manifest" - - version: semver-ish string (e.g., "2025.10") - - viewport: {"width": int, "height": int} - - grid: mapping with row_height/gaps/columns (binding-level shape) - - tiles: list of tile payloads: - { id: str, role: str, frame: {x,y,w,h}, props: {...}, atom?: {...} } - - etag: content hash for cache/patch validation - """ + """Canonical page manifest with optional multi-page site metadata.""" kind: str version: str @@ -24,3 +44,6 @@ class Manifest(BaseModel): grid: Mapping[str, Any] tiles: list[Mapping[str, Any]] etag: str + site: SiteManifest | None = None + channels: Sequence[ChannelManifest] = Field(default_factory=tuple) + ws_routes: Sequence[WsRouteManifest] = Field(default_factory=tuple) diff --git a/pkgs/experimental/layout_engine/src/layout_engine/manifest/utils.py b/pkgs/experimental/layout_engine/src/layout_engine/manifest/utils.py index d089f3b0d1..2380d7bdd2 100644 --- a/pkgs/experimental/layout_engine/src/layout_engine/manifest/utils.py +++ b/pkgs/experimental/layout_engine/src/layout_engine/manifest/utils.py @@ -2,9 +2,10 @@ import json import hashlib -from typing import Any, Mapping +from typing import Any, Mapping, MutableMapping -from .spec import Manifest +from .spec import ChannelManifest, Manifest, SiteManifest, WsRouteManifest +from ..events.validators import ALLOW from ..compile.utils import frame_diff from ..core.frame import Frame @@ -48,12 +49,66 @@ def _normalize_tile(tile: Mapping[str, Any]) -> dict: return base +def _normalize_site( + site: Mapping[str, Any] | SiteManifest | None, +) -> SiteManifest | None: + if site is None: + return None + if isinstance(site, SiteManifest): + return site + pages = site.get("pages") or [] + active = site.get("active_page") if "active_page" in site else site.get("active") + navigation = dict(site.get("navigation") or {}) + return SiteManifest(pages=list(pages), active_page=active, navigation=navigation) + + +def _normalize_channels( + channels: Mapping[str, Any] | ChannelManifest | list[Mapping[str, Any]] | None, +) -> list[dict[str, Any]]: + if not channels: + return [] + payload: list[dict[str, Any]] = [] + items = channels + if isinstance(channels, Mapping) and not isinstance(channels, ChannelManifest): + items = channels.values() + if isinstance(items, ChannelManifest): + items = [items] + for entry in items: # type: ignore[assignment] + if isinstance(entry, ChannelManifest): + payload.append(entry.model_dump()) + else: + payload.append(ChannelManifest.model_validate(entry).model_dump()) + return payload + + +def _normalize_ws_routes( + routes: Mapping[str, Any] | WsRouteManifest | list[Mapping[str, Any]] | None, +) -> list[dict[str, Any]]: + if not routes: + return [] + payload: list[dict[str, Any]] = [] + items = routes + if isinstance(routes, Mapping) and not isinstance(routes, WsRouteManifest): + items = routes.values() + if isinstance(items, WsRouteManifest): + items = [items] + for entry in items: # type: ignore[assignment] + if isinstance(entry, WsRouteManifest): + payload.append(entry.model_dump()) + else: + payload.append(WsRouteManifest.model_validate(entry).model_dump()) + return payload + + def _normalize_view_model(data: Mapping[str, Any]) -> dict: kind = str(data.get("kind") or "layout_manifest") version = str(data.get("version") or "2025.10") viewport = dict(data.get("viewport") or {}) grid = data.get("grid") or {} tiles = [_normalize_tile(t) for t in data.get("tiles", [])] + site = _normalize_site(data.get("site")) + channels = _normalize_channels(data.get("channels")) + ws_routes = _normalize_ws_routes(data.get("ws_routes")) return { "kind": kind, "version": version, @@ -63,6 +118,9 @@ def _normalize_view_model(data: Mapping[str, Any]) -> dict: }, "grid": dict(grid), "tiles": tiles, + "site": site.model_dump() if site else None, + "channels": channels, + "ws_routes": ws_routes, } @@ -72,11 +130,20 @@ def build_manifest( payload = _normalize_view_model(view_model) if version is not None: payload["version"] = version - return Manifest(**payload, etag=etag_of(payload)) + etag_payload: MutableMapping[str, Any] = dict(payload) + manifest = Manifest(**payload, etag=etag_of(etag_payload)) + if manifest.channels: + try: + from ..events.validators import register_channels as _register_channels + except ImportError: + pass + else: + _register_channels([channel.model_dump() for channel in manifest.channels]) + return manifest def to_dict(manifest: Manifest) -> dict: - return { + data = { "kind": manifest.kind, "version": manifest.version, "viewport": dict(manifest.viewport), @@ -84,6 +151,13 @@ def to_dict(manifest: Manifest) -> dict: "tiles": [dict(t) for t in manifest.tiles], "etag": manifest.etag, } + if manifest.site is not None: + data["site"] = manifest.site.model_dump() + if manifest.channels: + data["channels"] = [channel.model_dump() for channel in manifest.channels] + if manifest.ws_routes: + data["ws_routes"] = [route.model_dump() for route in manifest.ws_routes] + return data def from_dict(data: Mapping[str, Any]) -> Manifest: @@ -120,6 +194,44 @@ def validate(manifest: Manifest) -> None: for k in ("x", "y", "w", "h"): if k not in f or not isinstance(f[k], int): raise ValueError(f"tile {t['id']} frame.{k} must be int") + if manifest.site is not None: + ids: set[str] = set() + for page in manifest.site.pages: + pid = str(page.get("id")) + route = str(page.get("route")) + title = page.get("title") + if not pid: + raise ValueError("site.pages entries require an 'id'") + if pid in ids: + raise ValueError(f"duplicate site page id: {pid}") + ids.add(pid) + if not route.startswith("/"): + raise ValueError( + f"site page '{pid}' route must start with '/': {route}" + ) + if title is None: + raise ValueError(f"site page '{pid}' missing title") + if manifest.channels: + channel_ids: set[str] = set() + for ch in manifest.channels: + if ch.id in channel_ids: + raise ValueError(f"duplicate channel id: {ch.id}") + channel_ids.add(ch.id) + if ch.scope not in ALLOW: + raise ValueError(f"channel '{ch.id}' has unknown scope '{ch.scope}'") + if not ch.topic: + raise ValueError(f"channel '{ch.id}' requires a topic") + if manifest.ws_routes: + for route in manifest.ws_routes: + if not route.path.startswith("/"): + raise ValueError(f"ws route path must start with '/': {route.path}") + for channel_id in route.channels: + if manifest.channels and channel_id not in { + c.id for c in manifest.channels + }: + raise ValueError( + f"ws route '{route.path}' references unknown channel '{channel_id}'" + ) def schema() -> dict: @@ -172,6 +284,58 @@ def schema() -> dict: }, }, "etag": {"type": "string"}, + "site": { + "type": ["object", "null"], + "properties": { + "pages": { + "type": "array", + "items": { + "type": "object", + "required": ["id", "route", "title"], + "properties": { + "id": {"type": "string"}, + "route": {"type": "string"}, + "title": {"type": "string"}, + "slots": {"type": "array"}, + "meta": {"type": "object"}, + }, + }, + }, + "active_page": {"type": ["string", "null"]}, + "navigation": {"type": "object"}, + }, + }, + "channels": { + "type": "array", + "items": { + "type": "object", + "required": ["id", "scope", "topic"], + "properties": { + "id": {"type": "string"}, + "scope": {"type": "string"}, + "topic": {"type": "string"}, + "description": {"type": "string"}, + "payload_schema": {"type": "object"}, + "meta": {"type": "object"}, + }, + }, + }, + "ws_routes": { + "type": "array", + "items": { + "type": "object", + "required": ["path"], + "properties": { + "path": {"type": "string"}, + "channels": { + "type": "array", + "items": {"type": "string"}, + }, + "description": {"type": "string"}, + "meta": {"type": "object"}, + }, + }, + }, }, "additionalProperties": False, } diff --git a/pkgs/experimental/layout_engine/tests/test_atoms_registry.py b/pkgs/experimental/layout_engine/tests/test_atoms_registry.py new file mode 100644 index 0000000000..6521cf6b34 --- /dev/null +++ b/pkgs/experimental/layout_engine/tests/test_atoms_registry.py @@ -0,0 +1,32 @@ +from __future__ import annotations + +from layout_engine.atoms.default import AtomRegistry +from layout_engine.atoms.spec import AtomSpec + + +def test_atom_registry_override_merges_defaults_and_registry() -> None: + registry = AtomRegistry() + base = AtomSpec( + role="swarmakit:vue:button", + module="@swarmakit/vue", + export="Button", + version="0.0.22", + defaults={"size": "md"}, + framework="vue", + package="@swarmakit/vue", + registry={"name": "swarmakit", "version": "0.0.22"}, + tokens={"spacing": "m"}, + ) + registry.register(base) + + updated = registry.override( + "swarmakit:vue:button", + defaults={"tone": "primary"}, + registry={"version": "0.0.23"}, + tokens={"spacing": "l"}, + ) + + assert updated.defaults == {"size": "md", "tone": "primary"} + assert updated.registry["version"] == "0.0.23" + assert updated.registry["name"] == "swarmakit" + assert updated.tokens["spacing"] == "l" diff --git a/pkgs/experimental/layout_engine/tests/test_authoring_helpers.py b/pkgs/experimental/layout_engine/tests/test_authoring_helpers.py new file mode 100644 index 0000000000..624fd19bd5 --- /dev/null +++ b/pkgs/experimental/layout_engine/tests/test_authoring_helpers.py @@ -0,0 +1,82 @@ +from __future__ import annotations + +import importlib + +from layout_engine.authoring import build_site_manifest, grid_token_snapshot +from layout_engine.authoring.helpers import register_swarma_atoms +from layout_engine.atoms import AtomRegistry +from layout_engine.core.size import Size +from layout_engine.grid.spec import GridSpec, GridTrack +from layout_engine.site.spec import PageSpec, SiteSpec, SlotSpec + + +def test_grid_token_snapshot() -> None: + spec = GridSpec( + columns=[GridTrack(size=Size(1, "fr"))], + gap_x=24, + gap_y=16, + baseline_unit=12, + tokens={"columns": "sgd:columns:4"}, + ) + snapshot = grid_token_snapshot(spec) + assert snapshot["tokens"]["columns"] == "sgd:columns:4" + assert snapshot["baseline_unit"] == 12 + assert snapshot["columns"] == 1 + + +def test_build_site_manifest_compiles_pages(monkeypatch) -> None: + site = SiteSpec( + base_path="/", + pages=( + PageSpec( + id="home", + route="/", + title="Home", + slots=(SlotSpec(name="main", role="layout"),), + ), + ), + ) + + def compiler() -> dict[str, object]: + return { + "viewport": {"width": 800, "height": 600}, + "grid": {"columns": []}, + "tiles": [], + } + + result = build_site_manifest(site, {"home": compiler}) + assert "home" in result + manifest = result["home"] + assert manifest.site is not None + assert manifest.site.pages[0]["id"] == "home" + + +def test_register_swarma_atoms_optional(monkeypatch) -> None: + registry = AtomRegistry() + + class DummyCatalog: + def __init__(self): + self._presets = [] + + def presets(self): + return [] + + def get(self, role): + raise KeyError(role) + + class DummyModule: + @staticmethod + def build_catalog(name): + return DummyCatalog() + + module_stub = DummyModule() + + def fake_import(name): + if name == "layout_engine_atoms.catalog": + return module_stub + raise ImportError(name) + + monkeypatch.setattr(importlib, "import_module", fake_import) + + registry = register_swarma_atoms(registry, catalog="vue") + assert isinstance(registry, AtomRegistry) diff --git a/pkgs/experimental/layout_engine/tests/test_events_channels.py b/pkgs/experimental/layout_engine/tests/test_events_channels.py new file mode 100644 index 0000000000..df63d4b948 --- /dev/null +++ b/pkgs/experimental/layout_engine/tests/test_events_channels.py @@ -0,0 +1,47 @@ +from __future__ import annotations + +import pytest + +from layout_engine.events import ( + ValidationError, + clear_channels, + register_channels, + route_topic, + validate_envelope, +) + + +def test_event_channel_validation_and_routing() -> None: + clear_channels() + register_channels( + [ + { + "id": "ui.events", + "scope": "page", + "topic": "page:{page_id}:ui", + } + ] + ) + + envelope_dict = { + "scope": "page", + "type": "page:load", + "page_id": "home", + "slot": None, + "tile_id": None, + "ts": "2024-01-01T00:00:00Z", + "request_id": "req-1", + "target": {}, + "payload": {}, + "channel": "ui.events", + } + + env = validate_envelope(envelope_dict) + assert env.channel == "ui.events" + topic = route_topic(env) + assert topic == "page:home:ui" + + with pytest.raises(ValidationError): + validate_envelope({**envelope_dict, "channel": "unknown"}) + + clear_channels() diff --git a/pkgs/experimental/layout_engine/tests/test_grid_tokens.py b/pkgs/experimental/layout_engine/tests/test_grid_tokens.py new file mode 100644 index 0000000000..ade7008eff --- /dev/null +++ b/pkgs/experimental/layout_engine/tests/test_grid_tokens.py @@ -0,0 +1,49 @@ +from __future__ import annotations + +from layout_engine.core import ( + DEFAULT_BASELINE_MULTIPLIER, + SWISS_BASELINE_UNITS, + resolve_grid_tokens, +) +from layout_engine.grid import GridSpec, gridspec_to_dict, gridspec_from_dict + + +def test_resolve_grid_tokens_materializes_sizes() -> None: + tokens = { + "columns": "sgd:columns:12", + "gutter": "sgd:gutter:standard", + "baseline": "sgd:baseline:8", + } + resolved = resolve_grid_tokens(tokens) + + assert len(resolved["columns"]) == 12 + assert resolved["gap_x"] == 24 + assert resolved["gap_y"] == 24 + assert resolved["baseline_unit"] == SWISS_BASELINE_UNITS["sgd:baseline:8"] + assert ( + resolved["row_height"] + == SWISS_BASELINE_UNITS["sgd:baseline:8"] * DEFAULT_BASELINE_MULTIPLIER + ) + + +def test_grid_spec_from_tokens_roundtrip() -> None: + tokens = { + "columns": "sgd:columns:8", + "gutter": "sgd:gutter:tight", + "baseline": "sgd:baseline:10", + } + + spec = GridSpec.from_tokens(tokens) + + assert spec.tokens == tokens + assert spec.baseline_unit == SWISS_BASELINE_UNITS["sgd:baseline:10"] + assert len(spec.columns) == 8 + + payload = gridspec_to_dict(spec) + assert payload["tokens"] == tokens + assert payload["baseline_unit"] == SWISS_BASELINE_UNITS["sgd:baseline:10"] + + restored = gridspec_from_dict(payload) + assert restored.tokens == tokens + assert restored.baseline_unit == SWISS_BASELINE_UNITS["sgd:baseline:10"] + assert len(restored.columns) == 8 diff --git a/pkgs/experimental/layout_engine/tests/test_manifest_contract.py b/pkgs/experimental/layout_engine/tests/test_manifest_contract.py new file mode 100644 index 0000000000..2f67211b22 --- /dev/null +++ b/pkgs/experimental/layout_engine/tests/test_manifest_contract.py @@ -0,0 +1,86 @@ +from __future__ import annotations + +from layout_engine import ( + AtomRegistry, + AtomSpec, + LayoutCompiler, + ManifestBuilder, + TileSpec, + Viewport, + validate_manifest, +) +from layout_engine.core.size import Size +from layout_engine.grid.spec import GridSpec, GridTrack, GridTile +from layout_engine.manifest.spec import ChannelManifest, WsRouteManifest +from layout_engine.site.spec import PageSpec, SiteSpec, SlotSpec + + +def test_manifest_with_site_and_channels_validates() -> None: + builder = ManifestBuilder() + site = SiteSpec( + base_path="/app", + pages=( + PageSpec( + id="dashboard", + route="/", + title="Dashboard", + slots=(SlotSpec(name="main", role="layout"),), + ), + ), + ) + + view_model = { + "viewport": {"width": 1280, "height": 720}, + "grid": {"columns": []}, + "tiles": [], + } + + manifest = builder.build( + view_model, + site=site, + active_page="dashboard", + channels=[ + ChannelManifest(id="ui.events", scope="page", topic="page:{page_id}:ui") + ], + ws_routes=[WsRouteManifest(path="/ws/ui", channels=("ui.events",))], + ) + + validate_manifest(manifest) + assert manifest.channels[0].id == "ui.events" + assert manifest.ws_routes[0].channels == ("ui.events",) + + +def test_swarma_catalog_override_propagates_to_manifest() -> None: + # Build registry with an override without depending on layout_engine_atoms availability. + registry = AtomRegistry() + registry.register( + AtomSpec( + role="swarmakit:vue:button", + module="@swarmakit/vue", + export="Button", + version="0.0.22", + defaults={}, + ) + ) + registry.override("swarmakit:vue:button", defaults={"size": "lg"}) + + compiler = LayoutCompiler() + grid_spec = GridSpec(columns=[GridTrack(size=Size(1, "fr"))], row_height=200) + viewport = Viewport(width=800, height=600) + frames_map = compiler.frames( + grid_spec, viewport, [GridTile(tile_id="btn", col=0, row=0)] + ) + tiles = [TileSpec(id="btn", role="swarmakit:vue:button", props={})] + + view_model = compiler.view_model( + grid_spec, + viewport, + frames_map, + tiles, + atoms_registry=registry, + ) + + manifest = ManifestBuilder().build(view_model) + tile = manifest.tiles[0] + assert tile["props"]["size"] == "lg" + assert tile["atom"]["defaults"]["size"] == "lg" diff --git a/pkgs/experimental/layout_engine/tests/test_manifest_site.py b/pkgs/experimental/layout_engine/tests/test_manifest_site.py new file mode 100644 index 0000000000..f30dfd4a76 --- /dev/null +++ b/pkgs/experimental/layout_engine/tests/test_manifest_site.py @@ -0,0 +1,81 @@ +from __future__ import annotations + +from layout_engine.events import clear_channels, get_channel +from layout_engine.manifest.default import ManifestBuilder +from layout_engine.manifest.utils import to_dict +from layout_engine.manifest.spec import ChannelManifest, WsRouteManifest +from layout_engine.site.spec import PageSpec, SiteSpec, SlotSpec + + +def test_manifest_builder_merges_site_metadata() -> None: + builder = ManifestBuilder() + site = SiteSpec( + base_path="/app", + pages=( + PageSpec( + id="home", + route="/", + title="Home", + slots=(SlotSpec(name="main", role="layout"),), + ), + PageSpec( + id="reports", + route="/reports/:id", + title="Reports", + slots=(SlotSpec(name="main", role="layout"),), + ), + ), + ) + + manifest = builder.build( + { + "viewport": {"width": 1280, "height": 720}, + "grid": {"columns": []}, + "tiles": [], + }, + site=site, + active_page="home", + ) + + assert manifest.site is not None + assert manifest.site.active_page == "home" + assert len(manifest.site.pages) == 2 + home = manifest.site.pages[0] + assert home["id"] == "home" + assert home["route"] == "/" + + # Ensure serialization retains site payload + serialized = to_dict(manifest) + assert "site" in serialized + assert serialized["site"]["navigation"]["base_path"] == "/app" + + +def test_manifest_builder_registers_channels_and_routes() -> None: + clear_channels() + builder = ManifestBuilder() + channel = ChannelManifest( + id="ui.events", + scope="page", + topic="page:{page_id}:ui", + ) + route = WsRouteManifest(path="/ws/ui", channels=("ui.events",)) + + manifest = builder.build( + { + "viewport": {"width": 640, "height": 480}, + "grid": {"columns": []}, + "tiles": [], + }, + channels=[channel], + ws_routes=[route], + ) + + assert manifest.channels[0].id == "ui.events" + assert manifest.ws_routes[0].path == "/ws/ui" + serialized = to_dict(manifest) + assert serialized["channels"][0]["topic"] == "page:{page_id}:ui" + assert tuple(serialized["ws_routes"][0]["channels"]) == ("ui.events",) + channel_def = get_channel("ui.events") + assert channel_def is not None + assert channel_def["topic"] == "page:{page_id}:ui" + clear_channels() diff --git a/pkgs/experimental/layout_engine/tests/test_view_model_atoms.py b/pkgs/experimental/layout_engine/tests/test_view_model_atoms.py new file mode 100644 index 0000000000..e391a7b999 --- /dev/null +++ b/pkgs/experimental/layout_engine/tests/test_view_model_atoms.py @@ -0,0 +1,63 @@ +from __future__ import annotations + +from layout_engine.atoms.default import AtomRegistry +from layout_engine.atoms.spec import AtomSpec +from layout_engine.compile import LayoutCompiler +from layout_engine.core.size import Size +from layout_engine.core.viewport import Viewport +from layout_engine.grid.spec import GridSpec, GridTrack, GridTile +from layout_engine.tiles.spec import TileSpec + + +def test_view_model_includes_atom_metadata() -> None: + compiler = LayoutCompiler() + registry = AtomRegistry() + registry.register( + AtomSpec( + role="swarmakit:vue:button", + module="@swarmakit/vue", + export="Button", + version="0.0.22", + defaults={"size": "md"}, + framework="vue", + package="@swarmakit/vue", + family="swarmakit", + registry={"name": "swarmakit", "version": "0.0.22", "framework": "vue"}, + ) + ) + registry.revision = "rev-1" # type: ignore[attr-defined] + + grid_spec = GridSpec( + columns=[GridTrack(size=Size(1, "fr"))], + row_height=200, + gap_x=24, + gap_y=16, + tokens={"columns": "sgd:columns:8"}, + ) + placements = [GridTile(tile_id="btn", col=0, row=0)] + frames = compiler.frames(grid_spec, Viewport(width=800, height=600), placements) + + tiles = [TileSpec(id="btn", role="swarmakit:vue:button", props={"tone": "primary"})] + + vm = compiler.view_model( + grid_spec, + Viewport(width=800, height=600), + frames, + tiles, + atoms_registry=registry, + channels=[{"id": "ui.events", "scope": "page", "topic": "page:{page_id}:ui"}], + ws_routes=[{"path": "/ws/ui", "channels": ("ui.events",)}], + ) + + atom_payload = vm["tiles"][0]["atom"] + assert atom_payload["role"] == "swarmakit:vue:button" + assert atom_payload["framework"] == "vue" + assert atom_payload["registry"]["name"] == "swarmakit" + assert atom_payload["registry"]["version"] == "0.0.22" + assert vm["meta"]["grid_tokens"]["columns"] == "sgd:columns:8" + assert vm["meta"]["atoms"]["revision"] == "rev-1" + assert vm["meta"]["layout"]["gap"]["x"] == 24 + layout_props = vm["tiles"][0]["props"]["layout"] + assert layout_props["gap"]["x"] == 24 + assert layout_props["padding"]["x"] == 12 + assert vm["channels"][0]["id"] == "ui.events" diff --git a/pkgs/experimental/layout_engine_atoms/README.md b/pkgs/experimental/layout_engine_atoms/README.md index fc4e6c5357..ef6a050f55 100644 --- a/pkgs/experimental/layout_engine_atoms/README.md +++ b/pkgs/experimental/layout_engine_atoms/README.md @@ -15,18 +15,32 @@ --- -# Layout Engine Atoms +# Layout Engine Atoms & Vue Runtime -`layout-engine-atoms` packages Swarmauri's canonical UI atom presets for the [`layout-engine`](../layout_engine) runtime. Each preset maps a semantic role to a front-end module, export, version, and default props so downstream applications can bootstrap view-models without hand-curating every mapping. +`layout-engine-atoms` packages Swarmauri's canonical UI atoms **and** ships the +official Vue thin wrapper for [`layout-engine`](../layout_engine). With this +package you can: -> **Python compatibility:** officially supports Python 3.10, 3.11, and 3.12. +- prime manifest builders with curated atom presets, and +- mount a production-ready Vue dashboard that renders multi-page manifests, theme + tokens, and websocket event streams without writing custom front-end plumbing. -## Features +> **Python compatibility**: Python 3.10–3.12 -- **Curated presets** – ships ready-to-use role mappings covering common KPI, chart, data, and layout atoms. -- **Typed contracts** – builds on `layout-engine`'s `AtomSpec` models to validate module, export, and default prop shapes. -- **Composable registries** – helper utilities load presets into `AtomRegistry` instances or dictionaries for manifest pipelines. -- **Configurable overrides** – extend or replace built-ins by layering custom atoms on top of the defaults. +## Highlights + +- **Curated atom registry** – every semantic role maps to an importable module, + default props, and version metadata so manifests can stay declarative. +- **Vue thin wrapper** – packaged `mount_layout_app` helper mounts a drop-in + dashboard with grid layout, shared theme tokens, plugin hooks, and optional + realtime updates straight from FastAPI. +- **Swiss grid defaults** – spacing, typography, and layout tokens follow Swiss + graphic design ratios for consistent output across HTML, PDF, or SVG targets. +- **Realtime ready** – websocket bridge understands `manifest.replace`, + `manifest.patch`, and `manifest.refresh` payloads and can broadcast custom + messages to connected clients. + +--- ## Installation @@ -38,7 +52,13 @@ uv add layout-engine-atoms pip install layout-engine-atoms ``` -## Usage +The published wheel already includes the pre-built Vue assets under +`layout_engine_atoms/runtime/vue/client/dist/`. Only rebuild if you are +maintaining the bundle locally (see [Development](#development)). + +--- + +## Python: Working with Atom Presets ```python from layout_engine_atoms import DEFAULT_ATOMS, build_registry @@ -50,24 +70,131 @@ for role, spec in DEFAULT_ATOMS.items(): # Create a registry that layout-engine can consume registry = build_registry() -# Register an override before passing to layout_engine +# Override a role before passing it to layout_engine registry.override( - "dashboard.hero", + "viz:metric:kpi", module="@layout-app/atoms", defaults={"accent": "violet"}, ) ``` -See [`examples/basic_usage.py`](../layout_engine/examples/basic_usage.py) for how manifests consume registries to merge props with layout metadata. +The registry output can be fed directly into `layout-engine` manifest pipelines +when composing payloads for the runtime. See +[`examples/basic_usage.py`](../layout_engine/examples/basic_usage.py) for a +complete manifest construction flow. + +### Quick manifest builder + +To skip the lower-level layout primitives entirely, use the bundled helpers: + +```python +from layout_engine_atoms.manifest import create_registry, quick_manifest, tile + +registry = create_registry() +manifest = quick_manifest( + [ + tile("hero", "swarmakit:vue:cardbased-list", span="full", props={"cards": [...]}), + tile("summary", "swarmakit:vue:data-summary", span="half", props={"data": [...]}), + tile("activity", "swarmakit:vue:activity-indicators", span="half", props={"type": "success"}), + ], + registry=registry, +) + +manifest_json = manifest.model_dump() +``` + +Tiles auto-place into a responsive grid (`full`, `half`, or explicit spans), the +viewport is inferred, and registry defaults merge into each tile's props. + +If you prefer the authoring DSL, build a `table`/`row`/`col` structure and call +`quick_manifest_from_table(layout, tiles, ...)` for the same result. + +--- + +## Vue Thin Wrapper + +The Vue runtime lives under `layout_engine_atoms.runtime.vue` and ships both the +server-side helper and the browser bundle. + +### One-line FastAPI mount + +```python +from fastapi import FastAPI +from layout_engine_atoms.runtime.vue import mount_layout_app +from my_manifests import build_manifest + +app = FastAPI() + +mount_layout_app( + app, + manifest_builder=build_manifest, + base_path="/dashboard", + title="My Layout Engine Dashboard", +) +``` + +The helper ships the HTML shell, import map, and static bundles. Once mounted, +opening `/dashboard/` loads the packaged Vue runtime which automatically +fetches `/dashboard/manifest.json`. + +--- + +## Examples + +- **Simple manifest** – minimal script that builds a SwarmaKit manifest with + layout-engine (`pkgs/experimental/layout_engine_atoms/examples/simple_demo`). +- **Customer Success Command Center** – demonstrates multi-page manifests and + realtime incident streaming via websocket patches + (`pkgs/experimental/layout_engine_atoms/examples/customer_success`). +- **Hybrid SPA/MPA Demo** – mounts both a single-page and multi-page runtime in + one FastAPI app, showcasing realtime updates + (`pkgs/experimental/layout_engine_atoms/examples/hybrid_demo`). +- **Revenue Ops Command Center** – richer stream routing and manifest patches + (`pkgs/experimental/layout_engine_atoms/examples/revenue_ops`). + +You can run any example directly with `uvicorn`: + +```bash +uv run --directory pkgs/experimental/layout_engine_atoms \ + --package layout-engine-atoms \ + uvicorn layout_engine_atoms.examples.simple_demo.server:app --reload +``` + +This demo mounts the packaged Vue shell and manifest under `/`. Run it with +`uvicorn layout_engine_atoms.examples.simple_demo.server:app --reload` and visit +`http://127.0.0.1:8000/` to view the dashboard. + +```bash +uv run --directory pkgs/experimental/layout_engine_atoms \ + --package layout-engine-atoms \ + uvicorn examples.customer_success.server:app --reload +``` + +--- + +## Documentation + +- [Embedding guide](docs/vue_embedding_guide.md) +- [Runtime README](src/layout_engine_atoms/runtime/vue/README.md) +- [Swiss grid theme tokens](docs/swiss_grid_theme.md) +- [Bundle guide](docs/vue_client_bundle.md) + +--- ## Development ```bash uv sync --all-extras -uv run --directory experimental/layout_engine_atoms --package layout-engine-atoms ruff check . -uv run --directory experimental/layout_engine_atoms --package layout-engine-atoms pytest -q +# Python quality gates +uv run --directory pkgs/experimental/layout_engine_atoms --package layout-engine-atoms ruff check . +uv run --directory pkgs/experimental/layout_engine_atoms --package layout-engine-atoms pytest + +# Rebuild the Vue bundle (requires Node/npm) +./scripts/build_vue_runtime.sh ``` +--- + ## License Apache License 2.0. See [LICENSE](./LICENSE) for full terms. diff --git a/pkgs/experimental/layout_engine_atoms/examples/__init__.py b/pkgs/experimental/layout_engine_atoms/examples/__init__.py new file mode 100644 index 0000000000..0dc26bb26a --- /dev/null +++ b/pkgs/experimental/layout_engine_atoms/examples/__init__.py @@ -0,0 +1 @@ +"""Example applications for layout_engine_atoms.""" diff --git a/pkgs/experimental/layout_engine_atoms/examples/mpa_dashboard/README.md b/pkgs/experimental/layout_engine_atoms/examples/mpa_dashboard/README.md new file mode 100644 index 0000000000..5fa52cda9e --- /dev/null +++ b/pkgs/experimental/layout_engine_atoms/examples/mpa_dashboard/README.md @@ -0,0 +1,38 @@ +# Multi-page Mission Control Demo + +This example packages a styled, multi-page dashboard that showcases how +`layout_engine`, `layout_engine_atoms`, and the Vue runtime collaborate. +Each route returns a curated manifest composed from the SwarmaKit atom +catalogue and rendered through the prebuilt layout engine Vue shell. + +## What you get + +- A FastAPI server that mounts the layout engine Vue runtime assets. +- Three demo manifests (`overview`, `operations`, `revenue`) with rich props, + consistent theming, and per-page metadata. +- Classic MPA navigation: each link loads a route-specific manifest while + keeping the authored shell experience cohesive. + +## Running the demo + +```bash +# from repository root +uv run --directory pkgs/experimental/layout_engine_atoms \ + --package layout-engine-atoms \ + uvicorn layout_engine_atoms.examples.mpa_dashboard.server:app --reload +``` + +Visit for the overview page. The navigation bar +links to `/operations` and `/revenue`, each returning a manifest tailored for +that view. + +## Structure + +| File | Purpose | +| ---- | ------- | +| `manifests.py` | Builds the SwarmaKit atom registry, authoring layouts, and manifest payloads (with site metadata and styling hints). | +| `server.py` | Uses the enhanced `mount_layout_app` helper to serve static assets, apply theming, and configure multi-page navigation without hand-written HTML. | +| `README.md` | You are here. | + +Feel free to remix the page definitions or extend the manifest helpers to plug +into real data sources. diff --git a/pkgs/experimental/layout_engine_atoms/examples/mpa_dashboard/__init__.py b/pkgs/experimental/layout_engine_atoms/examples/mpa_dashboard/__init__.py new file mode 100644 index 0000000000..b787ccc3c7 --- /dev/null +++ b/pkgs/experimental/layout_engine_atoms/examples/mpa_dashboard/__init__.py @@ -0,0 +1,17 @@ +"""Interactive multi-page dashboard demo built on layout_engine_atoms.""" + +from __future__ import annotations + +__all__ = [ + "DEFAULT_PAGE_ID", + "available_pages", + "build_manifest", + "resolve_page_by_route", +] + +from .manifests import ( + DEFAULT_PAGE_ID, + available_pages, + build_manifest, + resolve_page_by_route, +) diff --git a/pkgs/experimental/layout_engine_atoms/examples/mpa_dashboard/manifests.py b/pkgs/experimental/layout_engine_atoms/examples/mpa_dashboard/manifests.py new file mode 100644 index 0000000000..22113e899d --- /dev/null +++ b/pkgs/experimental/layout_engine_atoms/examples/mpa_dashboard/manifests.py @@ -0,0 +1,623 @@ +from __future__ import annotations + +from dataclasses import dataclass +from functools import lru_cache +from typing import Callable + +from layout_engine import Manifest, Viewport, compute_etag +from layout_engine.manifest.utils import from_dict, to_dict +from layout_engine.site.spec import PageSpec, SiteSpec +from layout_engine.structure import block, col, row, table +from layout_engine.core import SizeToken +from layout_engine_atoms.manifest import ( + Tile, + build_manifest_from_table, + create_registry, + tile, +) + + +# --- Demo configuration ------------------------------------------------------------------------- + +ROW_HEIGHT = 220 +VIEWPORT = Viewport(width=1440, height=960) +VERSION = "2025.10" + +PANEL_STYLE = { + "backgroundColor": "rgba(15, 23, 42, 0.82)", + "border": "1px solid rgba(148, 163, 184, 0.22)", + "boxShadow": "0 18px 44px rgba(2, 6, 23, 0.6)", + "borderRadius": "18px", + "padding": "24px", +} + +CARD_STYLE = { + **PANEL_STYLE, + "--card-bg": "rgba(15, 23, 42, 0.82)", + "--card-border": "1px solid rgba(148, 163, 184, 0.3)", + "--card-hover-bg": "rgba(56, 189, 248, 0.16)", + "--card-selected-border-color": "#38bdf8", + "padding": "24px", + "height": "50%", +} + +SUMMARY_STYLE = { + **PANEL_STYLE, + "backgroundColor": "rgba(56, 189, 248, 0.14)", + "border": "1px solid rgba(56, 189, 248, 0.35)", + "--summary-bg": "rgba(15, 118, 110, 0.12)", + "--summary-border-color": "rgba(21, 94, 117, 0.45)", +} + +ACTION_STYLE = { + **PANEL_STYLE, + "background": "radial-gradient(circle at 20% 20%, rgba(56,189,248,0.28), rgba(15,23,42,0.92))", +} + + +@dataclass(frozen=True) +class PageDefinition: + """Declarative description of a demo page.""" + + id: str + title: str + route: str + tagline: str + description: str + builder: Callable[[], tuple] + + +# --- Page builders ------------------------------------------------------------------------------- + + +def _overview_page() -> tuple: + layout = table( + row(col(block("overview_hero"), size=SizeToken.xxl)), + row( + col( + block("overview_actions"), + block("overview_pulses"), + size=SizeToken.m, + ), + col(block("overview_metrics"), size=SizeToken.m), + col( + block("overview_progress"), + size=SizeToken.xl, + ), + ), + row(col(block("overview_timeline"), size=SizeToken.xxl)), + gap_x=24, + gap_y=6, + ) + + tiles: list[Tile] = [ + tile( + "overview_hero", + "swarmakit:vue:cardbased-list", + props={ + "style": CARD_STYLE, + "cards": [ + { + "title": "Autonomous services", + "description": "27 providers are synced; 94% SLA adherence in the last 24h.", + }, + { + "title": "Signals this hour", + "description": "3.1k realtime events processed with 0.2s median latency.", + }, + { + "title": "Experiments live", + "description": "Nine experiments running across ops, growth, and market loops.", + }, + ], + }, + ), + tile( + "overview_actions", + "swarmakit:vue:actionable-list", + props={ + "items": [ + {"label": "Review API catalogue drift", "actionLabel": "Inspect"}, + { + "label": "Enable adaptive throttling", + "actionLabel": "Activate", + "loading": False, + }, + { + "label": "Archive legacy widget set", + "actionLabel": "Archive", + "disabled": True, + }, + ], + "style": ACTION_STYLE, + }, + ), + tile( + "overview_pulses", + "swarmakit:vue:activity-indicators", + props={ + "type": "success", + "message": "All cores synchronized — next ingest checkpoint in 12 minutes.", + "style": { + "backgroundColor": "rgba(56, 189, 248, 0.16)", + "color": "#e0f2fe", + "border": "1px solid rgba(56, 189, 248, 0.45)", + "borderRadius": "18px", + "padding": "20px 24px", + "--success-bg-color": "rgba(56, 189, 248, 0.18)", + "--success-text-color": "#e0f2fe", + }, + }, + ), + tile( + "overview_metrics", + "swarmakit:vue:data-summary", + props={ + "data": [112, 126, 131, 148, 162, 176, 183, 195], + "style": SUMMARY_STYLE, + }, + ), + tile( + "overview_progress", + "swarmakit:vue:progress-bar", + props={ + "progress": 72, + "style": { + "background": "rgba(56, 189, 248, 0.18)", + "borderRadius": "14px", + "padding": "18px 20px", + "boxShadow": "0 18px 44px rgba(2, 6, 23, 0.55)", + "border": "1px solid rgba(56, 189, 248, 0.45)", + "margin": "0 auto", + "maxWidth": "320px", + }, + }, + ), + tile( + "overview_timeline", + "swarmakit:vue:timeline-list", + props={ + "items": [ + { + "id": "1", + "label": "✅ Platform deploy 09:10 UTC", + "completed": True, + }, + { + "id": "2", + "label": "✅ Ops swarm retro 11:30 UTC", + "completed": True, + }, + { + "id": "3", + "label": "• Partner sync-in 14:00 UTC", + "completed": False, + }, + { + "id": "4", + "label": "• Pulse export 18:45 UTC", + "completed": False, + }, + ], + "style": PANEL_STYLE, + }, + ), + ] + + return layout, tiles + + +def _operations_page() -> tuple: + layout = table( + row( + col(block("operations_overview", row_span=2), size=SizeToken.xxl), + col( + block("operations_alerts"), + size=SizeToken.l, + ), + col(block("operations_uptime")), + ), + row( + col(block("operations_queue_depth"), size=SizeToken.s), + col(block("operations_tasks"), size=SizeToken.s), + ), + gap_x=24, + gap_y=24, + ) + + tiles: list[Tile] = [ + tile( + "operations_overview", + "swarmakit:vue:cardbased-list", + props={ + "style": CARD_STYLE, + "cards": [ + { + "title": "Incidents", + "description": "0 active · 4 resolved in the last shift window.", + }, + { + "title": "Escalations", + "description": "3 teams on standby · pager latency 41s median.", + }, + { + "title": "Workflow health", + "description": "Auto-remediation closed 67% of triggered runbooks.", + }, + ], + }, + ), + tile( + "operations_alerts", + "swarmakit:vue:actionable-list", + props={ + "items": [ + {"label": "Rebalance build runners", "actionLabel": "Run"}, + {"label": "Approve router firmware", "actionLabel": "Review"}, + { + "label": "Mute noisy insight feed", + "actionLabel": "Pause", + "loading": True, + }, + ], + "style": ACTION_STYLE, + }, + ), + tile( + "operations_uptime", + "swarmakit:vue:progress-circle", + props={ + "progress": 80, + "status": "active", + "style": { + "padding": "32px", + "background": "rgba(15, 118, 110, 0.18)", + "borderRadius": "20px", + "border": "1px solid rgba(45, 212, 191, 0.45)", + "display": "grid", + "placeItems": "center", + "color": "#ccfbf1", + "gap": "12px", + }, + }, + ), + tile( + "operations_queue_depth", + "swarmakit:vue:data-summary", + props={ + "data": [42, 33, 24, 19, 21, 22, 18, 15], + "style": SUMMARY_STYLE, + }, + ), + tile( + "operations_tasks", + "swarmakit:vue:selectable-list-with-item-details", + props={ + "items": [ + { + "id": "A-104", + "label": "Validate drift reports", + "details": "Ensure new connectors conform to baseline schema.", + }, + { + "id": "A-221", + "label": "Rotate ephemeral keys", + "details": "Applies to fleet staging and XFN sandboxes.", + }, + { + "id": "A-310", + "label": "Sync playbook analytics", + "details": "Share trendline with reliability guild.", + }, + ], + "style": PANEL_STYLE, + }, + ), + ] + + return layout, tiles + + +def _revenue_page() -> tuple: + layout = table( + row( + col(block("revenue_highlights", row_span=2), size=SizeToken.l), + col( + block("revenue_health"), + size=SizeToken.m, + ), + ), + row( + col(block("revenue_forecast"), size=SizeToken.m), + col(block("revenue_actions"), size=SizeToken.m), + col(block("revenue_wins"), size=SizeToken.m), + ), + gap_x=24, + gap_y=24, + ) + + tiles: list[Tile] = [ + tile( + "revenue_highlights", + "swarmakit:vue:cardbased-list", + props={ + "style": CARD_STYLE, + "cards": [ + { + "title": "Quarter bookings", + "description": "$48.2M recognised · tracking +12% above target.", + }, + { + "title": "Expansion velocity", + "description": "Net retention 134% · 27 expansions closed this month.", + }, + { + "title": "Enterprise pilots", + "description": "Five Fortune 100 pilots active · two in procurement review.", + }, + ], + }, + ), + tile( + "revenue_health", + "swarmakit:vue:progress-bar", + props={ + "progress": 84, + "style": { + "background": "rgba(56, 189, 248, 0.18)", + "padding": "18px", + "borderRadius": "16px", + "boxShadow": "0 18px 44px rgba(2, 6, 23, 0.55)", + "border": "1px solid rgba(56, 189, 248, 0.45)", + "margin": "0 auto", + "maxWidth": "320px", + }, + }, + ), + tile( + "revenue_wins", + "swarmakit:vue:timeline-list", + props={ + "items": [ + { + "id": "deal-1", + "label": "Arcadia Labs · ARR $1.2M", + "completed": True, + }, + { + "id": "deal-2", + "label": "Northwind AI · ARR $880k", + "completed": True, + }, + { + "id": "deal-3", + "label": "Globex Cloud · ARR $1.9M", + "completed": False, + }, + { + "id": "deal-4", + "label": "Zeno Systems · ARR $620k", + "completed": False, + }, + ], + "style": PANEL_STYLE, + }, + ), + tile( + "revenue_forecast", + "swarmakit:vue:data-summary", + props={ + "data": [5.1, 6.3, 6.9, 7.8, 8.5, 9.4, 10.2, 10.8], + "style": SUMMARY_STYLE, + }, + ), + tile( + "revenue_actions", + "swarmakit:vue:actionable-list", + props={ + "items": [ + {"label": "Launch lifecycle nurture", "actionLabel": "Send"}, + {"label": "Prep executive QBR deck", "actionLabel": "Draft"}, + { + "label": "Prioritise CS follow-ups", + "actionLabel": "Review", + "loading": True, + }, + ], + "style": ACTION_STYLE, + }, + ), + ] + + return layout, tiles + + +PAGE_SEQUENCE: tuple[PageDefinition, ...] = ( + PageDefinition( + id="overview", + title="Executive pulse", + route="/", + tagline="Strategic signals, live impact", + description="An executive overview combining live telemetry, team focus, and forward-looking signals.", + builder=_overview_page, + ), + PageDefinition( + id="operations", + title="Operations watch", + route="/operations", + tagline="Reliability command surface", + description="Deep dive into reliability workflows, shift posture, and automation throughput.", + builder=_operations_page, + ), + PageDefinition( + id="revenue", + title="Revenue intelligence", + route="/revenue", + tagline="Momentum with leading indicators", + description="Commercial momentum tracking bookings, expansion efficiency, and deal choreography.", + builder=_revenue_page, + ), +) + +PAGES: dict[str, PageDefinition] = {page.id: page for page in PAGE_SEQUENCE} + +SITE_SPEC = SiteSpec( + base_path="/", + pages=tuple( + PageSpec( + id=page.id, + route=page.route, + title=page.title, + meta={"tagline": page.tagline}, + ) + for page in PAGE_SEQUENCE + ), +) + +DEFAULT_PAGE_ID = PAGE_SEQUENCE[0].id + + +# --- Registry helpers ---------------------------------------------------------------------------- + + +def _apply_atom_metadata(manifest: Manifest, registry) -> None: + tiles = manifest.tiles if isinstance(manifest.tiles, list) else list(manifest.tiles) + for entry in tiles: + if not isinstance(entry, dict): + continue + try: + spec = registry.get(entry["role"]) + except Exception: # noqa: BLE001 + continue + atom_data = dict(entry.get("atom") or {}) + atom_data.setdefault("role", spec.role) + atom_data.setdefault("module", spec.module) + atom_data.setdefault("export", spec.export) + atom_data.setdefault("version", spec.version) + if spec.defaults and not atom_data.get("defaults"): + atom_data["defaults"] = dict(spec.defaults) + if getattr(spec, "family", None) and not atom_data.get("family"): + atom_data["family"] = spec.family + if getattr(spec, "framework", None) and not atom_data.get("framework"): + atom_data["framework"] = spec.framework + if getattr(spec, "package", None) and not atom_data.get("package"): + atom_data["package"] = spec.package + tokens = getattr(spec, "tokens", None) + if tokens and not atom_data.get("tokens"): + atom_data["tokens"] = dict(tokens) + registry_meta = getattr(spec, "registry", None) + if registry_meta and not atom_data.get("registry"): + atom_data["registry"] = dict(registry_meta) + entry["atom"] = atom_data + + +def _ensure_viewport_bounds(payload: dict[str, object]) -> None: + viewport = payload.setdefault("viewport", {}) + if not isinstance(viewport, dict): + return + tiles = payload.get("tiles") + if not isinstance(tiles, list): + return + max_bottom = 0 + for entry in tiles: + if not isinstance(entry, dict): + continue + frame = entry.get("frame") + if not isinstance(frame, dict): + continue + bottom = int(frame.get("y", 0)) + int(frame.get("h", 0)) + if bottom > max_bottom: + max_bottom = bottom + current = int(viewport.get("height", 0)) + if max_bottom > current: + viewport["height"] = max_bottom + + +@lru_cache(maxsize=1) +def get_registry(): + registry = create_registry(catalog="vue") + return registry + + +# --- Manifest plumbing --------------------------------------------------------------------------- + + +def _site_payload(active_page: str) -> dict[str, object]: + return { + "pages": [ + { + "id": spec.id, + "route": spec.route, + "title": spec.title, + "slots": [slot.model_dump() for slot in spec.slots], + "meta": dict(spec.meta), + } + for spec in SITE_SPEC.pages + ], + "active_page": active_page, + "navigation": {"base_path": SITE_SPEC.base_path}, + } + + +def build_manifest(page_id: str) -> Manifest: + """Return a manifest for the requested page id with site metadata attached.""" + + try: + page = PAGES[page_id] + except KeyError as exc: + raise KeyError(f"Unknown demo page '{page_id}'") from exc + + layout, tiles = page.builder() + + manifest, _ = build_manifest_from_table( + layout, + tiles, + registry=get_registry(), + row_height=ROW_HEIGHT, + viewport=VIEWPORT, + version=VERSION, + ) + + manifest_dict = to_dict(manifest) + manifest_dict["site"] = _site_payload(page.id) + + meta = dict(manifest_dict.get("meta", {})) + meta["page"] = { + "id": page.id, + "title": page.title, + "description": page.description, + "tagline": page.tagline, + } + meta.setdefault("theme", {}).update( + { + "accent": "#38bdf8", + "panel": "rgba(15, 23, 42, 0.85)", + "surface": "#020617", + } + ) + manifest_dict["meta"] = meta + _ensure_viewport_bounds(manifest_dict) + manifest_dict["etag"] = compute_etag(manifest_dict) + + hydrated = from_dict(manifest_dict) + _apply_atom_metadata(hydrated, get_registry()) + return hydrated + + +# --- Page utilities ------------------------------------------------------------------------------- + + +def available_pages() -> tuple[PageDefinition, ...]: + """Return the ordered tuple of available demo pages.""" + + return PAGE_SEQUENCE + + +def resolve_page_by_route(route: str) -> str | None: + """Return the page id for a route if known.""" + + normalized = route or "/" + for page in PAGE_SEQUENCE: + if page.route == normalized: + return page.id + return None diff --git a/pkgs/experimental/layout_engine_atoms/examples/mpa_dashboard/server.py b/pkgs/experimental/layout_engine_atoms/examples/mpa_dashboard/server.py new file mode 100644 index 0000000000..aeef0dbebf --- /dev/null +++ b/pkgs/experimental/layout_engine_atoms/examples/mpa_dashboard/server.py @@ -0,0 +1,291 @@ +from __future__ import annotations + +import asyncio +import random +from datetime import datetime + +from fastapi import FastAPI, HTTPException, Request + +from layout_engine_atoms.runtime.vue.app import ( + LayoutOptions, + RouterOptions, + UIHooks, + mount_layout_app, +) +from layout_engine_atoms.runtime.vue.realtime import ( + RealtimeBinding, + RealtimeChannel, + RealtimeOptions, + WebsocketMuxHub, +) + +from .manifests import DEFAULT_PAGE_ID, build_manifest + +APP_TITLE = "Swarmauri Mission Control" + +app = FastAPI(title="Layout Engine MPA Demo", docs_url=None) + +MISSION_EVENTS_CHANNEL = RealtimeChannel( + id="mission.events", + scope="site", + topic="mission-control:events", + description="Broadcasts live mission control pulses rendered in the overview banner.", + meta={"demo": True}, +) + + +def _manifest_builder(request: Request): + page_id = request.query_params.get("page") + resolved = page_id or DEFAULT_PAGE_ID + try: + return build_manifest(resolved) + except KeyError as exc: + raise HTTPException(status_code=404, detail=str(exc)) from exc + + +async def mission_pulse_publisher(hub: WebsocketMuxHub) -> None: + """Emit rotating mission control pulses to demonstrate realtime updates.""" + + messages = [ + ("success", "Telemetry lock acquired — mission feed is nominal."), + ("warning", "Playbook 211 stalled for 3m — monitoring escalation lane."), + ("info", "New insight cards generated from overnight batch windows."), + ("success", "Realtime mux synced: 3.2k events/min with 120ms p99."), + ] + idx = 0 + while True: + level, message = messages[idx % len(messages)] + idx += 1 + payload = { + "level": level, + "message": message, + "timestamp": datetime.utcnow().isoformat() + "Z", + } + await hub.broadcast(MISSION_EVENTS_CHANNEL.id, payload) + await asyncio.sleep(random.uniform(4.0, 7.0)) + + +EXTRA_STYLES = [ + """ + +""", +] + +HEADER_SLOT = """ +
+ Mission Control +

{{ shellTitle }}

+

{{ shellSubtitle }}

+
+
+ + +
+""" + +layout_options = LayoutOptions( + title=APP_TITLE, + accent_palette={ + "accent": "rgba(56, 189, 248, 0.75)", + "panel": "rgba(15, 23, 42, 0.92)", + "surface": "rgba(2, 6, 23, 1)", + "text": "#e2e8f0", + }, + extra_styles=EXTRA_STYLES, + router=RouterOptions( + manifest_url="./manifest.json", + page_param="page", + default_page_id=DEFAULT_PAGE_ID, + history="history", + hydrate_site_meta=True, + ), +) + +ui_hooks = UIHooks( + header_slot=HEADER_SLOT, + content_slot=""" +
+
+

{{ manifest.meta?.page?.title ?? site.activePage.value?.title ?? 'Overview' }}

+

+ {{ manifest.meta?.page?.description ?? + manifest.meta?.page?.tagline ?? + 'Curated view powered by SwarmaKit atoms and the layout engine.' }} +

+
+
+ + Viewport {{ manifest.viewport.width }} × {{ manifest.viewport.height }} + + + Tiles {{ manifest.tiles.length }} + +
+
+ + + + """, +) + +mount_layout_app( + app, + _manifest_builder, + base_path="/", + layout_options=layout_options, + ui_hooks=ui_hooks, + realtime=RealtimeOptions( + path="/ws/events", + channels=(MISSION_EVENTS_CHANNEL,), + publishers=(mission_pulse_publisher,), + bindings=( + RealtimeBinding( + channel=MISSION_EVENTS_CHANNEL.id, + tile_id="overview_pulses", + fields={ + "message": "message", + "type": "level", + "timestamp": "timestamp", + }, + ), + ), + ), +) diff --git a/pkgs/experimental/layout_engine_atoms/examples/simple_demo/__init__.py b/pkgs/experimental/layout_engine_atoms/examples/simple_demo/__init__.py new file mode 100644 index 0000000000..adbad10e70 --- /dev/null +++ b/pkgs/experimental/layout_engine_atoms/examples/simple_demo/__init__.py @@ -0,0 +1 @@ +"""Simple manifest + server demo for layout_engine_atoms.""" diff --git a/pkgs/experimental/layout_engine_atoms/examples/simple_demo/manifest.py b/pkgs/experimental/layout_engine_atoms/examples/simple_demo/manifest.py new file mode 100644 index 0000000000..fcedf2a0be --- /dev/null +++ b/pkgs/experimental/layout_engine_atoms/examples/simple_demo/manifest.py @@ -0,0 +1,145 @@ +"""Minimal example showing how to use layout_engine_atoms manifest helpers.""" + +from __future__ import annotations + +import json + +from layout_engine.structure import block, col, row, table + +from layout_engine_atoms.manifest import ( + create_registry, + quick_manifest_from_table, + tile, +) + + +def build_manifest_dict() -> dict: + """Return a manifest dictionary ready to be served as JSON.""" + + registry = create_registry(catalog="vue") + + layout = table( + row(col(block("hero")), col(block("progress")), height_rows=2), + row(col(block("summary")), col(block("activity"))), + row(col(block("status")), col(block("actions"))), + ) + + tiles = [ + tile( + "hero", + "swarmakit:vue:cardbased-list", + props={ + "style": { + "color": "#e2e8f0", + "--card-bg": "rgba(15, 23, 42, 0.85)", + "--card-border": "1px solid rgba(148, 163, 184, 0.25)", + "--card-hover-bg": "rgba(56, 189, 248, 0.15)", + "--card-selected-border-color": "#38bdf8", + }, + "cards": [ + { + "title": "Swiss Grid Defaults", + "description": "Tokens keep columns, gaps, and baselines aligned across runtimes.", + }, + { + "title": "SwarmaKit Registry", + "description": "Atoms auto-import with merged defaults for consistent UX payloads.", + }, + { + "title": "Realtime Ready", + "description": "Channels and websocket routes unlock live dashboards out of the box.", + }, + ], + }, + ), + tile( + "summary", + "swarmakit:vue:data-summary", + span="half", + props={ + "style": { + "backgroundColor": "rgba(56, 189, 248, 0.12)", + "color": "#e0f2fe", + "--summary-bg": "rgba(56, 189, 248, 0.12)", + "--summary-border-color": "rgba(148, 163, 184, 0.45)", + "boxShadow": "0 0 0 1px rgba(56, 189, 248, 0.25)", + }, + "data": [120, 135, 142, 138, 149, 162, 158, 171], + }, + ), + tile( + "activity", + "swarmakit:vue:activity-indicators", + span="half", + props={ + "type": "success", + "message": "All runtimes healthy · last sync 2m ago", + "style": { + "backgroundColor": "rgba(56, 189, 248, 0.18)", + "color": "#e0f2fe", + "--success-bg-color": "rgba(56, 189, 248, 0.18)", + "--success-text-color": "#e0f2fe", + "--padding": "18px", + }, + }, + ), + tile( + "progress", + "swarmakit:vue:progress-bar", + props={ + "progress": 72, + }, + ), + tile( + "status", + "swarmakit:vue:status-dots", + span="half", + props={ + "status": "online", + "style": { + "fontSize": "1.1rem", + "gap": "12px", + }, + }, + ), + tile( + "actions", + "swarmakit:vue:actionable-list", + span="half", + props={ + "items": [ + {"label": "Review staging publish", "actionLabel": "Open"}, + { + "label": "Sync atom catalog updates", + "actionLabel": "Run", + "loading": True, + }, + { + "label": "Archive legacy layout", + "actionLabel": "Archive", + "disabled": True, + }, + ], + }, + ), + ] + + manifest = quick_manifest_from_table( + layout, + tiles, + registry=registry, + row_height=220, + channels=[{"id": "ui.events", "scope": "page", "topic": "page:{page_id}:ui"}], + ws_routes=[{"path": "/ws/ui", "channels": ["ui.events"]}], + ) + return manifest.model_dump() + + +def build_manifest_json(indent: int | None = 2) -> str: + """Return the manifest as a JSON string.""" + + return json.dumps(build_manifest_dict(), indent=indent) + + +if __name__ == "__main__": + print(build_manifest_json()) diff --git a/pkgs/experimental/layout_engine_atoms/examples/simple_demo/server.py b/pkgs/experimental/layout_engine_atoms/examples/simple_demo/server.py new file mode 100644 index 0000000000..7e2c5652c8 --- /dev/null +++ b/pkgs/experimental/layout_engine_atoms/examples/simple_demo/server.py @@ -0,0 +1,24 @@ +"""FastAPI demo using the packaged layout engine Vue runtime.""" + +from __future__ import annotations + +from fastapi import FastAPI, Request + +from layout_engine_atoms.runtime.vue import mount_layout_app +from .manifest import build_manifest_dict + +app = FastAPI(title="Layout Engine Atoms Demo") + +_MANIFEST_CACHE = build_manifest_dict() + + +def _manifest_builder(_: Request): + return _MANIFEST_CACHE + + +mount_layout_app( + app, + manifest_builder=_manifest_builder, + base_path="/", + title="Layout Engine × SwarmaKit", +) diff --git a/pkgs/experimental/layout_engine_atoms/pyproject.toml b/pkgs/experimental/layout_engine_atoms/pyproject.toml index 9b07eb19fc..65475d8022 100644 --- a/pkgs/experimental/layout_engine_atoms/pyproject.toml +++ b/pkgs/experimental/layout_engine_atoms/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "layout-engine-atoms" -version = "0.1.0" +version = "0.1.0.dev1" description = "Curated atom presets and registries for layout-engine." authors = [ { name = "Jacob Stewart", email = "jacob@swarmauri.com" }, @@ -20,7 +20,9 @@ classifiers = [ "Topic :: Software Development :: Libraries", ] dependencies = [ - "layout-engine>=0.1.1", + "layout-engine", + "fastapi", + "uvicorn[standard]", ] keywords = [ "layout", @@ -31,9 +33,21 @@ keywords = [ "swarmauri", ] +[tool.setuptools.package-data] +layout_engine_atoms = [ + "runtime/vue/assets/layout-engine-vue/*.js", + "runtime/vue/assets/swarma-vue/*.js", + "runtime/vue/assets/swarma-vue/*.css", + "runtime/vue/templates/*.html", +] + +[tool.setuptools.packages.find] +where = ["src"] + [dependency-groups] dev = [ "pytest>=8.2", + "pytest-asyncio>=0.23", "ruff>=0.6", ] diff --git a/pkgs/experimental/layout_engine_atoms/src/layout_engine_atoms/__init__.py b/pkgs/experimental/layout_engine_atoms/src/layout_engine_atoms/__init__.py index 51f2bf9ac3..224a036116 100644 --- a/pkgs/experimental/layout_engine_atoms/src/layout_engine_atoms/__init__.py +++ b/pkgs/experimental/layout_engine_atoms/src/layout_engine_atoms/__init__.py @@ -1,17 +1,60 @@ """Swarmauri atom presets for layout-engine.""" +# Manifest helpers +from .manifest import ( + Tile as ManifestTile, + build_manifest_from_tiles, + build_manifest_from_table, + create_registry as create_manifest_registry, + quick_manifest, + quick_manifest_from_table, + tile as manifest_tile, +) + from .spec import AtomPreset -from .default import AtomPresetCatalog -from .shortcuts import build_registry, load_catalog, presets_from_specs -from .catalog import DEFAULT_ATOMS, DEFAULT_PRESETS, build_default_registry +from .default import IAtomCatalog, AtomPresetCatalog +from .shortcuts import ( + build_registry as build_registry_from_presets, + load_catalog as load_catalog_from_data, + presets_from_specs, +) +from .catalog import ( + DEFAULT_ATOMS, + DEFAULT_PRESETS, + PRESET_VERSION, + SUPPORTED_CATALOGS, + build_catalog, + build_default_registry, + build_registry, + get_default_atoms, + get_default_presets, + get_preset_version, + load_catalog, +) __all__ = [ "AtomPreset", + "IAtomCatalog", "AtomPresetCatalog", - "build_registry", - "load_catalog", - "presets_from_specs", "DEFAULT_ATOMS", "DEFAULT_PRESETS", + "PRESET_VERSION", + "SUPPORTED_CATALOGS", + "build_catalog", "build_default_registry", + "build_registry", + "get_default_atoms", + "get_default_presets", + "get_preset_version", + "load_catalog", + "build_registry_from_presets", + "load_catalog_from_data", + "presets_from_specs", + "ManifestTile", + "manifest_tile", + "create_manifest_registry", + "quick_manifest", + "build_manifest_from_tiles", + "quick_manifest_from_table", + "build_manifest_from_table", ] diff --git a/pkgs/experimental/layout_engine_atoms/src/layout_engine_atoms/base.py b/pkgs/experimental/layout_engine_atoms/src/layout_engine_atoms/base.py deleted file mode 100644 index 87fd88c6f1..0000000000 --- a/pkgs/experimental/layout_engine_atoms/src/layout_engine_atoms/base.py +++ /dev/null @@ -1,29 +0,0 @@ -from __future__ import annotations - -from abc import ABC, abstractmethod -from typing import Iterable - -from layout_engine import AtomSpec, AtomRegistry - -from .spec import AtomPreset - - -class IAtomPresetCatalog(ABC): - """Abstract interface for loading atom presets.""" - - @abstractmethod - def presets(self) -> Iterable[AtomPreset]: - raise NotImplementedError - - def as_specs(self) -> dict[str, AtomSpec]: - """Materialize presets as a ``role -> AtomSpec`` mapping.""" - return {preset.role: preset.to_spec() for preset in self.presets()} - - def build_registry(self) -> AtomRegistry: - """Create an :class:`AtomRegistry` primed with the catalog presets.""" - registry = AtomRegistry() - registry.register_many(self.presets_to_specs()) - return registry - - def presets_to_specs(self) -> Iterable[AtomSpec]: - return (preset.to_spec() for preset in self.presets()) diff --git a/pkgs/experimental/layout_engine_atoms/src/layout_engine_atoms/catalog.py b/pkgs/experimental/layout_engine_atoms/src/layout_engine_atoms/catalog.py deleted file mode 100644 index 61af630281..0000000000 --- a/pkgs/experimental/layout_engine_atoms/src/layout_engine_atoms/catalog.py +++ /dev/null @@ -1,123 +0,0 @@ -from __future__ import annotations - -from layout_engine import AtomRegistry, AtomSpec - -from .spec import AtomPreset -from .shortcuts import build_registry - - -DEFAULT_PRESETS: dict[str, AtomPreset] = { - "ui:text:body": AtomPreset( - role="ui:text:body", - module="@swarmauri/atoms/Typography", - export="BodyText", - defaults={"variant": "body", "weight": "regular"}, - family="typography", - description="Paragraph copy for long-form content blocks.", - ), - "ui:text:caption": AtomPreset( - role="ui:text:caption", - module="@swarmauri/atoms/Typography", - export="CaptionText", - defaults={"variant": "caption", "weight": "medium"}, - family="typography", - description="Caption text used for footnotes or metadata labels.", - ), - "ui:button:primary": AtomPreset( - role="ui:button:primary", - module="@swarmauri/atoms/Button", - export="PrimaryButton", - defaults={"kind": "primary", "size": "md"}, - family="actions", - description="Primary call-to-action button with accent styling.", - ), - "ui:button:secondary": AtomPreset( - role="ui:button:secondary", - module="@swarmauri/atoms/Button", - export="SecondaryButton", - defaults={"kind": "secondary", "size": "md"}, - family="actions", - description="Secondary button used for neutral or cancel actions.", - ), - "ui:badge:status": AtomPreset( - role="ui:badge:status", - module="@swarmauri/atoms/Badge", - export="StatusBadge", - defaults={"variant": "info"}, - family="status", - description="Compact status badge for metadata and list items.", - ), - "viz:timeseries:line": AtomPreset( - role="viz:timeseries:line", - module="@swarmauri/atoms/charts/Timeseries", - export="LineChart", - defaults={"legend": True, "curve": "smooth"}, - family="visualization", - description="Responsive line chart for time-series data.", - ), - "viz:bar:horizontal": AtomPreset( - role="viz:bar:horizontal", - module="@swarmauri/atoms/charts/Bar", - export="HorizontalBar", - defaults={"legend": False, "stacked": False}, - family="visualization", - description="Horizontal bar chart for categorical comparisons.", - ), - "viz:metric:kpi": AtomPreset( - role="viz:metric:kpi", - module="@swarmauri/atoms/Metrics", - export="KpiCard", - defaults={"format": "compact", "sparkline": False}, - family="metrics", - description="Single KPI tile showing value, delta, and trend.", - ), - "viz:table:basic": AtomPreset( - role="viz:table:basic", - module="@swarmauri/atoms/Table", - export="DataTable", - defaults={"striped": True, "dense": False}, - family="data", - description="Paginated data table with sortable headers.", - ), - "layout:card": AtomPreset( - role="layout:card", - module="@swarmauri/atoms/Layout", - export="SurfaceCard", - defaults={"padding": "lg", "elevation": 2}, - family="layout", - description="Surface container used to group related atoms.", - ), - "layout:grid:two-up": AtomPreset( - role="layout:grid:two-up", - module="@swarmauri/atoms/Layout", - export="TwoUpGrid", - defaults={"gap": "lg"}, - family="layout", - description="Two-column grid wrapper for responsive sections.", - ), - "input:select": AtomPreset( - role="input:select", - module="@swarmauri/atoms/Inputs", - export="SelectField", - defaults={"filterable": True, "clearable": True}, - family="inputs", - description="Dropdown input with optional search and clear actions.", - ), - "input:date-range": AtomPreset( - role="input:date-range", - module="@swarmauri/atoms/Inputs", - export="DateRangePicker", - defaults={"presets": ["7d", "30d", "90d"]}, - family="inputs", - description="Date range picker with quick preset shortcuts.", - ), -} - -DEFAULT_ATOMS: dict[str, AtomSpec] = { - role: preset.to_spec() for role, preset in DEFAULT_PRESETS.items() -} - - -def build_default_registry() -> AtomRegistry: - """Return an :class:`AtomRegistry` populated with the default presets.""" - return build_registry(DEFAULT_PRESETS.values()) diff --git a/pkgs/experimental/layout_engine_atoms/src/layout_engine_atoms/catalog/__init__.py b/pkgs/experimental/layout_engine_atoms/src/layout_engine_atoms/catalog/__init__.py new file mode 100644 index 0000000000..7bab9daabe --- /dev/null +++ b/pkgs/experimental/layout_engine_atoms/src/layout_engine_atoms/catalog/__init__.py @@ -0,0 +1,97 @@ +from __future__ import annotations + +import importlib +from types import ModuleType +from typing import Iterable, Mapping + +from layout_engine import AtomRegistry, AtomSpec + +from ..default import AtomPresetCatalog +from ..spec import AtomPreset +from ..shortcuts import ( + build_registry as build_registry_from_data, + load_catalog as load_catalog_from_data, +) + +SUPPORTED_CATALOGS = ("vue", "svelte") + +_CATALOG_MODULES = { + "vue": "swarma_vue", + "svelte": "swarma_svelte", +} + + +def _import_catalog(name: str) -> ModuleType: + if name not in _CATALOG_MODULES: + raise KeyError( + f"Unknown atom catalog '{name}' (supported: {SUPPORTED_CATALOGS})" + ) + module_name = _CATALOG_MODULES[name] + return importlib.import_module(f"{__name__}.{module_name}") + + +def get_default_presets(name: str = "vue") -> Mapping[str, AtomPreset]: + module = _import_catalog(name) + return module.DEFAULT_PRESETS + + +def get_default_atoms(name: str = "vue") -> Mapping[str, AtomSpec]: + module = _import_catalog(name) + return module.DEFAULT_ATOMS + + +def get_preset_version(name: str = "vue") -> str: + module = _import_catalog(name) + return module.PRESET_VERSION + + +def load_catalog(name: str = "vue") -> AtomPresetCatalog: + presets = get_default_presets(name) + return load_catalog_from_data(presets) + + +def build_registry( + name: str = "vue", + *, + extra_presets: Iterable[AtomPreset] | Mapping[str, AtomPreset] | None = None, + overrides: Mapping[str, Mapping[str, object]] | None = None, +) -> AtomRegistry: + presets = dict(get_default_presets(name)) + if extra_presets: + if isinstance(extra_presets, Mapping): + presets.update(extra_presets) + else: + presets.update({preset.role: preset for preset in extra_presets}) + if overrides: + for role, patch in overrides.items(): + if role not in presets: + continue + presets[role] = presets[role].model_copy(update=patch) + return build_registry_from_data(presets) + + +def build_catalog(name: str = "vue") -> AtomPresetCatalog: + presets = get_default_presets(name) + return AtomPresetCatalog(presets) + + +_vue_module = _import_catalog("vue") + +DEFAULT_PRESETS = _vue_module.DEFAULT_PRESETS +DEFAULT_ATOMS = _vue_module.DEFAULT_ATOMS +PRESET_VERSION = _vue_module.PRESET_VERSION +build_default_registry = _vue_module.build_default_registry + +__all__ = [ + "SUPPORTED_CATALOGS", + "DEFAULT_PRESETS", + "DEFAULT_ATOMS", + "PRESET_VERSION", + "build_default_registry", + "build_catalog", + "build_registry", + "get_default_presets", + "get_default_atoms", + "get_preset_version", + "load_catalog", +] diff --git a/pkgs/experimental/layout_engine_atoms/src/layout_engine_atoms/catalog/swarma_svelte.py b/pkgs/experimental/layout_engine_atoms/src/layout_engine_atoms/catalog/swarma_svelte.py new file mode 100644 index 0000000000..3f6cdf03df --- /dev/null +++ b/pkgs/experimental/layout_engine_atoms/src/layout_engine_atoms/catalog/swarma_svelte.py @@ -0,0 +1,179 @@ +from __future__ import annotations + +import re +from typing import Iterable, Mapping + +from layout_engine import AtomRegistry, AtomSpec + +from ..spec import AtomPreset +from ..swarma import AtomProps, SwarmaAtomCatalog + +FRAMEWORK = "svelte" +SWARMAKIT_MODULE = "@swarmakit/svelte" +SWARMAKIT_VERSION = "0.0.22" +PRESET_VERSION = SWARMAKIT_VERSION + +_COMPONENT_EXPORTS: tuple[str, ...] = ( + "CheckList", + "Accordion", + "ActionableList", + "ActivityIndicators", + "AudioPlayer", + "AudioPlayerAdvanced", + "AudioWaveformDisplay", + "Badge", + "BadgeWithCounts", + "BatteryLevelIndicator", + "Button", + "Captcha", + "CardbasedList", + "Carousel", + "Checkbox", + "CollapsibleMenuList", + "ColorPicker", + "ContextualList", + "CountdownTimer", + "DataGrid", + "DateAndTimePicker", + "DatePicker", + "ThreeSixtyDegreeImageViewer", + "DragAndDropFileArea", + "EmbeddedMediaIframe", + "ExpandableList", + "FavoritesList", + "FileInputWithPreview", + "FileUpload", + "FilterableList", + "GroupedList", + "IconButton", + "ImageSlider", + "InteractivePollResults", + "LoadingBarsWithSteps", + "LoadingSpinner", + "LoadmorebuttoninList", + "MultiselectList", + "NotificationBellIcon", + "NumberedList", + "NumberInputWithIncrement", + "Pagination", + "PasswordConfirmationField", + "PinnedList", + "ProgressBar", + "ProgressCircle", + "RadioButton", + "RangeSlider", + "RatingStars", + "ScrollableList", + "SearchBar", + "SearchInputWithFilterOptions", + "SelectableListWithItemDetails", + "SignalStrengthIndicator", + "Slider", + "SortableList", + "SortableTable", + "StatusDots", + "Stepper", + "SystemAlertGlobalNotificationBar", + "Tabs", + "TaskCompletionCheckList", + "Textarea", + "TimelineList", + "Toast", + "ToggleSwitch", + "TreeviewList", + "Upload", + "ValidationMessages", + "VirtualizedList", + "VisualCueForAccessibilityFocusIndicator", +) + + +def _slugify(name: str) -> str: + text = re.sub(r"([a-z0-9])([A-Z])", r"\1-\2", name) + text = re.sub(r"([A-Z])([A-Z][a-z])", r"\1-\2", text) + return text.replace("_", "-").lower() + + +def _make_preset(name: str) -> AtomPreset: + role = f"swarmakit:svelte:{_slugify(name)}" + return AtomPreset( + role=role, + module=SWARMAKIT_MODULE, + export=name, + version=SWARMAKIT_VERSION, + defaults={}, + framework=FRAMEWORK, + package=SWARMAKIT_MODULE, + family="swarmakit", + registry={ + "name": "swarmakit", + "framework": FRAMEWORK, + "package": SWARMAKIT_MODULE, + "version": SWARMAKIT_VERSION, + }, + ) + + +_DEFAULT_PRESETS = tuple(_make_preset(name) for name in _COMPONENT_EXPORTS) + +DEFAULT_PRESETS: dict[str, AtomPreset] = { + preset.role: preset for preset in _DEFAULT_PRESETS +} + +DEFAULT_ATOMS: dict[str, AtomSpec] = { + role: preset.to_spec() for role, preset in DEFAULT_PRESETS.items() +} + +ATOM_TABLE = [ + { + "role": preset.role, + "framework": FRAMEWORK, + "module": preset.module, + "export": preset.export, + "version": preset.version, + "defaults": dict(preset.defaults), + } + for preset in DEFAULT_PRESETS.values() +] + + +def build_registry( + *, + extra_presets: Iterable[AtomPreset | AtomSpec] + | Mapping[str, AtomPreset | AtomSpec] + | None = None, + overrides: Mapping[str, Mapping[str, object]] | None = None, +) -> AtomRegistry: + """Create an AtomRegistry populated with SwarmaKit Svelte presets.""" + + catalog = SwarmaAtomCatalog(DEFAULT_PRESETS, props_schema=AtomProps) + + if extra_presets: + catalog = catalog.with_extra_presets(extra_presets) + if overrides: + for role, patch in overrides.items(): + try: + catalog = catalog.with_overrides(role, **patch) + except KeyError: + continue + + return catalog.build_registry() + + +def build_default_registry() -> AtomRegistry: + """Return an AtomRegistry populated with the default SwarmaKit Svelte presets.""" + + return build_registry() + + +__all__ = [ + "FRAMEWORK", + "SWARMAKIT_MODULE", + "SWARMAKIT_VERSION", + "PRESET_VERSION", + "DEFAULT_PRESETS", + "DEFAULT_ATOMS", + "ATOM_TABLE", + "build_registry", + "build_default_registry", +] diff --git a/pkgs/experimental/layout_engine_atoms/src/layout_engine_atoms/catalog/swarma_vue.py b/pkgs/experimental/layout_engine_atoms/src/layout_engine_atoms/catalog/swarma_vue.py new file mode 100644 index 0000000000..a9f4d6a50a --- /dev/null +++ b/pkgs/experimental/layout_engine_atoms/src/layout_engine_atoms/catalog/swarma_vue.py @@ -0,0 +1,265 @@ +from __future__ import annotations + +import re +from typing import Iterable, Mapping + +from layout_engine import AtomRegistry, AtomSpec + +from ..spec import AtomPreset +from ..swarma import AtomProps, SwarmaAtomCatalog + +FRAMEWORK = "vue" +SWARMAKIT_MODULE = "@swarmakit/vue" +SWARMAKIT_VERSION = "0.0.22" +PRESET_VERSION = SWARMAKIT_VERSION + +_COMPONENT_EXPORTS: tuple[str, ...] = ( + "ThreeSixtyDegreeImageViewer", + "Accordion", + "ActionableList", + "ActivityIndicators", + "AdminViewScheduler", + "AudioPlayer", + "AudioPlayerAdvanced", + "AudioWaveformDisplay", + "Avatar", + "Badge", + "BadgeWithCounts", + "BatteryLevelIndicator", + "BetSlider", + "BottomNavigationBar", + "BreadcrumbWithDropdowns", + "Breadcrumbs", + "BrushTool", + "Button", + "CalendarView", + "CallButton", + "Canvas", + "Captcha", + "CardActions", + "CardBadge", + "CardBody", + "CardFooter", + "CardHeader", + "CardImage", + "CardbasedList", + "Carousel", + "ChatBubble", + "CheckList", + "Checkbox", + "Chips", + "CollapsibleMenuList", + "ColorPicker", + "ColumnVisibilityToggle", + "CommandPalette", + "CommunityCards", + "ContextualList", + "ContextualNavigation", + "CountdownTimer", + "DarkModeToggle", + "DataExportButton", + "DataFilterPanel", + "DataGrid", + "DataImportDialog", + "DataSummary", + "DataTable", + "DateAndTimePicker", + "DatePicker", + "DealerButton", + "DeckOfCards", + "DiscardPile", + "DragAndDropScheduler", + "DropdownMenu", + "EditableDataTable", + "EmbeddedMediaIframe", + "EmojiReactionPoll", + "EraserTool", + "EventDetailsDialog", + "EventFilterBar", + "EventReminderSystem", + "ExpandableList", + "FavoritesList", + "FieldEditableDataTable", + "FileInputWithPreview", + "FileUpload", + "FillTool", + "FilterableList", + "FlipCard", + "FloatingActionButton", + "FoldButton", + "GroupedList", + "HandOfCards", + "IconButton", + "ImageChoicePoll", + "ImageSlider", + "InteractiveMediaMap", + "InteractivePollResults", + "LayerPanel", + "LiveResultsPoll", + "LiveStreamPlayer", + "LoadMoreButtonInList", + "LoadingBarsWithSteps", + "LoadingSpinner", + "MediaGallery", + "MultipleChoicePoll", + "MultiselectList", + "Notification", + "NotificationBellIcon", + "NumberInputWithIncrement", + "NumberedList", + "OpenEndedPoll", + "Pagination", + "PaginationControl", + "PasswordConfirmationField", + "PinnedList", + "PlayingCard", + "PodcastPlayer", + "PokerChips", + "PokerHand", + "PokerTable", + "PokerTimer", + "Pot", + "ProgressBar", + "ProgressCircle", + "PublicViewCalendar", + "RadioButton", + "RaiseButton", + "RangeSlider", + "RankingPoll", + "RatingStars", + "RecurringEventScheduler", + "RichTextEditor", + "RulerAndGuides", + "ScheduleCRUDPanel", + "ScrollableList", + "SearchBar", + "SearchBarWithSuggestions", + "SearchInputWithFilterOptions", + "SearchWithAutocomplete", + "SelectableListWithItemDetails", + "ShapeLibrary", + "ShapeTool", + "SignalStrengthIndicator", + "SingleChoicePoll", + "SkeletonLoading", + "Slider", + "SliderPoll", + "SortControl", + "SortableList", + "SortableTable", + "StarRatingPoll", + "StatusDots", + "Stepper", + "SystemAlertGlobalNotificationBar", + "Tabs", + "TaskCompletionCheckList", + "TextTool", + "Textarea", + "ThumbsUpThumbsDownPoll", + "TimelineAdjuster", + "TimelineList", + "Toast", + "ToggleSwitch", + "TreeviewList", + "UndoRedoButtons", + "Upload", + "ValidationMessages", + "Video", + "VideoPlayer", + "VirtualizedList", + "VisualCueForAccessibilityFocusIndicator", + "WinningHandDisplay", + "YesNoPoll", + "ZoomTool", +) + + +def _slugify(name: str) -> str: + text = re.sub(r"([a-z0-9])([A-Z])", r"\1-\2", name) + text = re.sub(r"([A-Z])([A-Z][a-z])", r"\1-\2", text) + return text.replace("_", "-").lower() + + +def _make_preset(name: str) -> AtomPreset: + role = f"swarmakit:vue:{_slugify(name)}" + return AtomPreset( + role=role, + module=SWARMAKIT_MODULE, + export=name, + version=SWARMAKIT_VERSION, + defaults={}, + framework=FRAMEWORK, + package=SWARMAKIT_MODULE, + family="swarmakit", + registry={ + "name": "swarmakit", + "framework": FRAMEWORK, + "package": SWARMAKIT_MODULE, + "version": SWARMAKIT_VERSION, + }, + ) + + +_DEFAULT_PRESETS = tuple(_make_preset(name) for name in _COMPONENT_EXPORTS) + +DEFAULT_PRESETS: dict[str, AtomPreset] = { + preset.role: preset for preset in _DEFAULT_PRESETS +} + +DEFAULT_ATOMS: dict[str, AtomSpec] = { + role: preset.to_spec() for role, preset in DEFAULT_PRESETS.items() +} + +ATOM_TABLE = [ + { + "role": preset.role, + "framework": FRAMEWORK, + "module": preset.module, + "export": preset.export, + "version": preset.version, + "defaults": dict(preset.defaults), + } + for preset in DEFAULT_PRESETS.values() +] + + +def build_registry( + *, + extra_presets: Iterable[AtomPreset | AtomSpec] + | Mapping[str, AtomPreset | AtomSpec] + | None = None, + overrides: Mapping[str, Mapping[str, object]] | None = None, +) -> AtomRegistry: + """Create an AtomRegistry populated with SwarmaKit Vue presets.""" + + catalog = SwarmaAtomCatalog(DEFAULT_PRESETS, props_schema=AtomProps) + + if extra_presets: + catalog = catalog.with_extra_presets(extra_presets) + if overrides: + for role, patch in overrides.items(): + try: + catalog = catalog.with_overrides(role, **patch) + except KeyError: + continue + + return catalog.build_registry() + + +def build_default_registry() -> AtomRegistry: + """Return an AtomRegistry populated with the default SwarmaKit Vue presets.""" + + return build_registry() + + +__all__ = [ + "FRAMEWORK", + "SWARMAKIT_MODULE", + "SWARMAKIT_VERSION", + "PRESET_VERSION", + "DEFAULT_PRESETS", + "DEFAULT_ATOMS", + "ATOM_TABLE", + "build_registry", + "build_default_registry", +] diff --git a/pkgs/experimental/layout_engine_atoms/src/layout_engine_atoms/default.py b/pkgs/experimental/layout_engine_atoms/src/layout_engine_atoms/default.py index bffedf9952..3a5c8bb4ba 100644 --- a/pkgs/experimental/layout_engine_atoms/src/layout_engine_atoms/default.py +++ b/pkgs/experimental/layout_engine_atoms/src/layout_engine_atoms/default.py @@ -1,13 +1,36 @@ from __future__ import annotations -from typing import Iterable, Mapping +from abc import ABC, abstractmethod +from typing import Iterable, Mapping, TypeVar, Type from layout_engine import AtomRegistry, AtomSpec from .spec import AtomPreset -class AtomPresetCatalog: +class IAtomCatalog(ABC): + """Core interface for atom preset catalogs.""" + + @abstractmethod + def presets(self) -> Iterable[AtomPreset]: + """Return the presets provided by the catalog.""" + raise NotImplementedError + + def as_specs(self) -> dict[str, AtomSpec]: + """Materialize presets as a ``role -> AtomSpec`` mapping.""" + return {preset.role: preset.to_spec() for preset in self.presets()} + + def build_registry(self) -> AtomRegistry: + """Create an :class:`AtomRegistry` primed with the catalog presets.""" + registry = AtomRegistry() + registry.register_many(self.as_specs().values()) + return registry + + +CatalogT = TypeVar("CatalogT", bound="AtomPresetCatalog") + + +class AtomPresetCatalog(IAtomCatalog): """In-memory catalog backed by a mapping or iterable of presets.""" def __init__(self, presets: Mapping[str, AtomPreset] | Iterable[AtomPreset]): @@ -28,7 +51,17 @@ def get(self, role: str) -> AtomPreset: def as_specs(self) -> dict[str, AtomSpec]: return {role: preset.to_spec() for role, preset in self._presets.items()} - def build_registry(self) -> AtomRegistry: - registry = AtomRegistry() - registry.register_many(self.as_specs().values()) - return registry + @classmethod + def from_specs(cls: Type[CatalogT], specs: Iterable[AtomSpec]) -> CatalogT: + """Instantiate a catalog from raw :class:`AtomSpec` entries.""" + presets = { + spec.role: AtomPreset( + role=spec.role, + module=spec.module, + export=spec.export, + version=spec.version, + defaults=dict(spec.defaults), + ) + for spec in specs + } + return cls(presets) diff --git a/pkgs/experimental/layout_engine_atoms/src/layout_engine_atoms/manifest.py b/pkgs/experimental/layout_engine_atoms/src/layout_engine_atoms/manifest.py new file mode 100644 index 0000000000..2e99c4df49 --- /dev/null +++ b/pkgs/experimental/layout_engine_atoms/src/layout_engine_atoms/manifest.py @@ -0,0 +1,322 @@ +"""High-level manifest helpers for layout-engine atoms.""" + +from __future__ import annotations + +from dataclasses import dataclass +from typing import Any, Iterable, Mapping, Sequence + +from layout_engine import LayoutCompiler, ManifestBuilder, TileSpec, Viewport +from layout_engine.core.size import Size +from layout_engine.grid.spec import GridSpec, GridTile, GridTrack +from layout_engine.structure import Table as LayoutTable + +from .catalog import build_registry as build_catalog_registry + +_DEFAULT_COLUMNS = 2 + + +@dataclass(slots=True) +class Tile: + """Declarative tile configuration for quick manifest helpers.""" + + id: str + role: str + props: Mapping[str, Any] | None = None + span: int | str = "auto" + row_span: int = 1 + + def to_spec(self) -> TileSpec: + return TileSpec( + id=self.id, + role=self.role, + props=dict(self.props or {}), + ) + + +def tile( + id: str, + role: str, + *, + props: Mapping[str, Any] | None = None, + span: int | str = "auto", + row_span: int = 1, +) -> Tile: + """Convenience factory matching :class:`Tile` signature.""" + + return Tile(id=id, role=role, props=props, span=span, row_span=row_span) + + +def create_registry( + *, + catalog: str = "vue", + extra_presets: Iterable[Mapping[str, Any]] | None = None, + overrides: Mapping[str, Mapping[str, Any]] | None = None, +): + """Return an :class:`AtomRegistry` primed with SwarmaKit presets.""" + + return build_catalog_registry( + catalog, + extra_presets=extra_presets, + overrides=overrides, + ) + + +def quick_manifest( + tiles: Sequence[Tile | Mapping[str, Any]], + *, + catalog: str = "vue", + registry=None, + columns: int = _DEFAULT_COLUMNS, + row_height: int = 220, + viewport: Viewport | tuple[int, int] | None = None, + channels: Sequence[Mapping[str, Any]] | None = None, + ws_routes: Sequence[Mapping[str, Any]] | None = None, + version: str = "2025.10", +): + """Build a manifest using the SwarmaKit registry defaults.""" + + registry = registry or create_registry(catalog=catalog) + manifest, _ = build_manifest_from_tiles( + tiles, + registry=registry, + columns=columns, + row_height=row_height, + viewport=viewport, + channels=channels, + ws_routes=ws_routes, + version=version, + ) + return manifest + + +def build_manifest_from_tiles( + tiles: Sequence[Tile | Mapping[str, Any]], + *, + registry, + columns: int = _DEFAULT_COLUMNS, + row_height: int = 220, + viewport: Viewport | tuple[int, int] | None = None, + channels: Sequence[Mapping[str, Any]] | None = None, + ws_routes: Sequence[Mapping[str, Any]] | None = None, + version: str = "2025.10", +): + """Build a :class:`Manifest` and return it alongside the view model.""" + + columns = max(1, int(columns)) + grid = _build_grid(columns, row_height) + + tile_entries: list[Tile] = [_coerce_tile(t) for t in tiles] + placements = _auto_place(tile_entries, columns) + + compiler = LayoutCompiler() + vp = _viewport_from(viewport, columns, placements, row_height) + frames = compiler.frames(grid, vp, placements) + + specs = [entry.to_spec() for entry in tile_entries] + view_model = compiler.view_model( + grid, + vp, + frames, + specs, + atoms_registry=registry, + channels=channels, + ws_routes=ws_routes, + ) + + manifest = ManifestBuilder().build(view_model, version=version) + _enrich_atom_metadata(manifest, registry) + return manifest, view_model + + +def quick_manifest_from_table( + layout: LayoutTable, + tiles: Sequence[Tile | Mapping[str, Any]], + *, + catalog: str = "vue", + registry=None, + row_height: int = 220, + viewport: Viewport | tuple[int, int] | None = None, + channels: Sequence[Mapping[str, Any]] | None = None, + ws_routes: Sequence[Mapping[str, Any]] | None = None, + version: str = "2025.10", +): + """Build a manifest from a structure.Table definition.""" + + registry = registry or create_registry(catalog=catalog) + manifest, _ = build_manifest_from_table( + layout, + tiles, + registry=registry, + row_height=row_height, + viewport=viewport, + channels=channels, + ws_routes=ws_routes, + version=version, + ) + return manifest + + +def build_manifest_from_table( + layout: LayoutTable, + tiles: Sequence[Tile | Mapping[str, Any]], + *, + registry, + row_height: int = 220, + viewport: Viewport | tuple[int, int] | None = None, + channels: Sequence[Mapping[str, Any]] | None = None, + ws_routes: Sequence[Mapping[str, Any]] | None = None, + version: str = "2025.10", +): + """Build a manifest from a Table/Row/Col structure.""" + + compiler = LayoutCompiler() + vp = _ensure_viewport(viewport) + grid, placements, frames = compiler.frames_from_structure( + layout, + vp, + row_height=row_height, + ) + + specs = [_coerce_tile(t).to_spec() for t in tiles] + view_model = compiler.view_model( + grid, + vp, + frames, + specs, + atoms_registry=registry, + channels=channels, + ws_routes=ws_routes, + ) + + manifest = ManifestBuilder().build(view_model, version=version) + _enrich_atom_metadata(manifest, registry) + return manifest, view_model + + +def _coerce_tile(candidate: Tile | Mapping[str, Any]) -> Tile: + if isinstance(candidate, Tile): + return candidate + data = dict(candidate) + return Tile( + id=data.pop("id"), + role=data.pop("role"), + props=data.pop("props", None), + span=data.pop("span", "auto"), + row_span=data.pop("row_span", 1), + ) + + +def _build_grid(columns: int, row_height: int) -> GridSpec: + tracks = [GridTrack(size=Size(1, "fr")) for _ in range(columns)] + return GridSpec( + columns=tracks, + row_height=row_height, + tokens={"columns": f"sgd:columns:{columns}"}, + ) + + +def _auto_place(tiles: Sequence[Tile], columns: int) -> list[GridTile]: + placements: list[GridTile] = [] + col = 0 + row = 0 + for entry in tiles: + span = _span_value(entry.span, columns) + if span > columns: + span = columns + if col + span > columns: + col = 0 + row += 1 + placements.append( + GridTile( + tile_id=entry.id, + col=col, + row=row, + col_span=span, + row_span=max(1, entry.row_span), + ) + ) + col += span + if col >= columns: + col = 0 + row += 1 + return placements + + +def _span_value(span: int | str, columns: int) -> int: + if isinstance(span, int): + return max(1, span) + mapping = { + "full": columns, + "half": max(1, columns // 2) if columns > 1 else 1, + "third": max(1, columns // 3), + "auto": 1, + } + return mapping.get(span, 1) + + +def _viewport_from( + viewport: Viewport | tuple[int, int] | None, + columns: int, + placements: Sequence[GridTile], + row_height: int, +) -> Viewport: + if isinstance(viewport, Viewport): + return viewport + if isinstance(viewport, tuple): + width, height = viewport + return Viewport(width=width, height=height) + rows = 1 + if placements: + rows = max(placement.row + placement.row_span for placement in placements) + width = max(columns * 640, 640) + height = max(rows * row_height, row_height) + return Viewport(width=width, height=height) + + +def _ensure_viewport(viewport: Viewport | tuple[int, int] | None) -> Viewport: + if isinstance(viewport, Viewport): + return viewport + if isinstance(viewport, tuple): + width, height = viewport + return Viewport(width=width, height=height) + return Viewport(width=1280, height=960) + + +def _enrich_atom_metadata(manifest, registry) -> None: + tiles = manifest.tiles if isinstance(manifest.tiles, list) else list(manifest.tiles) + for tile in tiles: + if not isinstance(tile, dict): + continue + try: + spec = registry.get(tile["role"]) + except Exception: # noqa: BLE001 + continue + atom_data = dict(tile.get("atom") or {}) + atom_data.setdefault("role", spec.role) + atom_data.setdefault("module", spec.module) + atom_data.setdefault("export", spec.export) + atom_data.setdefault("version", spec.version) + if spec.defaults and not atom_data.get("defaults"): + atom_data["defaults"] = dict(spec.defaults) + if getattr(spec, "family", None) and not atom_data.get("family"): + atom_data["family"] = spec.family + if getattr(spec, "framework", None) and not atom_data.get("framework"): + atom_data["framework"] = spec.framework + if getattr(spec, "package", None) and not atom_data.get("package"): + atom_data["package"] = spec.package + tokens = getattr(spec, "tokens", None) + if tokens and not atom_data.get("tokens"): + atom_data["tokens"] = dict(tokens) + registry_meta = getattr(spec, "registry", None) + if registry_meta and not atom_data.get("registry"): + atom_data["registry"] = dict(registry_meta) + tile["atom"] = atom_data + + +__all__ = [ + "Tile", + "tile", + "create_registry", + "quick_manifest", + "build_manifest_from_tiles", +] diff --git a/pkgs/experimental/layout_engine_atoms/src/layout_engine_atoms/runtime/__init__.py b/pkgs/experimental/layout_engine_atoms/src/layout_engine_atoms/runtime/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/pkgs/experimental/layout_engine_atoms/src/layout_engine_atoms/runtime/vue/__init__.py b/pkgs/experimental/layout_engine_atoms/src/layout_engine_atoms/runtime/vue/__init__.py new file mode 100644 index 0000000000..ef28932f59 --- /dev/null +++ b/pkgs/experimental/layout_engine_atoms/src/layout_engine_atoms/runtime/vue/__init__.py @@ -0,0 +1,21 @@ +"""Vue runtime helpers for layout_engine atoms.""" + +from .app import ( + LayoutOptions, + RouterOptions, + ScriptSpec, + UIHooks, + mount_layout_app, +) +from .realtime import RealtimeBinding, RealtimeChannel, RealtimeOptions + +__all__ = [ + "LayoutOptions", + "RouterOptions", + "ScriptSpec", + "UIHooks", + "RealtimeBinding", + "RealtimeChannel", + "RealtimeOptions", + "mount_layout_app", +] diff --git a/pkgs/experimental/layout_engine_atoms/src/layout_engine_atoms/runtime/vue/app.py b/pkgs/experimental/layout_engine_atoms/src/layout_engine_atoms/runtime/vue/app.py new file mode 100644 index 0000000000..fd5107227b --- /dev/null +++ b/pkgs/experimental/layout_engine_atoms/src/layout_engine_atoms/runtime/vue/app.py @@ -0,0 +1,364 @@ +from __future__ import annotations + +import inspect +import json +from dataclasses import dataclass +from pathlib import Path +from typing import Any, Callable, Literal, Mapping, MutableMapping, Sequence + +from fastapi import APIRouter, FastAPI, Request +from fastapi.responses import HTMLResponse, JSONResponse +from fastapi.staticfiles import StaticFiles + +from .template import render_shell +from .realtime import RealtimeBinding, RealtimeOptions, WebsocketMuxHub + +ManifestBuilder = Callable[[], Mapping[str, Any] | MutableMapping[str, Any] | Any] + + +@dataclass(slots=True) +class ScriptSpec: + src: str | None = None + content: str | None = None + type: str = "module" + async_attr: bool = False + defer: bool = False + + +@dataclass(slots=True) +class RouterOptions: + manifest_url: str = "./manifest.json" + page_param: str = "page" + default_page_id: str | None = None + history: Literal["history", "hash"] = "history" + hydrate_site_meta: bool = True + + @property + def enable_multipage(self) -> bool: + return bool(self.default_page_id) or bool(self.page_param) + + +@dataclass(slots=True) +class LayoutOptions: + title: str | None = None + accent_palette: dict[str, str] | None = None + import_map_overrides: dict[str, str] | None = None + extra_styles: Sequence[str] | None = None + extra_scripts: Sequence[ScriptSpec] | None = None + router: RouterOptions | None = None + + +@dataclass(slots=True) +class UIHooks: + header_slot: str | None = None + nav_slot: str | None = None + content_slot: str | None = None + footer_slot: str | None = None + client_setup: str | None = None + + +DEFAULT_TITLE = "Layout Engine Dashboard" + +DEFAULT_PALETTE = { + "accent": "rgba(56, 189, 248, 0.75)", + "panel": "rgba(15, 23, 42, 0.92)", + "surface": "rgba(2, 6, 23, 1)", + "text": "#e2e8f0", +} + +_ASSETS_ROOT = Path(__file__).resolve().parent / "assets" +_LAYOUT_ENGINE_DIST = _ASSETS_ROOT / "layout-engine-vue" +_SWARMA_VUE_DIST = _ASSETS_ROOT / "swarma-vue" + + +def mount_layout_app( + app: FastAPI, + manifest_builder: ManifestBuilder, + *, + base_path: str = "/dashboard", + title: str = DEFAULT_TITLE, + layout_options: LayoutOptions | None = None, + ui_hooks: UIHooks | None = None, + realtime: RealtimeOptions | None = None, +) -> None: + """Mount the layout engine Vue runtime on an existing FastAPI app.""" + + layout_options = layout_options or LayoutOptions() + ui_hooks = ui_hooks or UIHooks() + + resolved_title = layout_options.title or title + router_options = layout_options.router or RouterOptions() + accent_palette = {**DEFAULT_PALETTE, **(layout_options.accent_palette or {})} + + import_map = { + "vue": "https://cdn.jsdelivr.net/npm/vue@3/dist/vue.esm-browser.js", + "eventemitter3": "https://cdn.jsdelivr.net/npm/eventemitter3@5/dist/eventemitter3.esm.js", + "@swarmakit/vue": "./static/swarma-vue/vue.js", + } + if layout_options.import_map_overrides: + import_map.update(layout_options.import_map_overrides) + + pre_boot_scripts = [ + _render_script(spec) for spec in layout_options.extra_scripts or [] + ] + + norm_base = _normalize_base_path(base_path) + realtime_payload = {"enabled": False} + ws_route: str | None = None + hub: WebsocketMuxHub | None = None + if realtime: + ws_route = _join_paths(norm_base, realtime.path) + hub = WebsocketMuxHub(path=realtime.path) + realtime_payload = { + "enabled": True, + "path": ws_route, + "channels": [channel.id for channel in realtime.channels], + "autoSubscribe": realtime.auto_subscribe, + } + binding_script = _render_binding_script(realtime.bindings) + if binding_script: + if ui_hooks.client_setup: + ui_hooks.client_setup = f"{ui_hooks.client_setup}\n{binding_script}" + else: + ui_hooks.client_setup = binding_script + + config_payload = { + "title": resolved_title, + "theme": { + "accentPalette": accent_palette, + }, + "router": { + "manifestUrl": router_options.manifest_url, + "pageParam": router_options.page_param, + "defaultPageId": router_options.default_page_id, + "history": router_options.history, + "hydrateThemeFromManifest": router_options.hydrate_site_meta, + "enableMultipage": router_options.enable_multipage, + }, + "ui": { + "headerSlot": ui_hooks.header_slot, + "navSlot": ui_hooks.nav_slot, + "contentSlot": ui_hooks.content_slot, + "footerSlot": ui_hooks.footer_slot, + }, + "clientSetup": ui_hooks.client_setup, + "realtime": realtime_payload, + } + + shell_html = render_shell( + title=resolved_title, + import_map=import_map, + config_payload=config_payload, + palette=accent_palette, + bootstrap_module="./static/layout-engine-vue/mpa-bootstrap.js", + extra_styles=layout_options.extra_styles, + pre_boot_scripts=pre_boot_scripts, + ) + + router = _create_layout_router( + manifest_builder, + shell_html, + router_options, + realtime=realtime, + ws_route=ws_route, + ) + app.include_router(router, prefix=norm_base if norm_base != "/" else "") + + static_prefix = "" if norm_base == "/" else norm_base + app.mount( + f"{static_prefix}/static/layout-engine-vue", + StaticFiles(directory=_LAYOUT_ENGINE_DIST, html=False), + name="layout-engine-vue", + ) + app.mount( + f"{static_prefix}/static/swarma-vue", + StaticFiles(directory=_SWARMA_VUE_DIST, html=False), + name="swarma-vue", + ) + if realtime and hub and ws_route: + hub.mount(app, ws_route) + + async def _start_realtime() -> None: # noqa: D401 + await hub.start_publishers(realtime.publishers) + + async def _stop_realtime() -> None: # noqa: D401 + await hub.stop_publishers() + await hub.disconnect_all() + + app.add_event_handler("startup", _start_realtime) + app.add_event_handler("shutdown", _stop_realtime) + app.state.layout_engine_realtime = hub + + +def _create_layout_router( + manifest_builder: ManifestBuilder, + shell_html: str, + router_options: RouterOptions, + *, + realtime: RealtimeOptions | None, + ws_route: str | None, +) -> APIRouter: + router = APIRouter() + + @router.get("/", response_class=HTMLResponse) + async def layout_index(_: Request) -> HTMLResponse: # noqa: D401 + return HTMLResponse(shell_html) + + @router.get("/manifest.json", response_class=JSONResponse) + async def manifest_endpoint(request: Request) -> JSONResponse: + manifest = await _call_builder(manifest_builder, request) + payload = _normalize_manifest(manifest) + if router_options.enable_multipage and router_options.page_param: + requested = request.query_params.get(router_options.page_param) + if requested: + payload.setdefault("meta", {}).setdefault("page", {})["requested"] = ( + requested + ) + _inject_realtime_metadata(payload, realtime, ws_route) + return JSONResponse(content=payload) + + return router + + +def _normalize_base_path(base_path: str) -> str: + if not base_path: + return "/" + if not base_path.startswith("/"): + base_path = "/" + base_path + if base_path != "/" and base_path.endswith("/"): + base_path = base_path[:-1] + return base_path or "/" + + +def _join_paths(base_path: str, suffix: str) -> str: + if not suffix: + return base_path or "/" + if not suffix.startswith("/"): + suffix = "/" + suffix + if base_path in ("", "/"): + return suffix + if suffix == "/": + return base_path + return f"{base_path}{suffix}" + + +async def _call_builder( + builder: ManifestBuilder, request: Request | None = None +) -> Any: + if request is None: + raise RuntimeError("manifest builder requires request context") + result = builder(request) + if inspect.isawaitable(result): + return await result + return result + + +def _normalize_manifest(manifest: Any) -> Mapping[str, Any]: + if manifest is None: + raise ValueError("Manifest builder returned None") + if hasattr(manifest, "model_dump"): + return manifest.model_dump() + if hasattr(manifest, "model_dump_json"): + return manifest.model_dump() + if isinstance(manifest, Mapping): + return dict(manifest) + raise TypeError( + "Manifest builder must return a Mapping or pydantic model; got " + f"{type(manifest)!r}" + ) + + +def _inject_realtime_metadata( + payload: MutableMapping[str, Any], + realtime: RealtimeOptions | None, + ws_route: str | None, +) -> None: + if not realtime or not ws_route: + return + rt_channels = payload.setdefault("channels", []) + existing_ids = { + entry.get("id") + for entry in rt_channels + if isinstance(entry, Mapping) and isinstance(entry.get("id"), str) + } + for channel in realtime.channels: + if channel.id in existing_ids: + continue + rt_channels.append(channel.as_manifest()) + + ws_routes = payload.setdefault("ws_routes", []) + existing_paths = { + entry.get("path") + for entry in ws_routes + if isinstance(entry, Mapping) and isinstance(entry.get("path"), str) + } + if ws_route not in existing_paths: + ws_routes.append( + { + "path": ws_route, + "channels": [channel.id for channel in realtime.channels], + } + ) + + +def _render_binding_script(bindings: Sequence[RealtimeBinding]) -> str: + if not bindings: + return "" + payload = json.dumps( + [binding.as_payload() for binding in bindings], separators=(",", ":") + ) + return ( + "(function realtimeBindingBootstrap() {\n" + " if (!context || !context.manifest) {\n" + " console.warn('[layout-engine] realtime bindings require manifest context.');\n" + " return;\n" + " }\n" + " const manifest = context.manifest;\n" + f" const bindings = {payload};\n" + " const EVENT_NAME = 'layout-engine:channel';\n" + " const getValue = (payload, path) => {\n" + " if (!path) return payload;\n" + " return path.split('.').reduce((acc, key) => {\n" + " if (acc === undefined || acc === null) return undefined;\n" + " const next = acc[key];\n" + " return next === undefined ? undefined : next;\n" + " }, payload);\n" + " };\n" + " const applyBinding = (binding, payload) => {\n" + " const tiles = Array.isArray(manifest.tiles) ? manifest.tiles : [];\n" + " const target = tiles.find((tile) => tile.id === binding.tileId);\n" + " if (!target) return;\n" + " target.props = target.props && typeof target.props === 'object' ? target.props : {};\n" + " for (const [prop, path] of Object.entries(binding.fields || {})) {\n" + " const value = getValue(payload, path);\n" + " if (value !== undefined) {\n" + " target.props[prop] = value;\n" + " }\n" + " }\n" + " };\n" + " const handler = (event) => {\n" + " const detail = event.detail || {};\n" + " for (const binding of bindings) {\n" + " if (binding.channel !== detail.channel) continue;\n" + " applyBinding(binding, detail.payload || {});\n" + " }\n" + " };\n" + " window.addEventListener(EVENT_NAME, handler);\n" + "})();\n" + ) + + +def _render_script(spec: ScriptSpec) -> str: + attrs: list[str] = [] + script_type = spec.type or "module" + attrs.append(f'type="{script_type}"') + if spec.async_attr: + attrs.append("async") + if spec.defer: + attrs.append("defer") + if spec.src: + attrs.append(f'src="{spec.src}"') + attrs_str = " ".join(attrs) + if spec.content: + return f"" + return f"" diff --git a/pkgs/experimental/layout_engine_atoms/src/layout_engine_atoms/runtime/vue/assets/__init__.py b/pkgs/experimental/layout_engine_atoms/src/layout_engine_atoms/runtime/vue/assets/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/pkgs/experimental/layout_engine_atoms/src/layout_engine_atoms/runtime/vue/assets/layout-engine-vue/index.js b/pkgs/experimental/layout_engine_atoms/src/layout_engine_atoms/runtime/vue/assets/layout-engine-vue/index.js new file mode 100644 index 0000000000..2f3154a072 --- /dev/null +++ b/pkgs/experimental/layout_engine_atoms/src/layout_engine_atoms/runtime/vue/assets/layout-engine-vue/index.js @@ -0,0 +1,473 @@ +// src/composables.ts +import { computed, ref as ref2 } from "vue"; + +// src/loader.ts +import { markRaw } from "vue"; +var manifestCache = /* @__PURE__ */ new Map(); +var componentCache = /* @__PURE__ */ new Map(); +async function defaultFetcher(url) { + const res = await fetch(url); + if (!res.ok) { + throw new Error(`Failed to fetch manifest: ${res.status} ${res.statusText}`); + } + return await res.json(); +} +async function defaultImportResolver(atom) { + if (!atom.module) { + throw new Error(`Missing module specifier for atom ${atom.role}`); + } + const mod = await import( + /* @vite-ignore */ + atom.module + ); + const exportName = atom.export ?? "default"; + if (!(exportName in mod)) { + throw new Error(`Export '${exportName}' not found in module ${atom.module}`); + } + return mod[exportName]; +} +function cacheKeyFromManifest(manifest, explicit) { + if (explicit) return explicit; + const version = manifest.meta?.atoms && typeof manifest.meta.atoms === "object" ? manifest.meta.atoms["revision"] : void 0; + return String(version ?? manifest.etag ?? manifest.version ?? "default"); +} +async function loadManifest(manifestUrl, options = {}) { + const fetcher = options.fetcher ?? defaultFetcher; + const loader = options.importResolver ?? defaultImportResolver; + const manifest = await fetcher(manifestUrl); + const cacheKey = cacheKeyFromManifest(manifest, options.cacheKey); + if (manifestCache.has(cacheKey)) { + const cachedManifest = manifestCache.get(cacheKey); + const cachedComponents = componentCache.get(cacheKey) ?? /* @__PURE__ */ new Map(); + return { manifest: cachedManifest, components: cachedComponents }; + } + const registry = /* @__PURE__ */ new Map(); + const swarmaAtoms = manifest.tiles.map((tile) => tile.atom).filter((atom) => Boolean(atom && atom.family === "swarmakit")); + for (const atom of swarmaAtoms) { + if (registry.has(atom.role)) continue; + const component = await loader(atom); + registry.set(atom.role, { component: markRaw(component), atom }); + } + manifestCache.set(cacheKey, manifest); + componentCache.set(cacheKey, registry); + return { manifest, components: registry }; +} + +// src/plugin.ts +import { inject } from "vue"; +var MANIFEST_KEY = Symbol("layout-engine:manifest"); +var REGISTRY_KEY = Symbol("layout-engine:registry"); +var MUX_KEY = Symbol("layout-engine:mux"); +function createLayoutEnginePlugin(manifest, registry, mux) { + return { + install(app) { + app.provide(MANIFEST_KEY, manifest); + app.provide(REGISTRY_KEY, registry); + if (mux) { + app.provide(MUX_KEY, mux); + } + } + }; +} +function useLayoutManifest() { + const manifest = inject(MANIFEST_KEY); + if (!manifest) { + throw new Error("Layout manifest not found; did you install createLayoutEnginePlugin?"); + } + return manifest; +} +function useAtomRegistry() { + const registry = inject(REGISTRY_KEY); + if (!registry) { + throw new Error("Atom registry not found; did you install createLayoutEnginePlugin?"); + } + return registry; +} +function useMuxContext() { + const mux = inject(MUX_KEY); + if (!mux) { + throw new Error("Mux context not provided; pass a 'mux' option to createLayoutEngineApp."); + } + return mux; +} + +// src/events.ts +import { onBeforeUnmount } from "vue"; + +// src/mux.ts +import EventEmitter from "eventemitter3"; +var DEFAULT_RECONNECT_DELAY = 1e3; +var DEFAULT_MAX_DELAY = 1e4; +var DEFAULT_BACKOFF = 1.7; +var WSMuxClient = class extends EventEmitter { + constructor(options) { + super(); + this.socket = null; + this.reconnectTimer = null; + this.subscriptions = /* @__PURE__ */ new Map(); + this.pendingSubscribes = /* @__PURE__ */ new Set(); + this.outboundQueue = []; + this.handleOpen = () => { + this.emit("open"); + this.reconnectDelay = this.options.reconnectDelay; + while (this.outboundQueue.length) { + const message = this.outboundQueue.shift(); + if (message) { + this.socket?.send(JSON.stringify(message)); + } + } + this.flushSubscribes(); + }; + this.handleMessage = (event) => { + try { + const data = JSON.parse(String(event.data)); + if (!data || typeof data !== "object" || !("channel" in data)) { + this.emit("raw", data); + return; + } + const channelId = String(data["channel"] ?? ""); + if (!channelId) return; + this.emit("message", data); + const handlers = this.subscriptions.get(channelId); + if (!handlers) return; + const message = { + channel: channelId, + payload: data["payload"], + ...data + }; + for (const handler of handlers) { + handler(message); + } + } catch (err) { + this.emit("error", err); + } + }; + this.handleClose = () => { + this.emit("close"); + this.scheduleReconnect(); + }; + this.handleError = (event) => { + this.emit("error", event); + }; + if (!options.url) { + throw new Error("WSMuxClient requires a url"); + } + this.options = { + protocols: options.protocols ?? [], + reconnectDelay: options.reconnectDelay ?? DEFAULT_RECONNECT_DELAY, + maxReconnectDelay: options.maxReconnectDelay ?? DEFAULT_MAX_DELAY, + backoffFactor: options.backoffFactor ?? DEFAULT_BACKOFF, + url: options.url + }; + this.reconnectDelay = this.options.reconnectDelay; + } + connect() { + if (this.socket && (this.socket.readyState === WebSocket.OPEN || this.socket.readyState === WebSocket.CONNECTING)) { + return; + } + this.clearReconnect(); + this.socket = new WebSocket(this.options.url, this.options.protocols); + this.socket.addEventListener("open", this.handleOpen); + this.socket.addEventListener("message", this.handleMessage); + this.socket.addEventListener("close", this.handleClose); + this.socket.addEventListener("error", this.handleError); + } + disconnect() { + this.clearReconnect(); + if (this.socket) { + this.socket.removeEventListener("open", this.handleOpen); + this.socket.removeEventListener("message", this.handleMessage); + this.socket.removeEventListener("close", this.handleClose); + this.socket.removeEventListener("error", this.handleError); + this.socket.close(); + this.socket = null; + } + } + subscribe(channelId, handler) { + if (!this.subscriptions.has(channelId)) { + this.subscriptions.set(channelId, /* @__PURE__ */ new Set()); + this.enqueueSubscribe(channelId); + } + const set = this.subscriptions.get(channelId); + set.add(handler); + this.connect(); + return () => { + const group = this.subscriptions.get(channelId); + if (!group) return; + group.delete(handler); + if (group.size === 0) { + this.subscriptions.delete(channelId); + this.enqueueUnsubscribe(channelId); + } + }; + } + publish(channelId, payload) { + this.send({ action: "publish", channel: channelId, payload }); + } + enqueueSubscribe(channelId) { + this.pendingSubscribes.add(channelId); + this.send({ action: "subscribe", channel: channelId }); + } + enqueueUnsubscribe(channelId) { + this.send({ action: "unsubscribe", channel: channelId }); + } + flushSubscribes() { + for (const channelId of this.subscriptions.keys()) { + this.send({ action: "subscribe", channel: channelId }); + } + this.pendingSubscribes.clear(); + } + send(message) { + if (this.socket && this.socket.readyState === WebSocket.OPEN) { + this.socket.send(JSON.stringify(message)); + } else { + this.outboundQueue.push(message); + this.connect(); + } + } + scheduleReconnect() { + this.clearReconnect(); + this.reconnectTimer = setTimeout(() => { + this.connect(); + this.reconnectDelay = Math.min( + this.reconnectDelay * this.options.backoffFactor, + this.options.maxReconnectDelay + ); + }, this.reconnectDelay); + } + clearReconnect() { + if (this.reconnectTimer) { + clearTimeout(this.reconnectTimer); + this.reconnectTimer = null; + } + } +}; + +// src/events.ts +var muxKey = Symbol("layout-engine:mux"); +function createMuxContext(options) { + const client = new WSMuxClient({ url: options.muxUrl, protocols: options.protocols }); + client.connect(); + return { + client, + channels: options.manifest.channels ?? [] + }; +} +function useMux(mux, channelId, handler) { + const unsubscribe = mux.client.subscribe(channelId, handler); + onBeforeUnmount(() => unsubscribe()); + return { + publish: (payload) => mux.client.publish(channelId, payload) + }; +} + +// src/composables.ts +async function createLayoutEngineApp(options) { + const { manifest, components } = await loadManifest(options.manifestUrl); + const mux = options.muxUrl ? createMuxContext({ + manifest, + muxUrl: options.muxUrl, + protocols: options.muxProtocols + }) : void 0; + const manifestRef = ref2(manifest); + const registryRef = ref2(components); + const tiles = computed(() => manifestRef.value.tiles); + const plugin = createLayoutEnginePlugin(manifestRef.value, registryRef.value, mux); + return { + plugin, + manifest: manifestRef, + tiles, + components: registryRef, + mux + }; +} + +// src/site.ts +import { computed as computed2, isRef, reactive, toRefs, watchEffect } from "vue"; +function normaliseSite(manifest) { + const site = manifest.site; + if (!site) { + return { pages: [], activePageId: null, basePath: void 0 }; + } + const pages = Array.isArray(site.pages) ? site.pages.map((page) => ({ + id: String(page?.id ?? ""), + route: String(page?.route ?? "/"), + title: page?.title ? String(page.title) : void 0, + slots: Array.isArray(page?.slots) ? page?.slots : void 0 + })) : []; + return { + pages, + activePageId: site.active_page ?? site?.["activePage"], + basePath: site.navigation && typeof site.navigation === "object" ? site.navigation["base_path"] : void 0 + }; +} +function useSiteNavigation(manifest) { + const getManifest = () => isRef(manifest) ? manifest.value : manifest; + const state = reactive(normaliseSite(getManifest())); + watchEffect(() => { + const next = normaliseSite(getManifest()); + state.pages = next.pages; + state.activePageId = next.activePageId; + state.basePath = next.basePath; + }); + const activePage = computed2( + () => state.pages.find((page) => page.id === state.activePageId) ?? null + ); + const navigate = (pageId) => { + const page = state.pages.find((p) => p.id === pageId); + if (!page) return null; + state.activePageId = page.id; + return page.route; + }; + return { + ...toRefs(state), + activePage, + navigate + }; +} + +// src/components/LayoutEngineView.ts +import { defineComponent, h } from "vue"; +var LayoutEngineView_default = defineComponent({ + name: "LayoutEngineView", + setup(_, { slots }) { + const manifest = useLayoutManifest(); + const registry = useAtomRegistry(); + const site = useSiteNavigation(manifest); + const renderTiles = () => { + if (!manifest.tiles.length) { + return []; + } + const nodes = []; + for (const tile of manifest.tiles) { + const entry = registry.get(tile.role); + if (!entry) { + console.warn(`Atom '${tile.role}' not registered in registry`); + continue; + } + const frame = tile.frame ?? { x: 0, y: 0, w: 0, h: 0 }; + const style = { + position: "absolute", + left: `${frame.x}px`, + top: `${frame.y}px`, + width: `${frame.w}px`, + height: `${frame.h}px`, + boxSizing: "border-box", + padding: "12px", + display: "flex" + }; + nodes.push( + h( + "div", + { key: tile.id, class: "layout-engine-tile", style }, + [h(entry.component, { ...tile.props })] + ) + ); + } + return nodes; + }; + return () => { + const viewportWidth = manifest.viewport?.width ?? 0; + const viewportHeight = manifest.viewport?.height ?? 0; + if (slots.default) { + return slots.default({ + manifest, + site, + tiles: manifest.tiles, + components: registry + }); + } + return h( + "div", + { + class: "layout-engine-view", + style: { + position: "relative", + width: viewportWidth ? `${viewportWidth}px` : "100%", + minHeight: viewportHeight ? `${viewportHeight}px` : "auto" + } + }, + renderTiles() + ); + }; + } +}); + +// src/components/LayoutEngineShell.ts +import { defineComponent as defineComponent2, h as h2 } from "vue"; +var LayoutEngineShell_default = defineComponent2({ + name: "LayoutEngineShell", + setup(_, { slots }) { + const manifest = useLayoutManifest(); + const site = useSiteNavigation(manifest); + const defaultSlot = slots.default; + return () => { + const nav = site.pages.value; + const current = site.activePage.value; + return h2("div", { class: "layout-engine-shell" }, [ + defaultSlot ? defaultSlot({ site, manifest }) : h2( + "nav", + { class: "layout-engine-shell__nav" }, + nav.map( + (page) => h2( + "button", + { + class: [ + "layout-engine-shell__nav-item", + page.id === current?.id && "is-active" + ], + onClick: () => site.navigate(page.id) + }, + page.title ?? page.id + ) + ) + ), + h2(LayoutEngineView_default) + ]); + }; + } +}); + +// src/components/NavLink.ts +import { computed as computed3, defineComponent as defineComponent3, h as h3 } from "vue"; +var NavLink_default = defineComponent3({ + name: "LayoutEngineNavLink", + props: { + pageId: { + type: String, + required: true + } + }, + setup(props, { slots }) { + const manifest = useLayoutManifest(); + const site = useSiteNavigation(manifest); + const page = computed3(() => site.pages.value.find((p) => p.id === props.pageId)); + const onClick = () => site.navigate(props.pageId); + return () => h3( + "button", + { + class: [ + "layout-engine-nav-link", + page.value?.id === site.activePage.value?.id && "is-active" + ], + onClick + }, + slots.default ? slots.default() : page.value?.title ?? props.pageId + ); + } +}); +export { + NavLink_default as LayoutEngineNavLink, + LayoutEngineShell_default as LayoutEngineShell, + LayoutEngineView_default as LayoutEngineView, + WSMuxClient, + createLayoutEngineApp, + createLayoutEnginePlugin, + createMuxContext, + loadManifest, + useAtomRegistry, + useLayoutManifest, + useMux, + useMuxContext, + useSiteNavigation +}; diff --git a/pkgs/experimental/layout_engine_atoms/src/layout_engine_atoms/runtime/vue/assets/layout-engine-vue/mpa-bootstrap.js b/pkgs/experimental/layout_engine_atoms/src/layout_engine_atoms/runtime/vue/assets/layout-engine-vue/mpa-bootstrap.js new file mode 100644 index 0000000000..6924805303 --- /dev/null +++ b/pkgs/experimental/layout_engine_atoms/src/layout_engine_atoms/runtime/vue/assets/layout-engine-vue/mpa-bootstrap.js @@ -0,0 +1,483 @@ +import { + createApp, + defineComponent, + reactive, + computed, + watch, + onMounted, + onBeforeUnmount, +} from "vue"; +import { + createLayoutEnginePlugin, + LayoutEngineShell, + useSiteNavigation, + loadManifest, +} from "./index.js"; + +const CONFIG_ELEMENT_ID = "le-shell-config"; +const DEFAULT_TITLE = "Layout Engine Dashboard"; +const DEFAULT_HEADER_TEMPLATE = ` +
+

{{ shellTitle }}

+

{{ shellSubtitle }}

+
+`; +const DEFAULT_CONTENT_TEMPLATE = ` + + + +`; + +function parseShellConfig() { + const el = document.getElementById(CONFIG_ELEMENT_ID); + if (!el) return {}; + try { + return JSON.parse(el.textContent || "{}"); + } catch (error) { + console.error("Failed to parse shell configuration", error); + return {}; + } +} + +const REALTIME_EVENT = "layout-engine:channel"; + +const shellConfig = parseShellConfig(); +const routerConfig = { + manifestUrl: shellConfig.router?.manifestUrl ?? "./manifest.json", + pageParam: shellConfig.router?.pageParam ?? "page", + defaultPageId: shellConfig.router?.defaultPageId ?? null, + history: shellConfig.router?.history ?? "history", + hydrateThemeFromManifest: + shellConfig.router?.hydrateThemeFromManifest ?? true, + enableMultipage: shellConfig.router?.enableMultipage ?? false, +}; + +const realtimeConfig = normalizeRealtimeConfig(shellConfig.realtime); + +const basePalette = shellConfig.theme?.accentPalette ?? {}; + +function normalizeRealtimeConfig(raw) { + if (!raw || typeof raw !== "object") { + return { enabled: false, channels: [] }; + } + return { + enabled: Boolean(raw.enabled && raw.path), + path: raw.path ?? "", + autoSubscribe: raw.autoSubscribe ?? true, + channels: Array.isArray(raw.channels) ? raw.channels : [], + }; +} + +function applyPalette(palette) { + const root = document.documentElement; + Object.entries(palette).forEach(([key, value]) => { + root.style.setProperty(`--le-${key.replace(/_/g, "-")}`, value); + }); +} + +applyPalette(basePalette); + +function mergeThemeFromManifest(manifest) { + if (!routerConfig.hydrateThemeFromManifest) { + return; + } + const theme = manifest?.meta?.theme; + if (!theme || typeof theme !== "object") { + return; + } + applyPalette({ ...basePalette, ...theme }); +} + +function withPageParam(url, pageId) { + if (!routerConfig.pageParam) { + return url; + } + const [base, query = ""] = url.split("?"); + const params = new URLSearchParams(query); + if (pageId) { + params.set(routerConfig.pageParam, pageId); + } else { + params.delete(routerConfig.pageParam); + } + const queryString = params.toString(); + return queryString ? `${base}?${queryString}` : base; +} + +function currentPageFromLocation() { + if (!routerConfig.enableMultipage) { + return null; + } + if (routerConfig.history === "hash") { + const hash = window.location.hash.replace(/^#/, ""); + return hash || null; + } + const params = new URLSearchParams(window.location.search); + return params.get(routerConfig.pageParam) || null; +} + +function updateLocation(pageId, { replace = false } = {}) { + if (!routerConfig.enableMultipage) { + return; + } + + if (routerConfig.history === "hash") { + const hash = pageId ? `#${pageId}` : ""; + if (replace) { + const url = `${window.location.pathname}${window.location.search}${hash}`; + window.history.replaceState({ pageId }, "", url); + } else { + window.location.hash = hash; + } + return; + } + + const url = new URL(window.location.href); + if (pageId) { + url.searchParams.set(routerConfig.pageParam, pageId); + } else { + url.searchParams.delete(routerConfig.pageParam); + } + if (replace) { + window.history.replaceState({ pageId }, "", url); + } else { + window.history.pushState({ pageId }, "", url); + } +} + +const manifestCache = new Map(); + +function cloneManifestEntry(entry) { + const manifestClone = + typeof structuredClone === "function" + ? structuredClone(entry.manifest) + : JSON.parse(JSON.stringify(entry.manifest)); + const componentsClone = new Map(entry.components); + return { manifest: manifestClone, components: componentsClone }; +} + +async function fetchManifestForPage(pageId) { + const cacheKey = pageId || "__default__"; + if (manifestCache.has(cacheKey)) { + return cloneManifestEntry(manifestCache.get(cacheKey)); + } + + const url = withPageParam(routerConfig.manifestUrl, pageId); + const result = await loadManifest(url); + manifestCache.set(cacheKey, result); + return cloneManifestEntry(result); +} + +function replaceManifest(target, source) { + for (const key of Object.keys(target)) { + if (!(key in source)) { + delete target[key]; + } + } + for (const [key, value] of Object.entries(source)) { + target[key] = value; + } +} + +function updateRegistry(target, source) { + target.clear(); + source.forEach((entry, key) => { + target.set(key, entry); + }); +} + +function resolveWsUrl(path) { + if (!path) { + return null; + } + if (path.startsWith("ws://") || path.startsWith("wss://")) { + return path; + } + const scheme = window.location.protocol === "https:" ? "wss" : "ws"; + const suffix = path.startsWith("/") ? path : `/${path}`; + return `${scheme}://${window.location.host}${suffix}`; +} + +function setupRealtimeBridge(config) { + if (!config?.enabled) { + return null; + } + const url = resolveWsUrl(config.path); + if (!url) { + return null; + } + let socket; + let reconnectTimer = null; + + const connect = () => { + socket = new WebSocket(url); + window.__leRealtime = socket; + + socket.addEventListener("open", () => { + if (config.autoSubscribe && Array.isArray(config.channels)) { + for (const channelId of config.channels) { + socket.send(JSON.stringify({ action: "subscribe", channel: channelId })); + } + } + }); + + socket.addEventListener("message", (event) => { + try { + const data = JSON.parse(String(event.data)); + window.dispatchEvent( + new CustomEvent(REALTIME_EVENT, { + detail: { + channel: data.channel ?? null, + payload: data.payload ?? null, + }, + }), + ); + } catch (error) { + console.warn("Unable to parse realtime payload", error); + } + }); + + socket.addEventListener("close", () => { + reconnectTimer = window.setTimeout(connect, 2000); + }); + + socket.addEventListener("error", (error) => { + console.error("Realtime websocket error", error); + socket.close(); + }); + }; + + connect(); + + return () => { + if (reconnectTimer) { + window.clearTimeout(reconnectTimer); + reconnectTimer = null; + } + if (socket) { + socket.close(); + socket = null; + } + }; +} + +const initialPageId = currentPageFromLocation() || routerConfig.defaultPageId; +const initialManifestData = await fetchManifestForPage(initialPageId); + +mergeThemeFromManifest(initialManifestData.manifest); + +const manifestState = reactive(initialManifestData.manifest); +const registryState = new Map(initialManifestData.components); + +const layoutPlugin = createLayoutEnginePlugin(manifestState, registryState); +const teardownRealtime = setupRealtimeBridge(realtimeConfig); +if (teardownRealtime) { + window.addEventListener("beforeunload", () => teardownRealtime()); +} + +if (shellConfig.clientSetup) { + try { + // eslint-disable-next-line no-new-func + const setupFn = new Function( + "context", + shellConfig.clientSetup + ); + setupFn({ + config: shellConfig, + manifest: manifestState, + registry: registryState, + }); + } catch (error) { + console.error("clientSetup execution failed", error); + } +} + +function createHeaderComponent(template, context) { + if (!template) return null; + return defineComponent({ + name: "ShellHeader", + setup: context, + template, + }); +} + +function createFooterComponent(template, context) { + if (!template) return null; + return defineComponent({ + name: "ShellFooter", + setup: context, + template, + }); +} + +function createContentComponent(template, context) { + return defineComponent({ + name: "ShellContent", + components: { LayoutEngineShell }, + setup: context, + template, + }); +} + +const ShellApp = defineComponent({ + name: "LayoutEngineShellApp", + components: { + LayoutEngineShell, + }, + setup() { + const site = useSiteNavigation(manifestState); + const status = reactive({ + isLoading: false, + error: null, + }); + + const shellTitle = computed( + () => + manifestState.meta?.page?.title ?? + shellConfig.title ?? + DEFAULT_TITLE + ); + const shellSubtitle = computed( + () => manifestState.meta?.page?.description ?? manifestState.meta?.page?.tagline ?? "" + ); + + const headerTemplate = + shellConfig.ui?.headerSlot ?? DEFAULT_HEADER_TEMPLATE; + const footerTemplate = shellConfig.ui?.footerSlot ?? ""; + const navTemplate = shellConfig.ui?.navSlot ?? null; + const contentTemplate = + shellConfig.ui?.contentSlot ?? + (navTemplate + ? ` + + ` + : DEFAULT_CONTENT_TEMPLATE); + + const headerComponent = createHeaderComponent(headerTemplate, () => ({ + shellTitle, + shellSubtitle, + })); + const footerComponent = createFooterComponent(footerTemplate, () => ({})); + + let suppressHashChange = false; + + const handleNavigate = async (pageId, { pushHistory = true } = {}) => { + if (!pageId) return; + if (!routerConfig.enableMultipage) { + site.navigate(pageId); + if (pushHistory) { + updateLocation(pageId); + } + return; + } + status.isLoading = true; + try { + const { manifest, components } = await fetchManifestForPage(pageId); + replaceManifest(manifestState, manifest); + updateRegistry(registryState, components); + site.navigate(pageId); + mergeThemeFromManifest(manifest); + if (pushHistory) { + if (routerConfig.history === "hash") { + suppressHashChange = true; + } + updateLocation(pageId); + } + status.error = null; + } catch (error) { + console.error("Failed to load manifest for page", pageId, error); + status.error = "Failed to load dashboard page."; + } finally { + status.isLoading = false; + } + }; + + const contentComponent = createContentComponent(contentTemplate, () => ({ + site, + manifest: manifestState, + handleNavigate, + })); + + watch( + () => manifestState.meta, + () => mergeThemeFromManifest(manifestState), + { deep: true, immediate: true } + ); + + if (routerConfig.enableMultipage) { + const handler = async () => { + if (routerConfig.history === "hash" && suppressHashChange) { + suppressHashChange = false; + return; + } + const target = currentPageFromLocation() || routerConfig.defaultPageId; + if (target) { + await handleNavigate(target, { pushHistory: false }); + } + }; + if (routerConfig.history === "hash") { + window.addEventListener("hashchange", handler); + } else { + window.addEventListener("popstate", handler); + } + onBeforeUnmount(() => { + if (routerConfig.history === "hash") { + window.removeEventListener("hashchange", handler); + } else { + window.removeEventListener("popstate", handler); + } + }); + } + + onMounted(() => { + if (routerConfig.enableMultipage) { + const initial = currentPageFromLocation() || routerConfig.defaultPageId; + if (initial) { + updateLocation(initial, { replace: true }); + } + } + }); + + return { + headerComponent, + contentComponent, + footerComponent, + status, + }; + }, + template: ` +
+ +
+ +
+ +
Loading…
+
{{ status.error }}
+
+ `, +}); + +const app = createApp(ShellApp); +app.use(layoutPlugin); + +const mountEl = document.getElementById("le-app"); +if (!mountEl) { + throw new Error("Unable to find mount element '#le-app'"); +} +app.mount(mountEl); diff --git a/pkgs/experimental/layout_engine_atoms/src/layout_engine_atoms/runtime/vue/assets/swarma-vue/style.css b/pkgs/experimental/layout_engine_atoms/src/layout_engine_atoms/runtime/vue/assets/swarma-vue/style.css new file mode 100644 index 0000000000..ad11c82f73 --- /dev/null +++ b/pkgs/experimental/layout_engine_atoms/src/layout_engine_atoms/runtime/vue/assets/swarma-vue/style.css @@ -0,0 +1,7 @@ +.image-viewer[data-v-0fbe72d7]{position:relative;width:100%;max-width:600px;margin:auto;overflow:hidden;border:1px solid var(--viewer-border-color)}.image-container[data-v-0fbe72d7]{width:100%;height:auto;display:flex;justify-content:center;align-items:center}.image-container img[data-v-0fbe72d7]{width:100%;height:auto;transition:transform .3s ease}.loading-indicator[data-v-0fbe72d7]{position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);color:var(--loading-color)}.control-button[data-v-0fbe72d7]{margin:10px;padding:5px 10px;background-color:var(--button-bg-color);border:none;color:var(--button-text-color);cursor:pointer}.accordion[data-v-062ad5b7]{border:var(--accordion-border, 1px solid #ccc);margin:var(--accordion-margin, 10px 0);border-radius:var(--accordion-border-radius, 4px)}.accordion-header[data-v-062ad5b7]{background-color:var(--accordion-header-bg, #f1f1f1);color:var(--accordion-header-color, #333);padding:var(--accordion-header-padding, 10px);width:100%;text-align:left;cursor:pointer;font-size:var(--accordion-header-font-size, 16px);border:none;outline:none;transition:background-color .3s ease}.accordion-header.hovered[data-v-062ad5b7]{background-color:var(--accordion-header-hover-bg, #ddd)}.accordion-content[data-v-062ad5b7]{padding:var(--accordion-content-padding, 10px);background-color:var(--accordion-content-bg, #fff);border-top:var(--accordion-content-border-top, 1px solid #ccc)}.actionable-list[data-v-4a589b9f]{list-style-type:none;padding:0;margin:0}.actionable-list-item[data-v-4a589b9f]{display:flex;justify-content:space-between;align-items:center;padding:var(--actionable-list-item-padding, 10px);border-bottom:var(--actionable-list-item-border-bottom, 1px solid #eee);transition:background-color .3s ease}.actionable-list-item.hovered[data-v-4a589b9f]{background-color:var(--actionable-list-item-hover-bg, #f5f5f5)}.actionable-list-item.disabled[data-v-4a589b9f]{opacity:.6}.actionable-list-item.loading[data-v-4a589b9f]{background-color:var(--actionable-list-item-loading-bg, #f0f0f0)}.action-button[data-v-4a589b9f]{background-color:var(--action-button-bg, #007bff);color:var(--action-button-color, #fff);border:none;padding:var(--action-button-padding, 5px 10px);cursor:pointer;transition:background-color .3s ease}.action-button[data-v-4a589b9f]:hover{background-color:var(--action-button-hover-bg, #0056b3)}.loading-spinner[data-v-4a589b9f]{font-style:italic;color:var(--loading-spinner-color, #999)}[data-v-a4d57fae]:root{--loading-bg-color: #f5f5f5;--success-bg-color: #d4edda;--success-text-color: #155724;--error-bg-color: #f8d7da;--error-text-color: #721c24;--border-radius: 4px;--padding: 10px}.activity-indicator[data-v-a4d57fae]{padding:var(--padding);border-radius:var(--border-radius);display:flex;align-items:center;justify-content:center;width:100%;height:40px}.loading[data-v-a4d57fae]{background-color:var(--loading-bg-color)}.success[data-v-a4d57fae]{background-color:var(--success-bg-color);color:var(--success-text-color)}.error[data-v-a4d57fae]{background-color:var(--error-bg-color);color:var(--error-text-color)}.spinner[data-v-a4d57fae]{border:4px solid rgba(0,0,0,.1);border-left-color:#000;border-radius:50%;width:24px;height:24px;animation:spin-a4d57fae 1s linear infinite}@keyframes spin-a4d57fae{to{transform:rotate(360deg)}}@media (max-width: 576px){.activity-indicator[data-v-a4d57fae]{height:30px}}@media (min-width: 577px) and (max-width: 768px){.activity-indicator[data-v-a4d57fae]{height:35px}}@media (min-width: 769px) and (max-width: 992px){.activity-indicator[data-v-a4d57fae]{height:40px}}@media (min-width: 993px){.activity-indicator[data-v-a4d57fae]{height:40px}}.event[data-v-acf13d1f]{margin-bottom:10px;padding:10px;border:1px solid #ccc}.event h3[data-v-acf13d1f]{margin:0}.event p[data-v-acf13d1f]{margin:5px 0}button[data-v-acf13d1f]{margin-right:5px}.audio-player[data-v-f1cd7384]{display:flex;align-items:center;gap:10px;padding:10px;background-color:var(--player-bg-color);border-radius:5px}.control-button[data-v-f1cd7384]{background-color:var(--button-bg-color);color:var(--button-text-color);border:none;padding:5px 10px;cursor:pointer;border-radius:3px}.volume-control[data-v-f1cd7384]{width:100px}.audio-player-advanced[data-v-4192c021]{display:flex;align-items:center;gap:10px;padding:10px;background-color:var(--player-bg-color);border-radius:5px}.control-button[data-v-4192c021]{background-color:var(--button-bg-color);color:var(--button-text-color);border:none;padding:5px 10px;cursor:pointer;border-radius:3px}.volume-control[data-v-4192c021],.seek-bar[data-v-4192c021]{width:100px}select[data-v-4192c021]{padding:5px;border-radius:3px;border:1px solid var(--button-bg-color)}.audio-player-advanced[data-v-81b5aab6]{display:flex;align-items:center;gap:10px;padding:10px;background-color:var(--player-bg-color);border-radius:5px}.control-button[data-v-81b5aab6]{background-color:var(--button-bg-color);color:var(--button-text-color);border:none;padding:5px 10px;cursor:pointer;border-radius:3px}.volume-control[data-v-81b5aab6],.seek-bar[data-v-81b5aab6]{width:100px}select[data-v-81b5aab6]{padding:5px;border-radius:3px;border:1px solid var(--button-bg-color)}.avatar-container[data-v-703ea8e3]{display:inline-block;width:var(--avatar-size, 50px);height:var(--avatar-size, 50px);border-radius:50%;overflow:hidden}.avatar[data-v-703ea8e3]{width:100%;height:100%;background-size:cover;background-position:center;background-color:var(--avatar-bg-color, #ccc)}.placeholder[data-v-703ea8e3]{display:flex;justify-content:center;align-items:center;background-color:var(--avatar-placeholder-bg-color, #eee);color:var(--avatar-placeholder-text-color, #333);font-size:var(--avatar-font-size, 20px)}.badge[data-v-373bc344]{display:inline-flex;align-items:center;justify-content:center;padding:.25em .5em;border-radius:var(--badge-border-radius, .25em);font-size:var(--badge-font-size, .875rem);font-weight:var(--badge-font-weight, 600)}.default[data-v-373bc344]{background-color:var(--default-background, #e0e0e0);color:var(--default-text, #606060)}.notification[data-v-373bc344]{background-color:var(--notification-background, #007bff);color:var(--notification-text, #ffffff)}.status-indicator[data-v-373bc344]{background-color:var(--status-background, #28a745);color:var(--status-text, #ffffff)}.badge[data-v-42553945]{display:inline-flex;align-items:center;justify-content:center;padding:.25em .5em;border-radius:var(--badge-border-radius, .25em);font-size:var(--badge-font-size, .875rem);font-weight:var(--badge-font-weight, 600);background-color:var(--badge-background, #e0e0e0);color:var(--badge-text, #606060)}.zero[data-v-42553945]{background-color:var(--zero-background, #e0e0e0);color:var(--zero-text, #606060)}.active[data-v-42553945]{background-color:var(--active-background, #007bff);color:var(--active-text, #ffffff)}.overflow[data-v-42553945]{font-size:var(--overflow-font-size, .75rem);color:var(--overflow-text, #ff0000)}.battery-container[data-v-b2d4d309]{display:flex;align-items:center;width:var(--battery-width, 200px);height:var(--battery-height, 20px);border:2px solid var(--battery-border-color, #000);border-radius:var(--battery-border-radius, 5px);position:relative;background-color:var(--battery-bg-color, #e0e0e0)}.battery[data-v-b2d4d309]{height:100%;transition:width .3s ease}.charging[data-v-b2d4d309]{background-color:var(--charging-bg-color, #00ff00)}.full[data-v-b2d4d309]{background-color:var(--full-bg-color, #007bff)}.low[data-v-b2d4d309]{background-color:var(--low-bg-color, #ffff00)}.critical[data-v-b2d4d309]{background-color:var(--critical-bg-color, #ff0000)}.charging-icon[data-v-b2d4d309]{position:absolute;right:5px;font-size:var(--charging-icon-size, 16px);color:var(--charging-icon-color, #ff0)}.bet-slider[data-v-2449a36a]{display:flex;flex-direction:column;align-items:center;gap:var(--slider-gap)}.slider[data-v-2449a36a]{width:100%;margin:var(--slider-margin)}.bet-input[data-v-2449a36a]{width:var(--input-width);padding:var(--input-padding);font-size:var(--input-font-size)}.feedback[data-v-2449a36a]{color:var(--feedback-color);font-size:var(--feedback-font-size)}.bottom-navigation-bar[data-v-6c91e3f1]{display:flex;justify-content:space-around;background-color:var(--nav-bg, #fff);padding:10px 0}.nav-items[data-v-6c91e3f1]{list-style:none;display:flex;width:100%;justify-content:space-around}.nav-items li[data-v-6c91e3f1]{padding:10px;cursor:pointer;transition:background-color .3s}.nav-items li.selected[data-v-6c91e3f1]{background-color:var(--nav-selected-bg, #007bff);color:var(--nav-selected-color, #fff)}.nav-items li[data-v-6c91e3f1]:hover:not(.disabled){background-color:var(--nav-hover-bg, #e0e0e0)}.nav-items li.disabled[data-v-6c91e3f1]{cursor:not-allowed;color:var(--nav-disabled-color, #ccc)}.breadcrumb[data-v-00e68632]{display:flex;align-items:center;font-size:var(--font-size, 16px);color:var(--breadcrumb-color, #333)}.breadcrumb-list[data-v-00e68632]{list-style:none;display:flex;padding:0}.breadcrumb-item[data-v-00e68632]{margin-right:var(--breadcrumb-separator, 10px);position:relative}.breadcrumb-link[data-v-00e68632]{cursor:pointer;color:var(--breadcrumb-link-color, #007bff)}.dropdown button[data-v-00e68632]{background:none;border:none;cursor:pointer;color:var(--breadcrumb-link-color, #007bff)}.dropdown-menu[data-v-00e68632]{position:absolute;top:100%;left:0;background-color:var(--dropdown-bg, #fff);border:1px solid var(--dropdown-border-color, #ccc);list-style:none;padding:5px 0;display:block}.dropdown-menu li[data-v-00e68632]{padding:5px 10px;cursor:pointer}.dropdown-menu li[data-v-00e68632]:hover{background-color:var(--dropdown-hover-bg, #f1f1f1)}.breadcrumbs[data-v-de61de6d]{display:flex;align-items:center;font-size:var(--font-size, 16px);color:var(--breadcrumb-color, #333)}.breadcrumbs-list[data-v-de61de6d]{list-style:none;display:flex;padding:0}.breadcrumbs-item[data-v-de61de6d]{margin-right:var(--breadcrumb-separator, 10px)}.breadcrumbs-link[data-v-de61de6d]{cursor:pointer;color:var(--breadcrumb-link-color, #007bff)}.breadcrumbs-item.active .breadcrumbs-link[data-v-de61de6d]{font-weight:700;color:var(--active-breadcrumb-color, #000)}.brush-tool[data-v-aa617421]{display:flex;flex-direction:column;gap:10px}.brush-tool.active[data-v-aa617421]{border:2px solid var(--brush-tool-active-border)}.brush-settings[data-v-aa617421]{display:flex;flex-direction:column;gap:5px}.button[data-v-3457f604]{padding:var(--button-padding, 10px 20px);font-size:var(--button-font-size, 16px);border-radius:var(--button-border-radius, 4px);border:none;cursor:pointer;transition:background-color .3s}.primary[data-v-3457f604]{background-color:var(--button-primary-bg, #007bff);color:var(--button-primary-color, #fff)}.secondary[data-v-3457f604]{background-color:var(--button-secondary-bg, #6c757d);color:var(--button-secondary-color, #fff)}.disabled[data-v-3457f604]{background-color:var(--button-disabled-bg, #e0e0e0);color:var(--button-disabled-color, #9e9e9e);cursor:not-allowed}.hover[data-v-3457f604]:not(.disabled){background-color:var(--button-hover-bg, #0056b3)}.active[data-v-3457f604]:not(.disabled){background-color:var(--button-active-bg, #004085)}.calendar-view[data-v-f2b44818]{display:flex;flex-direction:column;--calendar-bg-color: #fff;--calendar-header-color: #333;--calendar-border-color: #ccc}.calendar-header[data-v-f2b44818]{display:flex;justify-content:space-between;align-items:center;background-color:var(--calendar-bg-color);color:var(--calendar-header-color);padding:1rem;border-bottom:1px solid var(--calendar-border-color)}.calendar-content[data-v-f2b44818]{flex-grow:1}.call-button[data-v-cbb8bf14]{padding:var(--button-padding);font-size:var(--button-font-size);background-color:var(--button-bg-color);color:var(--button-text-color);border:none;border-radius:var(--button-border-radius);cursor:pointer;transition:background-color .3s,transform .1s}.call-button[data-v-cbb8bf14]:hover:not(:disabled){background-color:var(--button-hover-bg-color)}.call-button[data-v-cbb8bf14]:disabled{background-color:var(--button-disabled-bg-color);cursor:not-allowed}.call-button[data-v-cbb8bf14]:active:not(:disabled){transform:scale(.95)}.canvas-container[data-v-ca98a860]{position:relative;width:100%;height:100%}canvas[data-v-ca98a860]{width:100%;height:100%;border:1px solid var(--canvas-border-color)}.controls[data-v-ca98a860]{position:absolute;top:10px;left:10px;display:flex;gap:10px}[data-v-e6fd93f9]:root{--captcha-bg: #f8f9fa;--captcha-text-color: #212529;--captcha-error-bg: #f8d7da;--captcha-error-text-color: #721c24;--captcha-solved-bg: #d4edda;--captcha-solved-text-color: #155724;--captcha-border-radius: 4px;--captcha-padding: .5rem}.captcha-container[data-v-e6fd93f9]{display:flex;flex-direction:column;align-items:center}.captcha[data-v-e6fd93f9]{background-color:var(--captcha-bg);color:var(--captcha-text-color);padding:var(--captcha-padding);border-radius:var(--captcha-border-radius);margin-bottom:1rem}.captcha--error[data-v-e6fd93f9]{background-color:var(--captcha-error-bg);color:var(--captcha-error-text-color)}.captcha--solved[data-v-e6fd93f9]{background-color:var(--captcha-solved-bg);color:var(--captcha-solved-text-color)}button[data-v-e6fd93f9]{padding:.5rem 1rem;font-size:1rem;border:none;border-radius:var(--captcha-border-radius);cursor:pointer;background-color:var(--captcha-bg);color:var(--captcha-text-color)}button[data-v-e6fd93f9]:disabled{opacity:.6;cursor:not-allowed}@media (max-width: 576px){.captcha[data-v-e6fd93f9]{font-size:.875rem}}@media (min-width: 577px) and (max-width: 768px){.captcha[data-v-e6fd93f9]{font-size:.9375rem}}@media (min-width: 769px) and (max-width: 992px){.captcha[data-v-e6fd93f9]{font-size:1rem}}@media (min-width: 993px){.captcha[data-v-e6fd93f9]{font-size:1.125rem}}.card-actions[data-v-10a84f95]{display:flex;gap:var(--spacing-md)}button[data-v-10a84f95]{padding:var(--spacing-sm) var(--spacing-md);border:none;border-radius:var(--border-radius);background-color:var(--color-primary);color:var(--color-text-light);cursor:pointer;transition:background-color .3s}button.hovered[data-v-10a84f95]{background-color:var(--color-primary-hover)}button.disabled[data-v-10a84f95]{background-color:var(--color-disabled);cursor:not-allowed}.card-badge[data-v-f22b6cfb]{display:inline-block;padding:var(--spacing-xs) var(--spacing-sm);border-radius:var(--border-radius);font-size:var(--font-size-sm);transition:background-color .3s}.badge-default[data-v-f22b6cfb]{background-color:var(--color-gray-light);color:var(--color-text-dark)}.badge-active[data-v-f22b6cfb]{background-color:var(--color-success);color:var(--color-text-light)}.badge-inactive[data-v-f22b6cfb]{background-color:var(--color-warning);color:var(--color-text-dark)}.badge-hovered[data-v-f22b6cfb]{background-color:var(--color-primary-hover)}.card-body[data-v-b7b4641a]{padding:var(--spacing-md);background-color:var(--color-background)}.card-body__content[data-v-b7b4641a]{max-height:none;overflow:auto;transition:max-height .3s ease}.card-body__content--collapsed[data-v-b7b4641a]{max-height:var(--collapsed-height);overflow:hidden}.card-body__toggle[data-v-b7b4641a]{margin-top:var(--spacing-sm);background-color:var(--color-primary);color:var(--color-text-light);border:none;padding:var(--spacing-sm) var(--spacing-md);cursor:pointer;border-radius:var(--border-radius)}.card-body__toggle[data-v-b7b4641a]:hover{background-color:var(--color-primary-hover)}.card-footer[data-v-7d9792f3]{padding:var(--spacing-md);background-color:var(--color-background);display:flex;gap:var(--spacing-sm);border-top:1px solid var(--color-border)}.card-footer button[data-v-7d9792f3],.card-footer a[data-v-7d9792f3]{background-color:var(--color-primary);color:var(--color-text-light);padding:var(--spacing-sm) var(--spacing-md);border:none;border-radius:var(--border-radius);text-decoration:none;cursor:pointer}.card-footer button[data-v-7d9792f3]:hover,.card-footer a[data-v-7d9792f3]:hover{background-color:var(--color-primary-hover)}.card-header[data-v-4b83e7e6]{display:flex;align-items:center;padding:var(--spacing-md);background-color:var(--color-background);border-bottom:var(--border-width) solid var(--color-border)}.card-header__image[data-v-4b83e7e6]{width:var(--image-size);height:var(--image-size);margin-right:var(--spacing-sm);border-radius:var(--border-radius);object-fit:cover}.card-header__icon[data-v-4b83e7e6]{font-size:var(--icon-size);margin-right:var(--spacing-sm)}.card-header__text[data-v-4b83e7e6]{flex-grow:1}.card-header__title[data-v-4b83e7e6]{font-size:var(--font-size-lg);margin:0}.card-header__subtitle[data-v-4b83e7e6]{font-size:var(--font-size-md);color:var(--color-text-secondary);margin:0}.card-image[data-v-62e35ada]{background-size:cover;background-position:center;position:relative;width:100%;padding-top:56.25%;border-radius:var(--border-radius);overflow:hidden}.caption[data-v-62e35ada]{position:absolute;bottom:0;width:100%;background-color:#00000080;color:var(--color-text-light);padding:var(--spacing-sm);text-align:center}.overlay[data-v-62e35ada]{position:absolute;top:0;left:0;width:100%;height:100%;background-color:#0000004d;color:var(--color-text-light);display:flex;align-items:center;justify-content:center;font-size:var(--font-size-lg)}.cardbased-list[data-v-342a232d]{display:flex;flex-wrap:wrap;gap:var(--card-gap, 10px)}.card[data-v-342a232d]{background-color:var(--card-bg, #fff);border:var(--card-border, 1px solid #ddd);padding:var(--card-padding, 15px);transition:transform .3s ease,background-color .3s ease;cursor:pointer}.card.hovered[data-v-342a232d]{background-color:var(--card-hover-bg, #f0f0f0)}.card.selected[data-v-342a232d]{border-color:var(--card-selected-border-color, #007bff)}.card.disabled[data-v-342a232d]{opacity:.6;cursor:not-allowed}.card-content h3[data-v-342a232d]{margin:0;font-size:var(--card-title-font-size, 16px)}.card-content p[data-v-342a232d]{margin:5px 0 0;font-size:var(--card-description-font-size, 14px)}.carousel[data-v-0a83c48d]{position:relative;overflow:hidden;width:100%;background-color:var(--carousel-bg-color)}.carousel-inner[data-v-0a83c48d]{display:flex;transition:transform .5s ease}.carousel-item[data-v-0a83c48d]{min-width:100%;transition:opacity .5s ease;opacity:0;position:absolute;top:0;left:0}.carousel-item.active[data-v-0a83c48d]{opacity:1;position:relative}.carousel-control[data-v-0a83c48d]{position:absolute;top:50%;transform:translateY(-50%);background-color:var(--control-bg-color);color:var(--control-text-color);border:none;font-size:2rem;cursor:pointer;padding:.5rem}.carousel-control.prev[data-v-0a83c48d]{left:10px}.carousel-control.next[data-v-0a83c48d]{right:10px}.chat-bubble[data-v-5ed7d014]{padding:var(--chat-bubble-padding, 10px);border-radius:var(--chat-bubble-radius, 8px);background-color:var(--chat-bubble-bg-color, #f1f1f1);color:var(--chat-bubble-text-color, #333);transition:background-color .3s}.chat-bubble.read[data-v-5ed7d014]{background-color:var(--chat-bubble-read-bg-color, #e0e0e0)}.chat-bubble.unread[data-v-5ed7d014]{background-color:var(--chat-bubble-unread-bg-color, #cce5ff)}.chat-bubble.hover[data-v-5ed7d014]{background-color:var(--chat-bubble-hover-bg-color, #b3d9ff)}.chat-bubble.active[data-v-5ed7d014]{background-color:var(--chat-bubble-active-bg-color, #99ccff)}.checklist[data-v-daf9afeb]{display:flex;flex-direction:column;gap:var(--checklist-gap, 10px)}.checklist-item[data-v-daf9afeb]{display:flex;align-items:center}input[type=checkbox][data-v-daf9afeb]{margin-right:var(--checkbox-margin-right, 8px)}input[type=checkbox]:checked+label[data-v-daf9afeb]{font-weight:var(--checked-label-weight, bold)}input[type=checkbox]:indeterminate+label[data-v-daf9afeb]{color:var(--indeterminate-label-color, #666)}input[type=checkbox]:disabled+label[data-v-daf9afeb]{color:var(--disabled-label-color, #ccc);cursor:not-allowed}[data-v-7926a921]:root{--checkbox-border: #adb5bd;--checkbox-checked-bg: #007bff;--checkbox-disabled-bg: #e9ecef;--checkbox-disabled-border: #ced4da;--checkbox-label-color: #212529}.checkbox-container[data-v-7926a921]{display:flex;align-items:center}input[type=checkbox][data-v-7926a921]{-webkit-appearance:none;-moz-appearance:none;appearance:none;width:1rem;height:1rem;border:2px solid var(--checkbox-border);border-radius:.25rem;margin-right:.5rem;cursor:pointer;transition:background-color .2s}input[type=checkbox][data-v-7926a921]:checked{background-color:var(--checkbox-checked-bg)}input[type=checkbox][data-v-7926a921]:disabled{background-color:var(--checkbox-disabled-bg);border-color:var(--checkbox-disabled-border)}.checkbox--disabled[data-v-7926a921]{color:var(--checkbox-label-color);opacity:.6}@media (max-width: 576px){label[data-v-7926a921]{font-size:.875rem}}@media (min-width: 577px) and (max-width: 768px){label[data-v-7926a921]{font-size:.9375rem}}@media (min-width: 769px) and (max-width: 992px){label[data-v-7926a921]{font-size:1rem}}@media (min-width: 993px){label[data-v-7926a921]{font-size:1.125rem}}.chip-container[data-v-8a744eae]{display:flex;flex-wrap:wrap;gap:var(--chip-gap, 8px)}.chip[data-v-8a744eae]{display:inline-flex;align-items:center;padding:var(--chip-padding, 8px 12px);border-radius:var(--chip-radius, 16px);background-color:var(--chip-bg-color, #e0e0e0);color:var(--chip-text-color, #333);cursor:pointer;transition:background-color .3s}.chip.selectable[data-v-8a744eae]:hover{background-color:var(--chip-hover-bg-color, #d0d0d0)}.chip[aria-pressed=true][data-v-8a744eae]{background-color:var(--chip-selected-bg-color, #b0b0b0)}.remove-button[data-v-8a744eae]{margin-left:var(--chip-remove-margin, 8px);background:none;border:none;color:var(--chip-remove-color, #666);cursor:pointer}.remove-button[data-v-8a744eae]:hover{color:var(--chip-remove-hover-color, #333)}.collapsible-menu-list[data-v-e135c15d]{list-style:none;padding:0;margin:0}.menu-item[data-v-e135c15d]{margin-bottom:var(--menu-item-margin-bottom, 10px)}.menu-button[data-v-e135c15d]{background:none;border:none;text-align:left;width:100%;padding:var(--menu-button-padding, 10px);cursor:pointer}.menu-item.expanded .menu-button[data-v-e135c15d]{font-weight:var(--expanded-font-weight, bold)}.menu-item.active .menu-button[data-v-e135c15d]{background-color:var(--active-background-color, #f0f0f0)}.submenu[data-v-e135c15d]{list-style:none;padding-left:var(--submenu-padding-left, 20px);margin:0}.color-picker[data-v-bef9cfe2]{display:flex;flex-direction:column;gap:10px}.color-picker.active .color-button[data-v-bef9cfe2]{border:2px solid var(--color-picker-active-border)}.color-settings[data-v-bef9cfe2]{display:flex;flex-direction:column;gap:5px}.color-button[data-v-bef9cfe2]{cursor:pointer}.color-history[data-v-bef9cfe2]{display:flex;gap:5px}.color-swatch[data-v-bef9cfe2]{width:20px;height:20px;cursor:pointer}.column-visibility-toggle[data-v-b2e7ed29]{padding:16px;background-color:var(--toggle-bg)}button[data-v-b2e7ed29]{padding:8px;margin:4px 0;border:none;background-color:var(--button-bg);color:var(--button-color);transition:background-color .3s ease}button[aria-pressed=true][data-v-b2e7ed29]{background-color:var(--button-active-bg)}.command-palette[data-v-a21814e7]{position:fixed;top:50%;left:50%;transform:translate(-50%,-50%);width:90%;max-width:600px;background-color:var(--palette-bg-color, #fff);box-shadow:0 0 10px #0000001a;border-radius:var(--palette-radius, 8px);padding:20px;z-index:1000}.command-palette-content[data-v-a21814e7]{display:flex;flex-direction:column;gap:10px}.command-palette-input[data-v-a21814e7]{width:100%;padding:10px;border:1px solid var(--input-border-color, #ccc);border-radius:var(--input-radius, 4px)}.command-list[data-v-a21814e7]{list-style:none;padding:0;margin:0;max-height:200px;overflow-y:auto}.command-list li[data-v-a21814e7]{padding:10px;cursor:pointer;transition:background-color .3s}.command-list li.active[data-v-a21814e7]{background-color:var(--command-active-bg-color, #e0e0e0)}@media (max-width: 600px){.community-cards[data-v-5f3f241e]{display:flex;justify-content:space-around}}@media (min-width: 601px) and (max-width: 768px){.community-cards[data-v-5f3f241e]{display:flex;justify-content:space-between}}@media (min-width: 769px) and (max-width: 1024px){.community-cards[data-v-5f3f241e]{display:flex;justify-content:space-evenly}}@media (min-width: 1025px){.community-cards[data-v-5f3f241e]{display:grid;grid-template-columns:repeat(auto-fill,minmax(100px,1fr));gap:var(--card-gap, 10px)}}.card[data-v-5f3f241e]{transition:transform .3s ease-in-out}.card.dealt[data-v-5f3f241e]{transform:rotateY(180deg)}.card.revealed[data-v-5f3f241e]{transform:rotateY(0)}.card-background[data-v-5f3f241e]{fill:var(--card-background-color, #ffffff);stroke:var(--card-border-color, #000000);stroke-width:2}.card-text[data-v-5f3f241e]{fill:var(--card-text-color, #000000);font-size:var(--card-text-size, 24px)}.contextual-list[data-v-85d407db]{padding:0}.list-item[data-v-85d407db]{display:flex;justify-content:space-between;margin-bottom:var(--list-item-margin-bottom, 10px);padding:var(--list-item-padding, 10px);background-color:var(--list-item-background, #fff);border:var(--list-item-border, 1px solid #ccc)}.list-item.actionTriggered[data-v-85d407db]{background-color:var(--action-triggered-background, #e0f7fa)}.list-item.dismissed[data-v-85d407db]{display:none}.contextual-navigation[data-v-12a7fc2e]{position:relative;font-size:var(--font-size, 16px)}.contextual-toggle[data-v-12a7fc2e]{background-color:var(--toggle-bg-color, #007bff);color:var(--toggle-text-color, #fff);border:none;padding:10px;cursor:pointer;font-size:var(--button-font-size, 16px)}.contextual-menu[data-v-12a7fc2e]{position:absolute;top:100%;left:0;background-color:var(--menu-bg-color, #fff);border:1px solid #ccc;box-shadow:0 2px 10px #0000001a;z-index:1000}.contextual-menu ul[data-v-12a7fc2e]{list-style:none;margin:0;padding:0}.contextual-menu li[data-v-12a7fc2e]{padding:10px}.contextual-menu a[data-v-12a7fc2e]{color:var(--menu-link-color, #007bff);text-decoration:none}.contextual-menu a[data-v-12a7fc2e]:hover{text-decoration:underline}.countdown-timer[data-v-c87bdf2c]{display:flex;align-items:center;font-size:var(--timer-font-size, 24px);color:var(--timer-text-color, #000)}.running[data-v-c87bdf2c]{color:var(--running-color, #007bff)}.paused[data-v-c87bdf2c]{color:var(--paused-color, #ff9900)}.completed[data-v-c87bdf2c]{color:var(--completed-color, #28a745)}button[data-v-c87bdf2c]{margin-left:10px;padding:var(--button-padding, 5px 10px);background-color:var(--button-bg-color, #f0f0f0);border:none;border-radius:var(--button-border-radius, 5px);cursor:pointer}.dark-mode-toggle[data-v-744ba664]{background-color:var(--toggle-bg-color, #f0f0f0);border:none;border-radius:var(--toggle-radius, 50%);padding:10px;cursor:pointer;transition:background-color .3s}.dark-mode-toggle[data-v-744ba664]:focus{outline:2px solid var(--toggle-focus-color, #000)}.sr-only[data-v-744ba664]{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);border:0}.data-export-button[data-v-0af20d1b]{--button-bg: #28a745;--button-color: #ffffff;--button-disabled-bg: #d6d6d6;--button-disabled-color: #8a8a8a;--loading-color: #007bff}button[data-v-0af20d1b]{background-color:var(--button-bg);color:var(--button-color);border:none;padding:10px 20px;margin:5px;cursor:pointer}button[data-v-0af20d1b]:disabled{background-color:var(--button-disabled-bg);color:var(--button-disabled-color);cursor:not-allowed}.loading-indicator[data-v-0af20d1b]{color:var(--loading-color);margin-top:10px}.data-filter-panel[data-v-2739f835]{--panel-bg: #f4f4f9;--panel-border: #ccc;--panel-text-color: #333;--button-bg: #007bff;--button-color: #ffffff;--button-disabled-bg: #d6d6d6;--button-disabled-color: #8a8a8a}.data-filter-panel[data-v-2739f835]{padding:16px;background-color:var(--panel-bg);border:1px solid var(--panel-border);color:var(--panel-text-color)}button[data-v-2739f835]{background-color:var(--button-bg);color:var(--button-color);border:none;padding:8px 16px;cursor:pointer;margin-top:8px}button[data-v-2739f835]:disabled{background-color:var(--button-disabled-bg);color:var(--button-disabled-color);cursor:not-allowed}.filters[data-v-2739f835]{margin-top:16px}.filter[data-v-2739f835]{margin-bottom:12px}.data-grid[data-v-804b97db]{overflow-x:auto}.search-bar[data-v-804b97db]{margin-bottom:var(--search-bar-margin-bottom, 10px)}table[data-v-804b97db]{width:100%;border-collapse:collapse}th[data-v-804b97db],td[data-v-804b97db]{padding:var(--cell-padding, 10px);border:var(--cell-border, 1px solid #ccc);text-align:left}.pagination[data-v-804b97db]{display:flex;justify-content:space-between;margin-top:var(--pagination-margin-top, 10px)}.data-import-dialog[data-v-520df743]{padding:16px;background-color:var(--dialog-bg)}input[type=file][data-v-520df743]{margin-bottom:12px}button[data-v-520df743]{padding:8px 16px;background-color:var(--button-bg);color:var(--button-color);border:none;cursor:pointer;transition:background-color .3s ease}button[data-v-520df743]:disabled{background-color:var(--button-disabled-bg);cursor:not-allowed}.progress-indicator[data-v-520df743],.success-message[data-v-520df743],.error-message[data-v-520df743]{margin-top:12px}.data-summary[data-v-e55471fe]{padding:16px;border:1px solid var(--summary-border-color);border-radius:8px;background-color:var(--summary-bg)}.error-message[data-v-e55471fe]{color:var(--error-color);font-weight:700}.data-table[data-v-c061f510]{--table-width: 100%;--table-border-color: #ddd;--table-header-bg: #f9f9f9;--table-header-text-color: #333;--table-row-hover-bg: #f1f1f1}table[data-v-c061f510]{width:var(--table-width);border-collapse:collapse}th[data-v-c061f510],td[data-v-c061f510]{padding:8px;border:1px solid var(--table-border-color)}th[data-v-c061f510]{background-color:var(--table-header-bg);color:var(--table-header-text-color)}tr[data-v-c061f510]:hover{background-color:var(--table-row-hover-bg)}.pagination-controls[data-v-c061f510]{margin-top:16px;display:flex;justify-content:space-between}[data-v-5121df4f]:root{--date-time-picker-border: #adb5bd;--date-time-picker-disabled-border: #ced4da;--date-time-picker-bg: #f8f9fa}.date-time-picker-container[data-v-5121df4f]{display:flex;align-items:center;gap:.5rem}input[type=date][data-v-5121df4f],input[type=time][data-v-5121df4f]{border:1px solid var(--date-time-picker-border);border-radius:.25rem;padding:.5rem;background-color:var(--date-time-picker-bg);cursor:pointer}input[type=date][data-v-5121df4f]:disabled,input[type=time][data-v-5121df4f]:disabled{border-color:var(--date-time-picker-disabled-border);cursor:not-allowed}@media (max-width: 576px){input[type=date][data-v-5121df4f],input[type=time][data-v-5121df4f]{width:100%;font-size:.875rem}}@media (min-width: 577px) and (max-width: 768px){input[type=date][data-v-5121df4f],input[type=time][data-v-5121df4f]{width:95%;font-size:.9375rem}}@media (min-width: 769px) and (max-width: 992px){input[type=date][data-v-5121df4f],input[type=time][data-v-5121df4f]{width:90%;font-size:1rem}}@media (min-width: 993px){input[type=date][data-v-5121df4f],input[type=time][data-v-5121df4f]{width:85%;font-size:1.125rem}}[data-v-e00e68cd]:root{--date-picker-border: #adb5bd;--date-picker-disabled-border: #ced4da;--date-picker-bg: #f8f9fa}.date-picker-container[data-v-e00e68cd]{display:flex;align-items:center;gap:.5rem}input[type=date][data-v-e00e68cd],input[type=time][data-v-e00e68cd]{border:1px solid var(--date-picker-border);border-radius:.25rem;padding:.5rem;background-color:var(--date-picker-bg);cursor:pointer}input[type=date][data-v-e00e68cd]:disabled,input[type=time][data-v-e00e68cd]:disabled{border-color:var(--date-picker-disabled-border);cursor:not-allowed}@media (max-width: 576px){input[type=date][data-v-e00e68cd],input[type=time][data-v-e00e68cd]{width:100%;font-size:.875rem}}@media (min-width: 577px) and (max-width: 768px){input[type=date][data-v-e00e68cd],input[type=time][data-v-e00e68cd]{width:95%;font-size:.9375rem}}@media (min-width: 769px) and (max-width: 992px){input[type=date][data-v-e00e68cd],input[type=time][data-v-e00e68cd]{width:90%;font-size:1rem}}@media (min-width: 993px){input[type=date][data-v-e00e68cd],input[type=time][data-v-e00e68cd]{width:85%;font-size:1.125rem}}.dealer-button[data-v-66f3c7ea]{width:var(--button-size);height:var(--button-size);border-radius:50%;background:var(--button-background);display:flex;align-items:center;justify-content:center;box-shadow:var(--button-shadow);transition:transform var(--transition-duration),background var(--transition-duration)}.dealer-button[data-v-66f3c7ea]:hover{background:var(--button-hover-background)}.default[data-v-66f3c7ea]{animation:none}.moving[data-v-66f3c7ea]{animation:move-66f3c7ea var(--move-duration) ease-in-out infinite alternate}.hovered[data-v-66f3c7ea]{background:var(--button-hover-background)}@keyframes move-66f3c7ea{0%{transform:translateY(0)}to{transform:translateY(-10px)}}.deck[data-v-4591d50a]{display:flex;flex-direction:column;position:relative;width:var(--deck-width, 300px);height:var(--deck-height, 400px)}.card[data-v-4591d50a]{position:absolute;width:100%;height:var(--card-height, 100%);transition:transform .3s ease;box-shadow:var(--card-shadow, 0 4px 6px rgba(0, 0, 0, .1))}.discard-pile[data-v-d1a5b2af]{position:relative;width:var(--pile-width, 150px);height:var(--pile-height, 200px);border:var(--pile-border, 2px dashed #ccc);border-radius:var(--pile-border-radius, 5px);background-color:var(--pile-bg-color, #f9f9f9);display:flex;align-items:center;justify-content:center}.card[data-v-d1a5b2af]{position:absolute;transition:transform .2s;border-radius:var(--card-border-radius, 5px);box-shadow:var(--card-shadow, 0 4px 8px rgba(0, 0, 0, .1))}.discard-pile.hovered[data-v-d1a5b2af]{border-color:var(--pile-hover-border-color, #007bff);background-color:var(--pile-hover-bg-color, #e6f7ff)}.empty-notification[data-v-d1a5b2af],.full-notification[data-v-d1a5b2af]{position:absolute;color:var(--notification-color, #ff6f61);font-size:var(--notification-font-size, 14px)}[data-v-25e365d3]:root{--scheduler-bg-color: #f7f7f7;--event-bg-color: #007bff;--event-dragging-bg-color: #0056b3;--event-border-color: #cccccc}.scheduler[data-v-25e365d3]{background-color:var(--scheduler-bg-color);display:grid;grid-template-rows:repeat(24,1fr);gap:2px;height:100%}.time-slot[data-v-25e365d3]{border:1px solid var(--event-border-color);position:relative}.event[data-v-25e365d3]{background-color:var(--event-bg-color);border-radius:4px;position:absolute;width:100%;color:#fff;padding:.5rem;box-shadow:0 2px 4px #0003;transition:transform .2s}.event.dragging[data-v-25e365d3]{background-color:var(--event-dragging-bg-color);box-shadow:0 4px 8px #0000004d}.dropdown-menu[data-v-f56e06f8]{position:relative;font-size:var(--font-size, 16px)}.dropdown-toggle[data-v-f56e06f8]{background-color:var(--toggle-bg-color, #007bff);color:var(--toggle-text-color, #fff);border:none;padding:10px;cursor:pointer;font-size:var(--button-font-size, 16px)}.dropdown-list[data-v-f56e06f8]{position:absolute;top:100%;left:0;background-color:var(--menu-bg-color, #fff);border:1px solid #ccc;box-shadow:0 2px 10px #0000001a;z-index:1000;list-style:none;margin:0;padding:0;width:200px}.dropdown-list li[data-v-f56e06f8]{padding:10px}.dropdown-list a[data-v-f56e06f8]{color:var(--menu-link-color, #007bff);text-decoration:none;display:block}.dropdown-list a[data-v-f56e06f8]:hover{background-color:var(--hover-bg-color, #f0f0f0)}.dropdown-list a.active[data-v-f56e06f8]{font-weight:700;background-color:var(--active-bg-color, #e0e0e0)}.editable-data-table[data-v-f7a88ca4]{--table-width: 100%;--table-border-color: #ddd;--table-header-bg: #f9f9f9;--table-header-text-color: #333;--table-row-hover-bg: #f1f1f1;--table-row-editing-bg: #e0f7fa}table[data-v-f7a88ca4]{width:var(--table-width);border-collapse:collapse}th[data-v-f7a88ca4],td[data-v-f7a88ca4]{padding:8px;border:1px solid var(--table-border-color)}th[data-v-f7a88ca4]{background-color:var(--table-header-bg);color:var(--table-header-text-color)}tr[data-v-f7a88ca4]:hover{background-color:var(--table-row-hover-bg)}tr.editing[data-v-f7a88ca4]{background-color:var(--table-row-editing-bg)}.pagination-controls[data-v-f7a88ca4]{margin-top:16px;display:flex;justify-content:space-between}.embedded-media-iframe[data-v-df79aece]{position:relative;width:100%;height:0;padding-bottom:56.25%;background-color:var(--iframe-bg-color);overflow:hidden}.embedded-media-iframe iframe[data-v-df79aece]{position:absolute;top:0;left:0;width:100%;height:100%;border:none}.embedded-media-iframe.fullscreen[data-v-df79aece]{width:100vw;height:100vh;padding-bottom:0}.fullscreen-btn[data-v-df79aece]{position:absolute;bottom:10px;right:10px;background-color:var(--button-bg-color);color:var(--button-text-color);border:none;font-size:1.5rem;cursor:pointer;padding:.5rem}.emoji-reaction-poll[data-v-d06ec7ef]{display:flex;flex-direction:column;align-items:center;margin:16px}.emojis[data-v-d06ec7ef]{display:flex}.emoji[data-v-d06ec7ef]{font-size:2rem;background:none;border:none;cursor:pointer;margin:0 5px;color:var(--secondary-color)}.emoji.selected[data-v-d06ec7ef]{color:var(--primary-color)}.emoji[data-v-d06ec7ef]:focus{outline:2px solid var(--primary-color)}.eraser-tool[data-v-022c39bc]{display:flex;flex-direction:column;gap:10px}.eraser-tool.active .eraser-button[data-v-022c39bc]{border:2px solid var(--eraser-tool-active-border)}.eraser-settings[data-v-022c39bc]{display:flex;flex-direction:column;gap:5px}.eraser-button[data-v-022c39bc]{cursor:pointer}[data-v-8ed5c1ba]:root{--dialog-bg-color: #ffffff;--dialog-text-color: #333333;--dialog-border-color: #dddddd;--dialog-shadow-color: rgba(0, 0, 0, .2);--button-bg-color: #007bff;--button-text-color: #ffffff;--loading-bg-color: #f0f0f0;--error-text-color: #ff0000}.dialog[data-v-8ed5c1ba]{background-color:var(--dialog-bg-color);color:var(--dialog-text-color);border:1px solid var(--dialog-border-color);box-shadow:0 2px 10px var(--dialog-shadow-color);width:80%;max-width:600px;margin:2rem auto;padding:1rem;position:relative}.dialog-content[data-v-8ed5c1ba]{padding:1rem}.dialog-actions[data-v-8ed5c1ba]{display:flex;gap:1rem}button[data-v-8ed5c1ba]{background-color:var(--button-bg-color);color:var(--button-text-color);border:none;padding:.5rem 1rem;border-radius:4px;cursor:pointer}.close-button[data-v-8ed5c1ba]{position:absolute;top:.5rem;right:.5rem;background:none;border:none;font-size:1.5rem;line-height:1;cursor:pointer;color:var(--dialog-text-color)}.loading[data-v-8ed5c1ba]{background-color:var(--loading-bg-color);padding:1rem;text-align:center}.error[data-v-8ed5c1ba]{color:var(--error-text-color);padding:1rem;text-align:center}@media (max-width: 600px){.dialog[data-v-8ed5c1ba]{width:95%}}[data-v-88e66fe9]:root{--filter-bar-bg-color: #f1f3f5;--filter-bar-text-color: #212529;--input-bg-color: #fff;--input-border-color: #ced4da;--button-bg-color: #007bff;--button-text-color: #fff;--active-filters-color: #495057}.event-filter-bar[data-v-88e66fe9]{background-color:var(--filter-bar-bg-color);color:var(--filter-bar-text-color);padding:1rem;border-radius:5px}.filter-group[data-v-88e66fe9]{margin-bottom:1rem}label[data-v-88e66fe9]{display:block;margin-bottom:.5rem}select[data-v-88e66fe9],input[data-v-88e66fe9]{width:100%;padding:.5rem;background-color:var(--input-bg-color);border:1px solid var(--input-border-color);border-radius:5px;box-sizing:border-box}.filter-buttons[data-v-88e66fe9]{display:flex;gap:.5rem}button[data-v-88e66fe9]{background-color:var(--button-bg-color);color:var(--button-text-color);border:none;padding:.5rem 1rem;border-radius:5px;cursor:pointer}.active-filters[data-v-88e66fe9]{color:var(--active-filters-color);margin-top:1rem}[data-v-abb09c51]:root{--system-bg-color: #f8f9fa;--system-text-color: #212529;--input-bg-color: #fff;--input-border-color: #ced4da;--button-bg-color: #28a745;--button-text-color: #fff;--feedback-bg-color: #d1ecf1;--feedback-text-color: #0c5460}.event-reminder-system[data-v-abb09c51]{background-color:var(--system-bg-color);color:var(--system-text-color);padding:1rem;border-radius:5px}.form-group[data-v-abb09c51]{margin-bottom:1rem}label[data-v-abb09c51]{display:block;margin-bottom:.5rem}select[data-v-abb09c51]{width:100%;padding:.5rem;background-color:var(--input-bg-color);border:1px solid var(--input-border-color);border-radius:5px;box-sizing:border-box}.reminder-buttons[data-v-abb09c51]{display:flex;gap:.5rem}button[data-v-abb09c51]{background-color:var(--button-bg-color);color:var(--button-text-color);border:none;padding:.5rem 1rem;border-radius:5px;cursor:pointer}.feedback[data-v-abb09c51]{background-color:var(--feedback-bg-color);color:var(--feedback-text-color);padding:.5rem;margin-top:1rem}.expandable-list[data-v-2bce1792]{list-style:none;padding:0}.list-item[data-v-2bce1792]{margin:var(--item-margin, 5px 0);padding:var(--item-padding, 10px);border:var(--item-border, 1px solid #ccc);cursor:pointer}.list-item[data-v-2bce1792]:hover{background-color:var(--hover-background-color, #f0f0f0)}.item-header[data-v-2bce1792]{font-weight:var(--item-header-font-weight, bold)}.item-content[data-v-2bce1792]{margin-top:var(--item-content-margin-top, 5px)}.favorites-list[data-v-62a6fd5a]{list-style:none;padding:0}.list-item[data-v-62a6fd5a]{margin:var(--item-margin, 5px 0);padding:var(--item-padding, 10px);border:var(--item-border, 1px solid #ccc);cursor:pointer}.list-item.selected[data-v-62a6fd5a]{background-color:var(--selected-background-color, #e0e0e0)}.list-item.hover[data-v-62a6fd5a]{background-color:var(--hover-background-color, #f0f0f0)}.item-header[data-v-62a6fd5a]{display:flex;justify-content:space-between;font-weight:var(--item-header-font-weight, bold)}.star-button[data-v-62a6fd5a]{background:none;border:none;cursor:pointer;font-size:var(--star-button-font-size, 1.5em)}.field-editable-data-table[data-v-5d7b0258]{width:100%;border-collapse:collapse}.table-row[data-v-5d7b0258]{display:flex;justify-content:space-between;padding:8px 0}.table-cell[data-v-5d7b0258]{display:flex;align-items:center;padding:8px;border-bottom:1px solid var(--table-border)}.editable-field[data-v-5d7b0258]{cursor:pointer;padding:4px;transition:background-color .3s ease}.editable-field[data-v-5d7b0258]:hover{background-color:var(--field-hover-bg)}textarea[data-v-5d7b0258],input[type=text][data-v-5d7b0258]{width:100%;padding:4px;margin-right:8px}button[data-v-5d7b0258]{padding:4px 8px;margin-left:4px}.error-message[data-v-5d7b0258]{color:var(--error-color);margin-top:12px}[data-v-a42dea44]:root{--file-input-border: #adb5bd;--file-input-bg: #f8f9fa;--error-color: #dc3545}.file-input-container[data-v-a42dea44]{display:flex;flex-direction:column;align-items:center;gap:1rem}input[type=file][data-v-a42dea44]{border:1px solid var(--file-input-border);padding:.5rem;background-color:var(--file-input-bg);cursor:pointer}input[type=file][data-v-a42dea44]:disabled{cursor:not-allowed}.preview-container[data-v-a42dea44]{max-width:100%;text-align:center}.preview-image[data-v-a42dea44]{max-width:100%;height:auto;border-radius:.25rem}.error-message[data-v-a42dea44]{color:var(--error-color);font-size:.875rem}@media (max-width: 576px){input[type=file][data-v-a42dea44]{width:100%;font-size:.875rem}}@media (min-width: 577px) and (max-width: 768px){input[type=file][data-v-a42dea44]{width:95%;font-size:.9375rem}}@media (min-width: 769px) and (max-width: 992px){input[type=file][data-v-a42dea44]{width:90%;font-size:1rem}}@media (min-width: 993px){input[type=file][data-v-a42dea44]{width:85%;font-size:1.125rem}}[data-v-e710dbb6]:root{--file-upload-border: #adb5bd;--file-upload-bg: #f8f9fa;--progress-bg: #007bff}.file-upload-container[data-v-e710dbb6]{border:2px dashed var(--file-upload-border);padding:1rem;background-color:var(--file-upload-bg);text-align:center;position:relative;transition:background-color .3s}.file-upload-container.drag-over[data-v-e710dbb6]{background-color:#e9ecef}input[type=file][data-v-e710dbb6]{display:none}.progress-bar[data-v-e710dbb6]{position:absolute;bottom:0;left:0;right:0;height:4px;background-color:#e9ecef}.progress[data-v-e710dbb6]{height:100%;background-color:var(--progress-bg);transition:width .3s}@media (max-width: 576px){.file-upload-container[data-v-e710dbb6]{font-size:.875rem}}@media (min-width: 577px) and (max-width: 768px){.file-upload-container[data-v-e710dbb6]{font-size:.9375rem}}@media (min-width: 769px) and (max-width: 992px){.file-upload-container[data-v-e710dbb6]{font-size:1rem}}@media (min-width: 993px){.file-upload-container[data-v-e710dbb6]{font-size:1.125rem}}.fill-tool[data-v-c9034f98]{display:flex;flex-direction:column;align-items:center}button[data-v-c9034f98]{cursor:pointer;padding:8px 16px;border:none;background-color:var(--button-bg);color:var(--button-text-color);transition:background-color .3s}button[data-v-c9034f98]:disabled{background-color:var(--button-disabled-bg);cursor:not-allowed}button.active[data-v-c9034f98]{background-color:var(--button-active-bg)}.fill-options[data-v-c9034f98]{margin-top:10px;display:flex;flex-direction:column;align-items:center}label[data-v-c9034f98]{margin-bottom:5px;color:var(--label-text-color)}input[type=range][data-v-c9034f98]{width:100px}.filterable-list[data-v-d92ce0dc]{width:100%}.filter-input[data-v-d92ce0dc]{width:100%;padding:var(--input-padding, 10px);margin-bottom:var(--input-margin-bottom, 10px);border:var(--input-border, 1px solid #ccc);border-radius:var(--input-border-radius, 4px)}.clear-button[data-v-d92ce0dc]{margin-bottom:var(--button-margin-bottom, 10px);padding:var(--button-padding, 10px);border:none;background-color:var(--button-background-color, #f0f0f0);cursor:pointer}.list-item[data-v-d92ce0dc]{padding:var(--list-item-padding, 5px 0);border-bottom:var(--list-item-border-bottom, 1px solid #ddd)}.no-results[data-v-d92ce0dc]{color:var(--no-results-color, #777)}.flip-card[data-v-fd008512]{width:var(--flip-card-width, 300px);height:var(--flip-card-height, 200px);perspective:1000px;border-radius:var(--flip-card-border-radius, 10px);transition:transform .6s}.flip-card-inner[data-v-fd008512]{position:relative;width:100%;height:100%;text-align:center;transition:transform .6s;transform-style:preserve-3d;border-radius:inherit}.flip-card-front[data-v-fd008512],.flip-card-back[data-v-fd008512]{position:absolute;width:100%;height:100%;backface-visibility:hidden;border-radius:inherit;display:flex;align-items:center;justify-content:center;background-color:var(--flip-card-bg-color, #f0f0f0)}.flip-card-back[data-v-fd008512]{transform:rotateY(180deg)}.flip-card.disabled[data-v-fd008512]{cursor:not-allowed}.flip-card[data-v-fd008512]:not(.disabled):hover{cursor:pointer}.floating-action-button[data-v-ec8edb50]{background-color:var(--fab-bg-color, #6200ee);color:var(--fab-color, #fff);border:none;border-radius:var(--fab-radius, 50%);padding:15px;position:fixed;bottom:20px;right:20px;cursor:pointer;transition:transform .3s,background-color .3s}.floating-action-button[data-v-ec8edb50]:hover{background-color:var(--fab-hover-bg-color, #3700b3)}.floating-action-button[data-v-ec8edb50]:focus{outline:2px solid var(--fab-focus-color, #000)}.sr-only[data-v-ec8edb50]{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);border:0}.fold-button[data-v-3abbd310]{padding:var(--button-padding);font-size:var(--button-font-size);background-color:var(--button-bg-color);color:var(--button-text-color);border:none;border-radius:var(--button-border-radius);cursor:pointer;transition:background-color .3s,transform .1s}.fold-button[data-v-3abbd310]:hover:not(:disabled){background-color:var(--button-hover-bg-color)}.fold-button[data-v-3abbd310]:disabled{background-color:var(--button-disabled-bg-color);cursor:not-allowed}.fold-button[data-v-3abbd310]:active:not(:disabled){transform:scale(.95)}.grouped-list[data-v-024aa25b]{width:100%}.group[data-v-024aa25b]{margin-bottom:var(--group-margin-bottom, 20px)}.group-header[data-v-024aa25b]{cursor:pointer;background-color:var(--group-header-bg, #f5f5f5);padding:var(--group-header-padding, 10px);border:var(--group-header-border, 1px solid #ccc)}.group-items[data-v-024aa25b]{list-style-type:none;padding:0}.list-item[data-v-024aa25b]{padding:var(--list-item-padding, 8px);cursor:pointer;background-color:var(--list-item-bg, #fff)}.list-item[data-v-024aa25b]:hover{background-color:var(--list-item-hover-bg, #e0e0e0)}.list-item.selected[data-v-024aa25b]{background-color:var(--list-item-selected-bg, #d0d0d0)}.hand[data-v-68df639c]{display:flex;flex-direction:row;gap:var(--hand-card-gap, 10px);padding:var(--hand-padding, 10px);background-color:var(--hand-bg-color, #f0f0f0);border-radius:var(--hand-border-radius, 5px)}.card[data-v-68df639c]{cursor:pointer;transition:transform .2s;box-shadow:var(--card-shadow, 0 4px 8px rgba(0, 0, 0, .1));border-radius:var(--card-border-radius, 5px)}.card.selected[data-v-68df639c]{transform:scale(1.1);box-shadow:var(--card-selected-shadow, 0 6px 12px rgba(0, 0, 0, .2))}.hand.full[data-v-68df639c]{background-color:var(--hand-full-bg-color, #d4edda)}.limit-notification[data-v-68df639c]{color:var(--limit-notification-color, #ff6f61);font-size:var(--limit-notification-font-size, 14px);margin-left:var(--limit-notification-margin-left, 10px)}.icon-button[data-v-1fd9d51b]{display:inline-flex;align-items:center;justify-content:center;padding:var(--icon-button-padding, 10px);border:none;background-color:transparent;cursor:pointer;transition:background-color .3s}.disabled[data-v-1fd9d51b]{color:var(--icon-button-disabled-color, #9e9e9e);cursor:not-allowed}.hover[data-v-1fd9d51b]:not(.disabled){background-color:var(--icon-button-hover-bg, #f0f0f0)}.active[data-v-1fd9d51b]:not(.disabled){background-color:var(--icon-button-active-bg, #d0d0d0)}.icon[data-v-1fd9d51b]{font-size:var(--icon-size, 24px)}.image-choice-poll[data-v-ec76f45f]{padding:1rem;border:1px solid var(--border-color, #ccc);border-radius:5px;max-width:500px;margin:0 auto}.poll-label[data-v-ec76f45f]{font-size:1.2rem;margin-bottom:.5rem}.options[data-v-ec76f45f]{display:flex;flex-wrap:wrap;gap:10px}.option[data-v-ec76f45f]{border:none;background:transparent;padding:0;cursor:pointer}.option.disabled[data-v-ec76f45f]{cursor:not-allowed;opacity:.6}.option-image[data-v-ec76f45f]{width:100px;height:100px;object-fit:cover;border-radius:5px}.selected .option-image[data-v-ec76f45f]{border:2px solid var(--selected-border-color, #007bff)}.result-display[data-v-ec76f45f]{font-size:.9rem;margin-top:.5rem}.image-slider[data-v-a1b0374f]{position:relative;width:100%;height:400px;overflow:hidden;background-color:var(--slider-bg-color)}.slider-images[data-v-a1b0374f]{display:flex;transition:transform .5s ease}.slider-image[data-v-a1b0374f]{flex:0 0 100%;background-size:cover;background-position:center;opacity:0;transition:opacity .5s ease}.slider-image.active[data-v-a1b0374f]{opacity:1}.slider-image.hover[data-v-a1b0374f]{filter:brightness(.8)}.prev-btn[data-v-a1b0374f],.next-btn[data-v-a1b0374f]{position:absolute;top:50%;transform:translateY(-50%);background-color:var(--button-bg-color);color:var(--button-text-color);border:none;padding:.5rem;cursor:pointer;font-size:2rem}.prev-btn[data-v-a1b0374f]{left:10px}.next-btn[data-v-a1b0374f]{right:10px}.interactive-media-map[data-v-45db09ed]{position:relative;width:100%;max-width:800px;margin:0 auto;background-color:var(--map-bg-color)}.map-container[data-v-45db09ed]{position:relative;overflow:hidden}.map-marker[data-v-45db09ed]{position:absolute;width:20px;height:20px;background-color:var(--marker-bg-color);border-radius:50%;cursor:pointer;transform:translate(-50%,-50%);transition:background-color .3s ease}.map-marker[data-v-45db09ed]:hover{background-color:var(--marker-hover-bg-color)}.zoom-btn[data-v-45db09ed]{position:absolute;top:10px;right:10px;background-color:var(--button-bg-color);color:var(--button-text-color);border:none;padding:.5rem;cursor:pointer;font-size:1.5rem;margin-left:5px}.marker-info[data-v-45db09ed]{position:absolute;bottom:10px;left:50%;transform:translate(-50%);background-color:var(--info-bg-color);color:var(--info-text-color);padding:1rem;border-radius:5px}.loading[data-v-45db09ed]{position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);font-size:1.5rem;color:var(--loading-text-color)}.poll-results[data-v-4fad34d2]{font-size:var(--poll-font-size, 16px);color:var(--poll-text-color, #000)}.option-text[data-v-4fad34d2]{margin-right:10px}.progress-bar[data-v-4fad34d2]{display:inline-block;width:calc(var(--progress) + 2px);height:var(--progress-bar-height, 20px);background-color:var(--progress-bar-color, #007bff);position:relative;overflow:hidden}.percentage[data-v-4fad34d2]{position:absolute;right:5px;top:50%;transform:translateY(-50%);color:var(--percentage-text-color, #fff)}.poll-status[data-v-4fad34d2]{margin-top:10px;font-weight:700;color:var(--status-text-color, #ff0000)}.layer-panel[data-v-54d3b63a]{display:flex;flex-direction:column;gap:10px}.active[data-v-54d3b63a]{border:2px solid var(--layer-panel-active-border)}ul[data-v-54d3b63a]{list-style:none;padding:0}li[data-v-54d3b63a]{display:flex;align-items:center;gap:5px}button[data-v-54d3b63a]{cursor:pointer}.live-results-poll[data-v-f650da5f]{padding:1rem;border:1px solid var(--border-color, #ccc);border-radius:5px;max-width:500px;margin:0 auto}.poll-label[data-v-f650da5f]{font-size:1.2rem;margin-bottom:.5rem}.options[data-v-f650da5f]{display:flex;flex-direction:column;gap:10px}.option[data-v-f650da5f]{border:1px solid var(--option-border-color, #ddd);background:var(--option-bg, #f9f9f9);padding:.5rem 1rem;cursor:pointer;border-radius:5px;display:flex;justify-content:space-between}.option.disabled[data-v-f650da5f]{cursor:not-allowed;opacity:.6}.selected[data-v-f650da5f]{background:var(--selected-bg-color, #e0f7fa)}.result-percentage[data-v-f650da5f]{font-size:.8rem;color:var(--result-percentage-color, #555)}.live-stream-player[data-v-86ab4e35]{position:relative;width:100%;max-width:800px;margin:0 auto;background-color:var(--player-bg-color)}video[data-v-86ab4e35]{width:100%;height:auto;background-color:var(--video-bg-color)}.buffering-overlay[data-v-86ab4e35]{position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);color:var(--buffering-text-color);font-size:1.5rem;background-color:#00000080;padding:.5rem;border-radius:5px}.mute-btn[data-v-86ab4e35]{position:absolute;top:10px;right:10px;background-color:var(--button-bg-color);color:var(--button-text-color);border:none;padding:.5rem;cursor:pointer;font-size:1rem}.load-more-button-in-list[data-v-c106ae76]{width:100%}.item-list[data-v-c106ae76]{list-style-type:none;padding:0;margin:0}.list-item[data-v-c106ae76]{padding:var(--list-item-padding, 8px);border-bottom:var(--list-item-border, 1px solid #ccc)}.load-more-button[data-v-c106ae76]{margin-top:var(--button-margin-top, 10px);padding:var(--button-padding, 10px);background-color:var(--button-bg, #007bff);color:var(--button-color, #fff);border:none;cursor:pointer}.load-more-button[data-v-c106ae76]:disabled{background-color:var(--button-disabled-bg, #cccccc);cursor:not-allowed}.end-of-list-message[data-v-c106ae76]{margin-top:var(--end-message-margin-top, 10px);color:var(--end-message-color, #999)}.loading-bars[data-v-5c336f48]{display:flex;font-size:var(--loading-font-size, 14px)}ul[data-v-5c336f48]{display:flex;list-style:none;padding:0}li[data-v-5c336f48]{flex:1;margin-right:10px;position:relative}.step-bar[data-v-5c336f48]{height:var(--step-bar-height, 5px);background-color:var(--step-bar-bg, #ccc);width:var(--progress);transition:width .3s ease}.active .step-bar[data-v-5c336f48]{background-color:var(--active-step-bar-color, #007bff)}.completed .step-bar[data-v-5c336f48]{background-color:var(--completed-step-bar-color, #28a745)}.step-label[data-v-5c336f48]{margin-top:5px;text-align:center;display:block}.spinner[data-v-8e2b0a89]{border:var(--spinner-border-width, 4px) solid var(--spinner-border-color, #f3f3f3);border-top:var(--spinner-border-width, 4px) solid var(--spinner-active-color, #3498db);border-radius:50%;width:var(--spinner-size, 40px);height:var(--spinner-size, 40px);animation:spin-8e2b0a89 2s linear infinite}.visually-hidden[data-v-8e2b0a89]{position:absolute;width:1px;height:1px;margin:-1px;border:0;padding:0;clip:rect(0,0,0,0);overflow:hidden;white-space:nowrap}@keyframes spin-8e2b0a89{0%{transform:rotate(0)}to{transform:rotate(360deg)}}.media-gallery[data-v-4974d288]{width:100%;max-width:1200px;margin:0 auto;background-color:var(--gallery-bg-color)}.thumbnail-view[data-v-4974d288]{display:flex;flex-wrap:wrap}.thumbnail[data-v-4974d288]{width:100px;height:100px;margin:5px;cursor:pointer}.expanded-view[data-v-4974d288]{position:relative;display:flex;align-items:center;justify-content:center}.expanded-image[data-v-4974d288]{max-width:100%;max-height:80vh;cursor:pointer}.nav-btn[data-v-4974d288]{background-color:var(--button-bg-color);color:var(--button-text-color);border:none;padding:.5rem;font-size:1.5rem;cursor:pointer}.controls[data-v-4974d288]{display:flex;justify-content:center;margin-top:1rem}.control-btn[data-v-4974d288]{background-color:var(--button-bg-color);color:var(--button-text-color);border:none;padding:.5rem;margin:0 .5rem;cursor:pointer}.poll[data-v-e84ac6d0]{margin:16px}.option[data-v-e84ac6d0]{margin-bottom:8px}input[type=checkbox][data-v-e84ac6d0]{accent-color:var(--primary-color)}.multiselect-list[data-v-d45a7e8f]{width:100%}.item-list[data-v-d45a7e8f]{list-style-type:none;padding:0;margin:0}.list-item[data-v-d45a7e8f]{padding:var(--list-item-padding, 8px);border-bottom:var(--list-item-border, 1px solid #ccc);cursor:pointer;transition:background-color .2s}.list-item.selected[data-v-d45a7e8f]{background-color:var(--selected-bg, #d1e7dd);color:var(--selected-color, #0f5132)}.list-item.disabled[data-v-d45a7e8f]{background-color:var(--disabled-bg, #f8f9fa);color:var(--disabled-color, #6c757d);cursor:not-allowed}.list-item[data-v-d45a7e8f]:hover:not(.disabled){background-color:var(--hover-bg, #e2e6ea)}.notification[data-v-cce4ab9d]{padding:15px;border-radius:5px;margin:10px 0;display:flex;justify-content:space-between;align-items:center}.notification.success[data-v-cce4ab9d]{background-color:var(--notification-success-bg, #d4edda);color:var(--notification-success-color, #155724)}.notification.error[data-v-cce4ab9d]{background-color:var(--notification-error-bg, #f8d7da);color:var(--notification-error-color, #721c24)}.notification.warning[data-v-cce4ab9d]{background-color:var(--notification-warning-bg, #fff3cd);color:var(--notification-warning-color, #856404)}.close-btn[data-v-cce4ab9d]{background:none;border:none;color:inherit;cursor:pointer;font-size:16px}.notification-bell[data-v-5f09368b]{position:relative;width:var(--bell-icon-size, 24px);height:var(--bell-icon-size, 24px);cursor:pointer}.bell-icon[data-v-5f09368b]{fill:var(--bell-icon-color, #000)}.has-notifications[data-v-5f09368b]{fill:var(--bell-icon-notification-color, #ff0000)}.dismissed[data-v-5f09368b]{fill:var(--bell-icon-dismissed-color, #ccc)}.notification-dot[data-v-5f09368b]{position:absolute;top:0;right:0;width:var(--notification-dot-size, 8px);height:var(--notification-dot-size, 8px);background-color:var(--notification-dot-color, #ff0000);border-radius:50%}[data-v-0261d126]:root{--button-bg: #f8f9fa;--button-border: #adb5bd;--button-color: #495057;--input-border: #ced4da;--input-bg: #ffffff;--input-color: #495057;--button-disabled-bg: #e9ecef;--button-disabled-color: #6c757d}.number-input-container[data-v-0261d126]{display:flex;align-items:center}button[data-v-0261d126]{background-color:var(--button-bg);border:1px solid var(--button-border);color:var(--button-color);padding:.5rem;cursor:pointer;transition:background-color .3s,color .3s}button[data-v-0261d126]:disabled{background-color:var(--button-disabled-bg);color:var(--button-disabled-color);cursor:not-allowed}input[type=number][data-v-0261d126]{width:3rem;padding:.5rem;border:1px solid var(--input-border);background-color:var(--input-bg);color:var(--input-color);text-align:center}@media (max-width: 576px){.number-input-container[data-v-0261d126]{flex-direction:column}}@media (min-width: 577px) and (max-width: 768px){.number-input-container[data-v-0261d126]{flex-direction:row}}@media (min-width: 769px) and (max-width: 992px){.number-input-container[data-v-0261d126]{flex-direction:row}}@media (min-width: 993px){.number-input-container[data-v-0261d126]{flex-direction:row}}.numbered-list[data-v-3038608d]{list-style-type:decimal;padding:0;margin:0}.list-item[data-v-3038608d]{padding:var(--list-item-padding, 8px);border-bottom:var(--list-item-border, 1px solid #ccc);cursor:pointer;transition:background-color .2s}.list-item.selected[data-v-3038608d]{background-color:var(--selected-bg, #d1e7dd);color:var(--selected-color, #0f5132)}.list-item.disabled[data-v-3038608d]{background-color:var(--disabled-bg, #f8f9fa);color:var(--disabled-color, #6c757d);cursor:not-allowed}.list-item[data-v-3038608d]:hover:not(.disabled){background-color:var(--hover-bg, #e2e6ea)}.open-ended-poll[data-v-5c6668a4]{padding:1rem;border:1px solid var(--border-color, #ccc);border-radius:5px;max-width:500px;margin:0 auto}.poll-label[data-v-5c6668a4]{font-size:1.2rem;margin-bottom:.5rem;display:block}.response-input[data-v-5c6668a4]{width:100%;height:100px;border:1px solid var(--input-border-color, #ddd);border-radius:5px;padding:.5rem;margin-bottom:.5rem;resize:none}.submit-button[data-v-5c6668a4]{background-color:var(--button-bg-color, #007bff);color:#fff;border:none;padding:.5rem 1rem;border-radius:5px;cursor:pointer}.submit-button[data-v-5c6668a4]:disabled{background-color:var(--button-disabled-bg-color, #ccc);cursor:not-allowed}.results[data-v-5c6668a4]{margin-top:1rem}.results h3[data-v-5c6668a4]{margin-bottom:.5rem}.results ul[data-v-5c6668a4]{list-style-type:disc;padding-left:1.5rem}.pagination[data-v-62783a13]{display:flex;justify-content:center}.pagination-list[data-v-62783a13]{display:flex;list-style:none;padding:0;margin:0}.pagination-item[data-v-62783a13]{padding:var(--pagination-item-padding, 8px 12px);margin:0 4px;cursor:pointer;transition:background-color .2s;border-radius:var(--pagination-border-radius, 4px)}.pagination-item.active[data-v-62783a13]{background-color:var(--active-bg, #007bff);color:var(--active-color, #fff)}.pagination-item[data-v-62783a13]:hover:not(.active){background-color:var(--hover-bg, #e9ecef)}.pagination-control[data-v-e9633912]{--button-bg: #007bff;--button-color: #ffffff;--button-disabled-bg: #d6d6d6;--button-disabled-color: #8a8a8a;--highlight-color: #28a745}button[data-v-e9633912]{background-color:var(--button-bg);color:var(--button-color);border:none;padding:5px 10px;margin:0 5px;cursor:pointer}button[data-v-e9633912]:disabled{background-color:var(--button-disabled-bg);color:var(--button-disabled-color);cursor:not-allowed}span[data-v-e9633912]{color:var(--highlight-color);margin:0 10px}select[data-v-e9633912]{margin-left:10px}[data-v-7bc855fa]:root{--input-border: #ced4da;--input-bg: #ffffff;--input-color: #495057;--match-color: #28a745;--not-match-color: #dc3545}.password-confirmation-container[data-v-7bc855fa]{display:flex;flex-direction:column;gap:.5rem}input[type=password][data-v-7bc855fa]{padding:.5rem;border:1px solid var(--input-border);background-color:var(--input-bg);color:var(--input-color)}p.match[data-v-7bc855fa]{color:var(--match-color)}p.not-match[data-v-7bc855fa]{color:var(--not-match-color)}@media (max-width: 576px){.password-confirmation-container[data-v-7bc855fa]{flex-direction:column}}@media (min-width: 577px) and (max-width: 768px){.password-confirmation-container[data-v-7bc855fa]{flex-direction:column}}@media (min-width: 769px) and (max-width: 992px){.password-confirmation-container[data-v-7bc855fa]{flex-direction:column}}@media (min-width: 993px){.password-confirmation-container[data-v-7bc855fa]{flex-direction:column}}.pinned-list[data-v-ecd634ef]{list-style:none;padding:0;margin:0}.pinned-list-item[data-v-ecd634ef]{padding:var(--list-item-padding, 10px 15px);margin:4px 0;cursor:pointer;transition:background-color .2s;border-radius:var(--list-border-radius, 4px)}.pinned-list-item.pinned[data-v-ecd634ef]{background-color:var(--pinned-bg, #ffc107)}.pinned-list-item.selected[data-v-ecd634ef]{background-color:var(--selected-bg, #007bff);color:var(--selected-color, #fff)}.pinned-list-item.hover[data-v-ecd634ef]:not(.selected){background-color:var(--hover-bg, #e9ecef)}.playing-card[data-v-4b36289e]{--card-width: 100px;--card-height: 140px;--border-radius: 10px;--transition-duration: .6s;width:var(--card-width);height:var(--card-height);perspective:1000px;cursor:pointer;transition:transform var(--transition-duration);outline:none}.playing-card.is-disabled[data-v-4b36289e]{cursor:not-allowed;opacity:.5}.card-face[data-v-4b36289e]{width:100%;height:100%;border-radius:var(--border-radius);box-shadow:0 4px 8px #0003;backface-visibility:hidden;position:absolute;display:flex;justify-content:center;align-items:center}.card-front[data-v-4b36289e]{background:var(--card-back-pattern, #ccc)}.card-back[data-v-4b36289e]{background:var(--card-front-pattern, white);transform:rotateY(180deg)}.playing-card.is-flipped .card-back[data-v-4b36289e]{transform:rotateY(0)}.playing-card.is-flipped .card-front[data-v-4b36289e]{transform:rotateY(180deg)}.playing-card[data-v-4b36289e]:hover:not(.is-disabled){transform:scale(1.05)}@media (max-width: 320px){.playing-card[data-v-4b36289e]{--card-width: 80px;--card-height: 112px}}@media (min-width: 321px) and (max-width: 480px){.playing-card[data-v-4b36289e]{--card-width: 90px;--card-height: 126px}}@media (min-width: 481px) and (max-width: 768px){.playing-card[data-v-4b36289e]{--card-width: 110px;--card-height: 154px}}@media (min-width: 769px){.playing-card[data-v-4b36289e]{--card-width: 120px;--card-height: 168px}}.podcast-player[data-v-6c576cf9]{width:100%;max-width:600px;margin:0 auto;background-color:var(--player-bg-color)}.player-controls[data-v-6c576cf9]{display:flex;justify-content:center;margin-bottom:1rem}.control-btn[data-v-6c576cf9]{background-color:var(--button-bg-color);color:var(--button-text-color);border:none;padding:.5rem;margin:0 .5rem;cursor:pointer}.episode-list ul[data-v-6c576cf9]{list-style:none;padding:0}.episode-list li[data-v-6c576cf9]{padding:.5rem;cursor:pointer;background-color:var(--episode-bg-color);margin-bottom:.25rem}.poker-chips[data-v-4c1ec75e]{display:flex;flex-wrap:wrap;transition:all var(--transition-duration)}.chip[data-v-4c1ec75e]{width:var(--chip-size);height:var(--chip-size);border-radius:50%;display:flex;align-items:center;justify-content:center;box-shadow:var(--chip-shadow);margin:var(--chip-margin)}.stacked .chip[data-v-4c1ec75e]{transition:transform var(--transition-duration)}.moving .chip[data-v-4c1ec75e]{animation:move-4c1ec75e var(--move-duration) ease-in-out infinite alternate}.betPlaced .chip[data-v-4c1ec75e]{animation:bet-4c1ec75e var(--bet-duration) ease-in-out}.allIn .chip[data-v-4c1ec75e]{animation:shake-4c1ec75e var(--shake-duration) ease-in-out}@keyframes move-4c1ec75e{0%{transform:translateY(0)}to{transform:translateY(-10px)}}@keyframes bet-4c1ec75e{0%{transform:translate(0)}to{transform:translate(20px)}}@keyframes shake-4c1ec75e{0%,to{transform:translate(0)}50%{transform:translate(-5px)}}.poker-hand[data-v-a5885677]{display:flex;gap:var(--card-gap);transition:opacity .3s}.poker-hand[data-folded=true][data-v-a5885677]{opacity:.5}.card[data-v-a5885677]{width:var(--card-width);height:var(--card-height);background-color:var(--card-back-color);display:flex;justify-content:center;align-items:center;border-radius:var(--card-border-radius);transition:transform .3s,background-color .3s}.card[data-revealed=true][data-v-a5885677]{background-color:var(--card-front-color);transform:rotateY(180deg)}.card-value[data-v-a5885677]{font-size:var(--card-value-font-size);color:var(--card-value-color)}.poker-table[data-v-e5c0bbc5]{display:flex;flex-direction:column;justify-content:center;align-items:center;border-radius:var(--border-radius);box-shadow:var(--table-shadow);padding:var(--table-padding);transition:background-color var(--transition-duration)}.seats[data-v-e5c0bbc5]{display:flex;justify-content:space-around;width:100%;margin-bottom:var(--seat-margin-bottom)}.player[data-v-e5c0bbc5]{background-color:var(--player-bg);padding:var(--player-padding);border-radius:var(--border-radius);transition:all var(--transition-duration)}.player-active[data-v-e5c0bbc5]{background-color:var(--player-active-bg)}.community-cards[data-v-e5c0bbc5]{display:flex;justify-content:center;margin-top:var(--community-cards-margin-top)}.card[data-v-e5c0bbc5]{margin:0 var(--card-margin);padding:var(--card-padding);background-color:var(--card-bg);border-radius:var(--border-radius);box-shadow:var(--card-shadow)}.dealer-button[data-v-e5c0bbc5]{width:var(--dealer-button-size);height:var(--dealer-button-size);background-color:var(--dealer-button-bg);border-radius:50%;margin-top:var(--dealer-button-margin-top);transition:background-color var(--transition-duration)}.poker-timer[data-v-439e81ff]{display:flex;flex-direction:column;align-items:center;gap:var(--timer-gap)}.timer-display[data-v-439e81ff]{font-size:var(--timer-font-size);color:var(--timer-color)}.time-running-out[data-v-439e81ff]{color:var(--timer-warning-color)}.timer-button[data-v-439e81ff]{padding:var(--button-padding);font-size:var(--button-font-size)}.pot[data-v-177d41d0]{display:flex;flex-direction:column;align-items:center;gap:var(--pot-gap);transition:background-color .3s}.won[data-v-177d41d0]{background-color:var(--pot-won-bg-color)}.chips[data-v-177d41d0]{display:flex;justify-content:center;flex-wrap:wrap;gap:var(--chip-gap)}.empty[data-v-177d41d0]{opacity:.5}.chip-stack[data-v-177d41d0]{display:flex;flex-direction:column;align-items:center}.chip[data-v-177d41d0]{padding:var(--chip-padding);border:var(--chip-border);border-radius:var(--chip-border-radius);background-color:var(--chip-bg-color)}.total[data-v-177d41d0]{font-weight:700}.chip-move-enter-active[data-v-177d41d0],.chip-move-leave-active[data-v-177d41d0]{transition:transform .5s}.chip-move-enter[data-v-177d41d0],.chip-move-leave-to[data-v-177d41d0]{transform:translateY(-20px)}.progress-bar-container[data-v-0da1f739]{width:100%;height:var(--progress-bar-height, 20px);background-color:var(--progress-bar-bg-color, #e0e0e0);border-radius:var(--progress-bar-radius, 10px);overflow:hidden;transition:background-color .3s}.progress-bar[data-v-0da1f739]{height:100%;background-color:var(--progress-bar-color, #76c7c0);transition:width .3s}.progress-bar-container:hover .progress-bar[data-v-0da1f739]{background-color:var(--progress-bar-hover-color, #5baaa8)}.progress-bar-container.disabled[data-v-0da1f739]{background-color:var(--progress-bar-disabled-bg-color, #c0c0c0)}.progress-bar-container.disabled .progress-bar[data-v-0da1f739]{background-color:var(--progress-bar-disabled-color, #a0a0a0)}.progress-circle[data-v-d33da7d5]{width:var(--progress-circle-size, 100px);height:var(--progress-circle-size, 100px)}.circular-chart[data-v-d33da7d5]{max-width:100%;max-height:100%}.circle-bg[data-v-d33da7d5]{fill:none;stroke:var(--progress-circle-bg-color, #e0e0e0);stroke-width:3.8}.circle[data-v-d33da7d5]{fill:none;stroke-width:2.8;stroke-linecap:round;transition:stroke-dasharray .3s}.progress-circle[aria-valuenow="100"] .circle[data-v-d33da7d5]{stroke:var(--progress-circle-complete-color, #76c7c0)}.progress-circle[aria-valuenow="0"] .circle[data-v-d33da7d5]{stroke:var(--progress-circle-incomplete-color, #ff6f61)}.progress-circle[status=paused] .circle[data-v-d33da7d5]{stroke:var(--progress-circle-paused-color, #f9a825)}.progress-circle[status=active] .circle[data-v-d33da7d5]{stroke:var(--progress-circle-active-color, #76c7c0)}[data-v-4ed1ea3e]:root{--calendar-bg-color: #f9f9f9;--calendar-text-color: #333333;--event-bg-color: #ffffff;--event-text-color: #333333;--event-border-color: #dddddd;--details-bg-color: #ffffff;--details-text-color: #333333;--button-bg-color: #007bff;--button-text-color: #ffffff}.calendar[data-v-4ed1ea3e]{background-color:var(--calendar-bg-color);color:var(--calendar-text-color);padding:1rem}.filter-options[data-v-4ed1ea3e]{display:flex;gap:1rem;margin-bottom:1rem}.events[data-v-4ed1ea3e]{display:grid;grid-template-columns:repeat(auto-fill,minmax(200px,1fr));gap:1rem}.event[data-v-4ed1ea3e]{background-color:var(--event-bg-color);color:var(--event-text-color);border:1px solid var(--event-border-color);padding:1rem;cursor:pointer}.event-details[data-v-4ed1ea3e]{background-color:var(--details-bg-color);color:var(--details-text-color);padding:1rem;border:1px solid var(--event-border-color);position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);max-width:400px;width:80%}.close-details[data-v-4ed1ea3e]{background-color:var(--button-bg-color);color:var(--button-text-color);border:none;padding:.5rem 1rem;cursor:pointer}@media (max-width: 600px){.events[data-v-4ed1ea3e]{grid-template-columns:1fr}}[data-v-31d50aca]:root{--radio-color: #007bff;--radio-disabled-color: #6c757d;--radio-border-color: #ced4da;--radio-label-color: #212529}.radio-button-container[data-v-31d50aca]{display:flex;align-items:center}input[type=radio][data-v-31d50aca]{accent-color:var(--radio-color);margin-right:.5rem}input[type=radio][disabled][data-v-31d50aca]{accent-color:var(--radio-disabled-color)}.radio-label[data-v-31d50aca]{color:var(--radio-label-color)}label.disabled .radio-label[data-v-31d50aca]{color:var(--radio-disabled-color)}@media (max-width: 576px){.radio-button-container[data-v-31d50aca]{flex-direction:column}}@media (min-width: 577px) and (max-width: 768px){.radio-button-container[data-v-31d50aca]{flex-direction:row}}@media (min-width: 769px) and (max-width: 992px){.radio-button-container[data-v-31d50aca]{flex-direction:row}}@media (min-width: 993px){.radio-button-container[data-v-31d50aca]{flex-direction:row}}.raise-button[data-v-cd38f00c]{padding:var(--button-padding);font-size:var(--button-font-size);background-color:var(--raise-button-bg-color);color:var(--button-text-color);border:none;border-radius:var(--button-border-radius);cursor:pointer;transition:background-color .3s,transform .1s}.raise-button[data-v-cd38f00c]:hover:not(:disabled){background-color:var(--raise-button-hover-bg-color)}.raise-button[data-v-cd38f00c]:disabled{background-color:var(--button-disabled-bg-color);cursor:not-allowed}.raise-button[data-v-cd38f00c]:active:not(:disabled){transform:scale(.95)}[data-v-99cac0a4]:root{--slider-track-color: #ddd;--slider-thumb-color: #007bff;--slider-disabled-color: #6c757d;--slider-hover-color: #0056b3}.range-slider-container[data-v-99cac0a4]{display:flex;align-items:center}input[type=range][data-v-99cac0a4]{-webkit-appearance:none;width:100%;margin:0 .5rem;background-color:transparent}input[type=range][data-v-99cac0a4]:focus{outline:none}input[type=range][data-v-99cac0a4]::-webkit-slider-runnable-track{height:4px;background:var(--slider-track-color)}input[type=range][data-v-99cac0a4]::-webkit-slider-thumb{-webkit-appearance:none;height:16px;width:16px;border-radius:50%;background:var(--slider-thumb-color);cursor:pointer;transition:background .3s ease;position:relative;top:-6px}input[type=range][data-v-99cac0a4]:hover::-webkit-slider-thumb{background:var(--slider-hover-color)}input[type=range][data-v-99cac0a4]:disabled::-webkit-slider-thumb{background:var(--slider-disabled-color)}.range-label[data-v-99cac0a4]{display:flex;align-items:center;justify-content:center;width:100%}.range-label.left[data-v-99cac0a4]{justify-content:flex-start}.range-label.center[data-v-99cac0a4]{justify-content:center}.range-label.right[data-v-99cac0a4]{justify-content:flex-end}@media (max-width: 576px){.range-slider-container[data-v-99cac0a4]{flex-direction:column}}@media (min-width: 577px) and (max-width: 768px){.range-slider-container[data-v-99cac0a4]{flex-direction:row}}@media (min-width: 769px) and (max-width: 992px){.range-slider-container[data-v-99cac0a4]{flex-direction:row}}@media (min-width: 993px){.range-slider-container[data-v-99cac0a4]{flex-direction:row}}.ranking-poll[data-v-f7cd36d5]{margin:16px}.rank-option[data-v-f7cd36d5]{padding:8px;margin-bottom:4px;background-color:var(--secondary-color);color:#fff;border-radius:4px;cursor:grab;-webkit-user-select:none;user-select:none}.rank-option[data-v-f7cd36d5]:focus{outline:2px solid var(--primary-color)}.rating-stars[data-v-97f264b6]{display:flex;gap:var(--rating-stars-gap, 4px)}.star[data-v-97f264b6]{font-size:var(--rating-stars-size, 24px);color:var(--rating-stars-inactive-color, #dcdcdc);background-color:var(--rating-stars-inactive-bg, #000000);border-color:var(--rating-stars-inactive-border-color, #000000);border-width:var(--rating-stars-inactive-border-width, "10px");border-radius:var(--rating-stars-inactive-border-radius, "10px");cursor:pointer;transition:color .3s}.star.active[data-v-97f264b6],.star.hover[data-v-97f264b6]{color:var(--rating-stars-active-color, #ffcc00);background-color:var(--rating-stars-active-bg, #000000);border-color:var(--rating-stars-active-border-color, #000000);border-width:var(--rating-stars-active-border-width, "10px");border-radius:var(--rating-stars-active-border-radius, "10px")}.rating-stars[aria-disabled=true] .star[data-v-97f264b6]{cursor:not-allowed;color:var(--rating-stars-inactive-color, #dcdcdc)}[data-v-29126a52]:root{--scheduler-bg-color: #ffffff;--scheduler-text-color: #333333;--button-bg-color: #007bff;--button-text-color: #ffffff;--input-border-color: #dddddd;--feedback-bg-color: #e2e3e5;--feedback-text-color: #383d41}.recurring-event-scheduler[data-v-29126a52]{background-color:var(--scheduler-bg-color);color:var(--scheduler-text-color);padding:1rem}.recurrence-settings[data-v-29126a52]{display:flex;flex-direction:column;gap:.5rem;margin-bottom:1rem}button[data-v-29126a52]{background-color:var(--button-bg-color);color:var(--button-text-color);border:none;padding:.5rem 1rem;cursor:pointer}.feedback[data-v-29126a52]{background-color:var(--feedback-bg-color);color:var(--feedback-text-color);padding:.5rem;margin-top:1rem}/*! + * Quill Editor v2.0.3 + * https://quilljs.com + * Copyright (c) 2017-2024, Slab + * Copyright (c) 2014, Jason Chen + * Copyright (c) 2013, salesforce.com + */.ql-container{box-sizing:border-box;font-family:Helvetica,Arial,sans-serif;font-size:13px;height:100%;margin:0;position:relative}.ql-container.ql-disabled .ql-tooltip{visibility:hidden}.ql-container:not(.ql-disabled) li[data-list=checked]>.ql-ui,.ql-container:not(.ql-disabled) li[data-list=unchecked]>.ql-ui{cursor:pointer}.ql-clipboard{left:-100000px;height:1px;overflow-y:hidden;position:absolute;top:50%}.ql-clipboard p{margin:0;padding:0}.ql-editor{box-sizing:border-box;counter-reset:list-0 list-1 list-2 list-3 list-4 list-5 list-6 list-7 list-8 list-9;line-height:1.42;height:100%;outline:none;overflow-y:auto;padding:12px 15px;tab-size:4;-moz-tab-size:4;text-align:left;white-space:pre-wrap;word-wrap:break-word}.ql-editor>*{cursor:text}.ql-editor p,.ql-editor ol,.ql-editor pre,.ql-editor blockquote,.ql-editor h1,.ql-editor h2,.ql-editor h3,.ql-editor h4,.ql-editor h5,.ql-editor h6{margin:0;padding:0}@supports (counter-set:none){.ql-editor p,.ql-editor h1,.ql-editor h2,.ql-editor h3,.ql-editor h4,.ql-editor h5,.ql-editor h6{counter-set:list-0 list-1 list-2 list-3 list-4 list-5 list-6 list-7 list-8 list-9}}@supports not (counter-set:none){.ql-editor p,.ql-editor h1,.ql-editor h2,.ql-editor h3,.ql-editor h4,.ql-editor h5,.ql-editor h6{counter-reset:list-0 list-1 list-2 list-3 list-4 list-5 list-6 list-7 list-8 list-9}}.ql-editor table{border-collapse:collapse}.ql-editor td{border:1px solid #000;padding:2px 5px}.ql-editor ol{padding-left:1.5em}.ql-editor li{list-style-type:none;padding-left:1.5em;position:relative}.ql-editor li>.ql-ui:before{display:inline-block;margin-left:-1.5em;margin-right:.3em;text-align:right;white-space:nowrap;width:1.2em}.ql-editor li[data-list=checked]>.ql-ui,.ql-editor li[data-list=unchecked]>.ql-ui{color:#777}.ql-editor li[data-list=bullet]>.ql-ui:before{content:"•"}.ql-editor li[data-list=checked]>.ql-ui:before{content:"☑"}.ql-editor li[data-list=unchecked]>.ql-ui:before{content:"☐"}@supports (counter-set:none){.ql-editor li[data-list]{counter-set:list-1 list-2 list-3 list-4 list-5 list-6 list-7 list-8 list-9}}@supports not (counter-set:none){.ql-editor li[data-list]{counter-reset:list-1 list-2 list-3 list-4 list-5 list-6 list-7 list-8 list-9}}.ql-editor li[data-list=ordered]{counter-increment:list-0}.ql-editor li[data-list=ordered]>.ql-ui:before{content:counter(list-0,decimal) ". "}.ql-editor li[data-list=ordered].ql-indent-1{counter-increment:list-1}.ql-editor li[data-list=ordered].ql-indent-1>.ql-ui:before{content:counter(list-1,lower-alpha) ". "}@supports (counter-set:none){.ql-editor li[data-list].ql-indent-1{counter-set:list-2 list-3 list-4 list-5 list-6 list-7 list-8 list-9}}@supports not (counter-set:none){.ql-editor li[data-list].ql-indent-1{counter-reset:list-2 list-3 list-4 list-5 list-6 list-7 list-8 list-9}}.ql-editor li[data-list=ordered].ql-indent-2{counter-increment:list-2}.ql-editor li[data-list=ordered].ql-indent-2>.ql-ui:before{content:counter(list-2,lower-roman) ". "}@supports (counter-set:none){.ql-editor li[data-list].ql-indent-2{counter-set:list-3 list-4 list-5 list-6 list-7 list-8 list-9}}@supports not (counter-set:none){.ql-editor li[data-list].ql-indent-2{counter-reset:list-3 list-4 list-5 list-6 list-7 list-8 list-9}}.ql-editor li[data-list=ordered].ql-indent-3{counter-increment:list-3}.ql-editor li[data-list=ordered].ql-indent-3>.ql-ui:before{content:counter(list-3,decimal) ". "}@supports (counter-set:none){.ql-editor li[data-list].ql-indent-3{counter-set:list-4 list-5 list-6 list-7 list-8 list-9}}@supports not (counter-set:none){.ql-editor li[data-list].ql-indent-3{counter-reset:list-4 list-5 list-6 list-7 list-8 list-9}}.ql-editor li[data-list=ordered].ql-indent-4{counter-increment:list-4}.ql-editor li[data-list=ordered].ql-indent-4>.ql-ui:before{content:counter(list-4,lower-alpha) ". "}@supports (counter-set:none){.ql-editor li[data-list].ql-indent-4{counter-set:list-5 list-6 list-7 list-8 list-9}}@supports not (counter-set:none){.ql-editor li[data-list].ql-indent-4{counter-reset:list-5 list-6 list-7 list-8 list-9}}.ql-editor li[data-list=ordered].ql-indent-5{counter-increment:list-5}.ql-editor li[data-list=ordered].ql-indent-5>.ql-ui:before{content:counter(list-5,lower-roman) ". "}@supports (counter-set:none){.ql-editor li[data-list].ql-indent-5{counter-set:list-6 list-7 list-8 list-9}}@supports not (counter-set:none){.ql-editor li[data-list].ql-indent-5{counter-reset:list-6 list-7 list-8 list-9}}.ql-editor li[data-list=ordered].ql-indent-6{counter-increment:list-6}.ql-editor li[data-list=ordered].ql-indent-6>.ql-ui:before{content:counter(list-6,decimal) ". "}@supports (counter-set:none){.ql-editor li[data-list].ql-indent-6{counter-set:list-7 list-8 list-9}}@supports not (counter-set:none){.ql-editor li[data-list].ql-indent-6{counter-reset:list-7 list-8 list-9}}.ql-editor li[data-list=ordered].ql-indent-7{counter-increment:list-7}.ql-editor li[data-list=ordered].ql-indent-7>.ql-ui:before{content:counter(list-7,lower-alpha) ". "}@supports (counter-set:none){.ql-editor li[data-list].ql-indent-7{counter-set:list-8 list-9}}@supports not (counter-set:none){.ql-editor li[data-list].ql-indent-7{counter-reset:list-8 list-9}}.ql-editor li[data-list=ordered].ql-indent-8{counter-increment:list-8}.ql-editor li[data-list=ordered].ql-indent-8>.ql-ui:before{content:counter(list-8,lower-roman) ". "}@supports (counter-set:none){.ql-editor li[data-list].ql-indent-8{counter-set:list-9}}@supports not (counter-set:none){.ql-editor li[data-list].ql-indent-8{counter-reset:list-9}}.ql-editor li[data-list=ordered].ql-indent-9{counter-increment:list-9}.ql-editor li[data-list=ordered].ql-indent-9>.ql-ui:before{content:counter(list-9,decimal) ". "}.ql-editor .ql-indent-1:not(.ql-direction-rtl){padding-left:3em}.ql-editor li.ql-indent-1:not(.ql-direction-rtl){padding-left:4.5em}.ql-editor .ql-indent-1.ql-direction-rtl.ql-align-right{padding-right:3em}.ql-editor li.ql-indent-1.ql-direction-rtl.ql-align-right{padding-right:4.5em}.ql-editor .ql-indent-2:not(.ql-direction-rtl){padding-left:6em}.ql-editor li.ql-indent-2:not(.ql-direction-rtl){padding-left:7.5em}.ql-editor .ql-indent-2.ql-direction-rtl.ql-align-right{padding-right:6em}.ql-editor li.ql-indent-2.ql-direction-rtl.ql-align-right{padding-right:7.5em}.ql-editor .ql-indent-3:not(.ql-direction-rtl){padding-left:9em}.ql-editor li.ql-indent-3:not(.ql-direction-rtl){padding-left:10.5em}.ql-editor .ql-indent-3.ql-direction-rtl.ql-align-right{padding-right:9em}.ql-editor li.ql-indent-3.ql-direction-rtl.ql-align-right{padding-right:10.5em}.ql-editor .ql-indent-4:not(.ql-direction-rtl){padding-left:12em}.ql-editor li.ql-indent-4:not(.ql-direction-rtl){padding-left:13.5em}.ql-editor .ql-indent-4.ql-direction-rtl.ql-align-right{padding-right:12em}.ql-editor li.ql-indent-4.ql-direction-rtl.ql-align-right{padding-right:13.5em}.ql-editor .ql-indent-5:not(.ql-direction-rtl){padding-left:15em}.ql-editor li.ql-indent-5:not(.ql-direction-rtl){padding-left:16.5em}.ql-editor .ql-indent-5.ql-direction-rtl.ql-align-right{padding-right:15em}.ql-editor li.ql-indent-5.ql-direction-rtl.ql-align-right{padding-right:16.5em}.ql-editor .ql-indent-6:not(.ql-direction-rtl){padding-left:18em}.ql-editor li.ql-indent-6:not(.ql-direction-rtl){padding-left:19.5em}.ql-editor .ql-indent-6.ql-direction-rtl.ql-align-right{padding-right:18em}.ql-editor li.ql-indent-6.ql-direction-rtl.ql-align-right{padding-right:19.5em}.ql-editor .ql-indent-7:not(.ql-direction-rtl){padding-left:21em}.ql-editor li.ql-indent-7:not(.ql-direction-rtl){padding-left:22.5em}.ql-editor .ql-indent-7.ql-direction-rtl.ql-align-right{padding-right:21em}.ql-editor li.ql-indent-7.ql-direction-rtl.ql-align-right{padding-right:22.5em}.ql-editor .ql-indent-8:not(.ql-direction-rtl){padding-left:24em}.ql-editor li.ql-indent-8:not(.ql-direction-rtl){padding-left:25.5em}.ql-editor .ql-indent-8.ql-direction-rtl.ql-align-right{padding-right:24em}.ql-editor li.ql-indent-8.ql-direction-rtl.ql-align-right{padding-right:25.5em}.ql-editor .ql-indent-9:not(.ql-direction-rtl){padding-left:27em}.ql-editor li.ql-indent-9:not(.ql-direction-rtl){padding-left:28.5em}.ql-editor .ql-indent-9.ql-direction-rtl.ql-align-right{padding-right:27em}.ql-editor li.ql-indent-9.ql-direction-rtl.ql-align-right{padding-right:28.5em}.ql-editor li.ql-direction-rtl{padding-right:1.5em}.ql-editor li.ql-direction-rtl>.ql-ui:before{margin-left:.3em;margin-right:-1.5em;text-align:left}.ql-editor table{table-layout:fixed;width:100%}.ql-editor table td{outline:none}.ql-editor .ql-code-block-container{font-family:monospace}.ql-editor .ql-video{display:block;max-width:100%}.ql-editor .ql-video.ql-align-center{margin:0 auto}.ql-editor .ql-video.ql-align-right{margin:0 0 0 auto}.ql-editor .ql-bg-black{background-color:#000}.ql-editor .ql-bg-red{background-color:#e60000}.ql-editor .ql-bg-orange{background-color:#f90}.ql-editor .ql-bg-yellow{background-color:#ff0}.ql-editor .ql-bg-green{background-color:#008a00}.ql-editor .ql-bg-blue{background-color:#06c}.ql-editor .ql-bg-purple{background-color:#93f}.ql-editor .ql-color-white{color:#fff}.ql-editor .ql-color-red{color:#e60000}.ql-editor .ql-color-orange{color:#f90}.ql-editor .ql-color-yellow{color:#ff0}.ql-editor .ql-color-green{color:#008a00}.ql-editor .ql-color-blue{color:#06c}.ql-editor .ql-color-purple{color:#93f}.ql-editor .ql-font-serif{font-family:Georgia,Times New Roman,serif}.ql-editor .ql-font-monospace{font-family:Monaco,Courier New,monospace}.ql-editor .ql-size-small{font-size:.75em}.ql-editor .ql-size-large{font-size:1.5em}.ql-editor .ql-size-huge{font-size:2.5em}.ql-editor .ql-direction-rtl{direction:rtl;text-align:inherit}.ql-editor .ql-align-center{text-align:center}.ql-editor .ql-align-justify{text-align:justify}.ql-editor .ql-align-right{text-align:right}.ql-editor .ql-ui{position:absolute}.ql-editor.ql-blank:before{color:#0009;content:attr(data-placeholder);font-style:italic;left:15px;pointer-events:none;position:absolute;right:15px}.ql-snow.ql-toolbar:after,.ql-snow .ql-toolbar:after{clear:both;content:"";display:table}.ql-snow.ql-toolbar button,.ql-snow .ql-toolbar button{background:none;border:none;cursor:pointer;display:inline-block;float:left;height:24px;padding:3px 5px;width:28px}.ql-snow.ql-toolbar button svg,.ql-snow .ql-toolbar button svg{float:left;height:100%}.ql-snow.ql-toolbar button:active:hover,.ql-snow .ql-toolbar button:active:hover{outline:none}.ql-snow.ql-toolbar input.ql-image[type=file],.ql-snow .ql-toolbar input.ql-image[type=file]{display:none}.ql-snow.ql-toolbar button:hover,.ql-snow .ql-toolbar button:hover,.ql-snow.ql-toolbar button:focus,.ql-snow .ql-toolbar button:focus,.ql-snow.ql-toolbar button.ql-active,.ql-snow .ql-toolbar button.ql-active,.ql-snow.ql-toolbar .ql-picker-label:hover,.ql-snow .ql-toolbar .ql-picker-label:hover,.ql-snow.ql-toolbar .ql-picker-label.ql-active,.ql-snow .ql-toolbar .ql-picker-label.ql-active,.ql-snow.ql-toolbar .ql-picker-item:hover,.ql-snow .ql-toolbar .ql-picker-item:hover,.ql-snow.ql-toolbar .ql-picker-item.ql-selected,.ql-snow .ql-toolbar .ql-picker-item.ql-selected{color:#06c}.ql-snow.ql-toolbar button:hover .ql-fill,.ql-snow .ql-toolbar button:hover .ql-fill,.ql-snow.ql-toolbar button:focus .ql-fill,.ql-snow .ql-toolbar button:focus .ql-fill,.ql-snow.ql-toolbar button.ql-active .ql-fill,.ql-snow .ql-toolbar button.ql-active .ql-fill,.ql-snow.ql-toolbar .ql-picker-label:hover .ql-fill,.ql-snow .ql-toolbar .ql-picker-label:hover .ql-fill,.ql-snow.ql-toolbar .ql-picker-label.ql-active .ql-fill,.ql-snow .ql-toolbar .ql-picker-label.ql-active .ql-fill,.ql-snow.ql-toolbar .ql-picker-item:hover .ql-fill,.ql-snow .ql-toolbar .ql-picker-item:hover .ql-fill,.ql-snow.ql-toolbar .ql-picker-item.ql-selected .ql-fill,.ql-snow .ql-toolbar .ql-picker-item.ql-selected .ql-fill,.ql-snow.ql-toolbar button:hover .ql-stroke.ql-fill,.ql-snow .ql-toolbar button:hover .ql-stroke.ql-fill,.ql-snow.ql-toolbar button:focus .ql-stroke.ql-fill,.ql-snow .ql-toolbar button:focus .ql-stroke.ql-fill,.ql-snow.ql-toolbar button.ql-active .ql-stroke.ql-fill,.ql-snow .ql-toolbar button.ql-active .ql-stroke.ql-fill,.ql-snow.ql-toolbar .ql-picker-label:hover .ql-stroke.ql-fill,.ql-snow .ql-toolbar .ql-picker-label:hover .ql-stroke.ql-fill,.ql-snow.ql-toolbar .ql-picker-label.ql-active .ql-stroke.ql-fill,.ql-snow .ql-toolbar .ql-picker-label.ql-active .ql-stroke.ql-fill,.ql-snow.ql-toolbar .ql-picker-item:hover .ql-stroke.ql-fill,.ql-snow .ql-toolbar .ql-picker-item:hover .ql-stroke.ql-fill,.ql-snow.ql-toolbar .ql-picker-item.ql-selected .ql-stroke.ql-fill,.ql-snow .ql-toolbar .ql-picker-item.ql-selected .ql-stroke.ql-fill{fill:#06c}.ql-snow.ql-toolbar button:hover .ql-stroke,.ql-snow .ql-toolbar button:hover .ql-stroke,.ql-snow.ql-toolbar button:focus .ql-stroke,.ql-snow .ql-toolbar button:focus .ql-stroke,.ql-snow.ql-toolbar button.ql-active .ql-stroke,.ql-snow .ql-toolbar button.ql-active .ql-stroke,.ql-snow.ql-toolbar .ql-picker-label:hover .ql-stroke,.ql-snow .ql-toolbar .ql-picker-label:hover .ql-stroke,.ql-snow.ql-toolbar .ql-picker-label.ql-active .ql-stroke,.ql-snow .ql-toolbar .ql-picker-label.ql-active .ql-stroke,.ql-snow.ql-toolbar .ql-picker-item:hover .ql-stroke,.ql-snow .ql-toolbar .ql-picker-item:hover .ql-stroke,.ql-snow.ql-toolbar .ql-picker-item.ql-selected .ql-stroke,.ql-snow .ql-toolbar .ql-picker-item.ql-selected .ql-stroke,.ql-snow.ql-toolbar button:hover .ql-stroke-miter,.ql-snow .ql-toolbar button:hover .ql-stroke-miter,.ql-snow.ql-toolbar button:focus .ql-stroke-miter,.ql-snow .ql-toolbar button:focus .ql-stroke-miter,.ql-snow.ql-toolbar button.ql-active .ql-stroke-miter,.ql-snow .ql-toolbar button.ql-active .ql-stroke-miter,.ql-snow.ql-toolbar .ql-picker-label:hover .ql-stroke-miter,.ql-snow .ql-toolbar .ql-picker-label:hover .ql-stroke-miter,.ql-snow.ql-toolbar .ql-picker-label.ql-active .ql-stroke-miter,.ql-snow .ql-toolbar .ql-picker-label.ql-active .ql-stroke-miter,.ql-snow.ql-toolbar .ql-picker-item:hover .ql-stroke-miter,.ql-snow .ql-toolbar .ql-picker-item:hover .ql-stroke-miter,.ql-snow.ql-toolbar .ql-picker-item.ql-selected .ql-stroke-miter,.ql-snow .ql-toolbar .ql-picker-item.ql-selected .ql-stroke-miter{stroke:#06c}@media (pointer:coarse){.ql-snow.ql-toolbar button:hover:not(.ql-active),.ql-snow .ql-toolbar button:hover:not(.ql-active){color:#444}.ql-snow.ql-toolbar button:hover:not(.ql-active) .ql-fill,.ql-snow .ql-toolbar button:hover:not(.ql-active) .ql-fill,.ql-snow.ql-toolbar button:hover:not(.ql-active) .ql-stroke.ql-fill,.ql-snow .ql-toolbar button:hover:not(.ql-active) .ql-stroke.ql-fill{fill:#444}.ql-snow.ql-toolbar button:hover:not(.ql-active) .ql-stroke,.ql-snow .ql-toolbar button:hover:not(.ql-active) .ql-stroke,.ql-snow.ql-toolbar button:hover:not(.ql-active) .ql-stroke-miter,.ql-snow .ql-toolbar button:hover:not(.ql-active) .ql-stroke-miter{stroke:#444}}.ql-snow,.ql-snow *{box-sizing:border-box}.ql-snow .ql-hidden{display:none}.ql-snow .ql-out-bottom,.ql-snow .ql-out-top{visibility:hidden}.ql-snow .ql-tooltip{position:absolute;transform:translateY(10px)}.ql-snow .ql-tooltip a{cursor:pointer;text-decoration:none}.ql-snow .ql-tooltip.ql-flip{transform:translateY(-10px)}.ql-snow .ql-formats{display:inline-block;vertical-align:middle}.ql-snow .ql-formats:after{clear:both;content:"";display:table}.ql-snow .ql-stroke{fill:none;stroke:#444;stroke-linecap:round;stroke-linejoin:round;stroke-width:2}.ql-snow .ql-stroke-miter{fill:none;stroke:#444;stroke-miterlimit:10;stroke-width:2}.ql-snow .ql-fill,.ql-snow .ql-stroke.ql-fill{fill:#444}.ql-snow .ql-empty{fill:none}.ql-snow .ql-even{fill-rule:evenodd}.ql-snow .ql-thin,.ql-snow .ql-stroke.ql-thin{stroke-width:1}.ql-snow .ql-transparent{opacity:.4}.ql-snow .ql-direction svg:last-child{display:none}.ql-snow .ql-direction.ql-active svg:last-child{display:inline}.ql-snow .ql-direction.ql-active svg:first-child{display:none}.ql-snow .ql-editor h1{font-size:2em}.ql-snow .ql-editor h2{font-size:1.5em}.ql-snow .ql-editor h3{font-size:1.17em}.ql-snow .ql-editor h4{font-size:1em}.ql-snow .ql-editor h5{font-size:.83em}.ql-snow .ql-editor h6{font-size:.67em}.ql-snow .ql-editor a{text-decoration:underline}.ql-snow .ql-editor blockquote{border-left:4px solid #ccc;margin-bottom:5px;margin-top:5px;padding-left:16px}.ql-snow .ql-editor code,.ql-snow .ql-editor .ql-code-block-container{background-color:#f0f0f0;border-radius:3px}.ql-snow .ql-editor .ql-code-block-container{margin-bottom:5px;margin-top:5px;padding:5px 10px}.ql-snow .ql-editor code{font-size:85%;padding:2px 4px}.ql-snow .ql-editor .ql-code-block-container{background-color:#23241f;color:#f8f8f2;overflow:visible}.ql-snow .ql-editor img{max-width:100%}.ql-snow .ql-picker{color:#444;display:inline-block;float:left;font-size:14px;font-weight:500;height:24px;position:relative;vertical-align:middle}.ql-snow .ql-picker-label{cursor:pointer;display:inline-block;height:100%;padding-left:8px;padding-right:2px;position:relative;width:100%}.ql-snow .ql-picker-label:before{display:inline-block;line-height:22px}.ql-snow .ql-picker-options{background-color:#fff;display:none;min-width:100%;padding:4px 8px;position:absolute;white-space:nowrap}.ql-snow .ql-picker-options .ql-picker-item{cursor:pointer;display:block;padding-bottom:5px;padding-top:5px}.ql-snow .ql-picker.ql-expanded .ql-picker-label{color:#ccc;z-index:2}.ql-snow .ql-picker.ql-expanded .ql-picker-label .ql-fill{fill:#ccc}.ql-snow .ql-picker.ql-expanded .ql-picker-label .ql-stroke{stroke:#ccc}.ql-snow .ql-picker.ql-expanded .ql-picker-options{display:block;margin-top:-1px;top:100%;z-index:1}.ql-snow .ql-color-picker,.ql-snow .ql-icon-picker{width:28px}.ql-snow .ql-color-picker .ql-picker-label,.ql-snow .ql-icon-picker .ql-picker-label{padding:2px 4px}.ql-snow .ql-color-picker .ql-picker-label svg,.ql-snow .ql-icon-picker .ql-picker-label svg{right:4px}.ql-snow .ql-icon-picker .ql-picker-options{padding:4px 0}.ql-snow .ql-icon-picker .ql-picker-item{height:24px;width:24px;padding:2px 4px}.ql-snow .ql-color-picker .ql-picker-options{padding:3px 5px;width:152px}.ql-snow .ql-color-picker .ql-picker-item{border:1px solid transparent;float:left;height:16px;margin:2px;padding:0;width:16px}.ql-snow .ql-picker:not(.ql-color-picker):not(.ql-icon-picker) svg{position:absolute;margin-top:-9px;right:0;top:50%;width:18px}.ql-snow .ql-picker.ql-header .ql-picker-label[data-label]:not([data-label=""]):before,.ql-snow .ql-picker.ql-font .ql-picker-label[data-label]:not([data-label=""]):before,.ql-snow .ql-picker.ql-size .ql-picker-label[data-label]:not([data-label=""]):before,.ql-snow .ql-picker.ql-header .ql-picker-item[data-label]:not([data-label=""]):before,.ql-snow .ql-picker.ql-font .ql-picker-item[data-label]:not([data-label=""]):before,.ql-snow .ql-picker.ql-size .ql-picker-item[data-label]:not([data-label=""]):before{content:attr(data-label)}.ql-snow .ql-picker.ql-header{width:98px}.ql-snow .ql-picker.ql-header .ql-picker-label:before,.ql-snow .ql-picker.ql-header .ql-picker-item:before{content:"Normal"}.ql-snow .ql-picker.ql-header .ql-picker-label[data-value="1"]:before,.ql-snow .ql-picker.ql-header .ql-picker-item[data-value="1"]:before{content:"Heading 1"}.ql-snow .ql-picker.ql-header .ql-picker-label[data-value="2"]:before,.ql-snow .ql-picker.ql-header .ql-picker-item[data-value="2"]:before{content:"Heading 2"}.ql-snow .ql-picker.ql-header .ql-picker-label[data-value="3"]:before,.ql-snow .ql-picker.ql-header .ql-picker-item[data-value="3"]:before{content:"Heading 3"}.ql-snow .ql-picker.ql-header .ql-picker-label[data-value="4"]:before,.ql-snow .ql-picker.ql-header .ql-picker-item[data-value="4"]:before{content:"Heading 4"}.ql-snow .ql-picker.ql-header .ql-picker-label[data-value="5"]:before,.ql-snow .ql-picker.ql-header .ql-picker-item[data-value="5"]:before{content:"Heading 5"}.ql-snow .ql-picker.ql-header .ql-picker-label[data-value="6"]:before,.ql-snow .ql-picker.ql-header .ql-picker-item[data-value="6"]:before{content:"Heading 6"}.ql-snow .ql-picker.ql-header .ql-picker-item[data-value="1"]:before{font-size:2em}.ql-snow .ql-picker.ql-header .ql-picker-item[data-value="2"]:before{font-size:1.5em}.ql-snow .ql-picker.ql-header .ql-picker-item[data-value="3"]:before{font-size:1.17em}.ql-snow .ql-picker.ql-header .ql-picker-item[data-value="4"]:before{font-size:1em}.ql-snow .ql-picker.ql-header .ql-picker-item[data-value="5"]:before{font-size:.83em}.ql-snow .ql-picker.ql-header .ql-picker-item[data-value="6"]:before{font-size:.67em}.ql-snow .ql-picker.ql-font{width:108px}.ql-snow .ql-picker.ql-font .ql-picker-label:before,.ql-snow .ql-picker.ql-font .ql-picker-item:before{content:"Sans Serif"}.ql-snow .ql-picker.ql-font .ql-picker-label[data-value=serif]:before,.ql-snow .ql-picker.ql-font .ql-picker-item[data-value=serif]:before{content:"Serif"}.ql-snow .ql-picker.ql-font .ql-picker-label[data-value=monospace]:before,.ql-snow .ql-picker.ql-font .ql-picker-item[data-value=monospace]:before{content:"Monospace"}.ql-snow .ql-picker.ql-font .ql-picker-item[data-value=serif]:before{font-family:Georgia,Times New Roman,serif}.ql-snow .ql-picker.ql-font .ql-picker-item[data-value=monospace]:before{font-family:Monaco,Courier New,monospace}.ql-snow .ql-picker.ql-size{width:98px}.ql-snow .ql-picker.ql-size .ql-picker-label:before,.ql-snow .ql-picker.ql-size .ql-picker-item:before{content:"Normal"}.ql-snow .ql-picker.ql-size .ql-picker-label[data-value=small]:before,.ql-snow .ql-picker.ql-size .ql-picker-item[data-value=small]:before{content:"Small"}.ql-snow .ql-picker.ql-size .ql-picker-label[data-value=large]:before,.ql-snow .ql-picker.ql-size .ql-picker-item[data-value=large]:before{content:"Large"}.ql-snow .ql-picker.ql-size .ql-picker-label[data-value=huge]:before,.ql-snow .ql-picker.ql-size .ql-picker-item[data-value=huge]:before{content:"Huge"}.ql-snow .ql-picker.ql-size .ql-picker-item[data-value=small]:before{font-size:10px}.ql-snow .ql-picker.ql-size .ql-picker-item[data-value=large]:before{font-size:18px}.ql-snow .ql-picker.ql-size .ql-picker-item[data-value=huge]:before{font-size:32px}.ql-snow .ql-color-picker.ql-background .ql-picker-item{background-color:#fff}.ql-snow .ql-color-picker.ql-color .ql-picker-item{background-color:#000}.ql-code-block-container{position:relative}.ql-code-block-container .ql-ui{right:5px;top:5px}.ql-toolbar.ql-snow{border:1px solid #ccc;box-sizing:border-box;font-family:Helvetica Neue,Helvetica,Arial,sans-serif;padding:8px}.ql-toolbar.ql-snow .ql-formats{margin-right:15px}.ql-toolbar.ql-snow .ql-picker-label{border:1px solid transparent}.ql-toolbar.ql-snow .ql-picker-options{border:1px solid transparent;box-shadow:#0003 0 2px 8px}.ql-toolbar.ql-snow .ql-picker.ql-expanded .ql-picker-label,.ql-toolbar.ql-snow .ql-picker.ql-expanded .ql-picker-options{border-color:#ccc}.ql-toolbar.ql-snow .ql-color-picker .ql-picker-item.ql-selected,.ql-toolbar.ql-snow .ql-color-picker .ql-picker-item:hover{border-color:#000}.ql-toolbar.ql-snow+.ql-container.ql-snow{border-top:0}.ql-snow .ql-tooltip{background-color:#fff;border:1px solid #ccc;box-shadow:0 0 5px #ddd;color:#444;padding:5px 12px;white-space:nowrap}.ql-snow .ql-tooltip:before{content:"Visit URL:";line-height:26px;margin-right:8px}.ql-snow .ql-tooltip input[type=text]{display:none;border:1px solid #ccc;font-size:13px;height:26px;margin:0;padding:3px 5px;width:170px}.ql-snow .ql-tooltip a.ql-preview{display:inline-block;max-width:200px;overflow-x:hidden;text-overflow:ellipsis;vertical-align:top}.ql-snow .ql-tooltip a.ql-action:after{border-right:1px solid #ccc;content:"Edit";margin-left:16px;padding-right:8px}.ql-snow .ql-tooltip a.ql-remove:before{content:"Remove";margin-left:8px}.ql-snow .ql-tooltip a{line-height:26px}.ql-snow .ql-tooltip.ql-editing a.ql-preview,.ql-snow .ql-tooltip.ql-editing a.ql-remove{display:none}.ql-snow .ql-tooltip.ql-editing input[type=text]{display:inline-block}.ql-snow .ql-tooltip.ql-editing a.ql-action:after{border-right:0;content:"Save";padding-right:0}.ql-snow .ql-tooltip[data-mode=link]:before{content:"Enter link:"}.ql-snow .ql-tooltip[data-mode=formula]:before{content:"Enter formula:"}.ql-snow .ql-tooltip[data-mode=video]:before{content:"Enter video:"}.ql-snow a{color:#06c}.ql-container.ql-snow{border:1px solid #ccc}[data-v-a295d163]:root{--editor-border-color: #ccc;--editor-background-color: #fff;--editor-readonly-background-color: #f7f7f7}.editor-container[data-v-a295d163]{border:1px solid var(--editor-border-color);background-color:var(--editor-background-color);border-radius:5px;padding:1rem;min-height:200px}.ql-toolbar[data-v-a295d163]{border:none;border-bottom:1px solid var(--editor-border-color)}.ql-container[data-v-a295d163]{border:none}.editor-container[aria-readonly=true][data-v-a295d163]{background-color:var(--editor-readonly-background-color)}@media (max-width: 576px){.editor-container[data-v-a295d163]{padding:.5rem}}@media (min-width: 577px) and (max-width: 768px){.editor-container[data-v-a295d163]{padding:.75rem}}@media (min-width: 769px) and (max-width: 992px){.editor-container[data-v-a295d163]{padding:1rem}}@media (min-width: 993px){.editor-container[data-v-a295d163]{padding:1rem}}.ruler-and-guides[data-v-53005f5f]{position:relative;width:100%;height:100%}.ruler[data-v-53005f5f]{position:absolute;background-color:var(--ruler-bg)}.guides[data-v-53005f5f]{position:relative;width:100%;height:100%}.guide[data-v-53005f5f]{position:absolute;width:1px;height:100%;background-color:var(--guide-color);cursor:pointer}[data-v-4d6e4564]:root{--panel-bg-color: #f0f2f5;--panel-text-color: #333;--input-bg-color: #fff;--input-border-color: #ccc;--button-bg-color: #007bff;--button-text-color: #fff;--feedback-bg-color: #d4edda;--feedback-text-color: #155724}.schedule-crud-panel[data-v-4d6e4564]{background-color:var(--panel-bg-color);color:var(--panel-text-color);padding:1rem;border-radius:5px}.form-group[data-v-4d6e4564]{margin-bottom:1rem}label[data-v-4d6e4564]{display:block;margin-bottom:.5rem}input[data-v-4d6e4564],textarea[data-v-4d6e4564]{width:100%;padding:.5rem;background-color:var(--input-bg-color);border:1px solid var(--input-border-color);border-radius:5px;box-sizing:border-box}.crud-buttons[data-v-4d6e4564]{display:flex;gap:.5rem}button[data-v-4d6e4564]{background-color:var(--button-bg-color);color:var(--button-text-color);border:none;padding:.5rem 1rem;border-radius:5px;cursor:pointer}.feedback[data-v-4d6e4564]{background-color:var(--feedback-bg-color);color:var(--feedback-text-color);padding:.5rem;margin-top:1rem}.scrollable-list[data-v-a0c69f47]{max-height:var(--list-max-height, 200px);overflow-y:auto;border:var(--list-border, 1px solid #ddd);border-radius:var(--list-border-radius, 4px)}.scrollable-list-items[data-v-a0c69f47]{list-style:none;padding:0;margin:0}.scrollable-list-item[data-v-a0c69f47]{padding:var(--list-item-padding, 10px 15px);margin:4px 0;transition:background-color .2s}.scrollable-list-item.hover[data-v-a0c69f47]:not(.disabled){background-color:var(--hover-bg, #e9ecef)}.scrollable-list-item.disabled[data-v-a0c69f47]{color:var(--disabled-color, #ccc);cursor:not-allowed}.end-of-list-message[data-v-a0c69f47]{text-align:center;padding:10px;font-size:.9em;color:var(--end-of-list-color, #555)}.search-bar[data-v-dfc81841]{display:flex;align-items:center;padding:var(--search-bar-padding, 8px);border:var(--search-bar-border, 1px solid #ccc);border-radius:var(--search-bar-border-radius, 4px);background-color:var(--search-bar-bg, #fff);transition:border-color .3s ease}.search-bar input[data-v-dfc81841]{flex:1;border:none;outline:none;padding:var(--search-bar-input-padding, 8px)}.search-bar.focused[data-v-dfc81841]{border-color:var(--search-bar-focused-border-color, #007bff)}.search-bar.disabled[data-v-dfc81841]{background-color:var(--search-bar-disabled-bg, #f0f0f0)}.search-bar-with-suggestions[data-v-7d9836c1]{--input-border-color: #cccccc;--suggestion-bg: #ffffff;--suggestion-hover-bg: #f0f0f0;--no-results-color: #ff0000}input[data-v-7d9836c1]{width:100%;padding:8px;border:1px solid var(--input-border-color);border-radius:4px}.suggestions-list[data-v-7d9836c1]{list-style:none;padding:0;margin:0;border:1px solid var(--input-border-color);border-top:none}.suggestions-list li[data-v-7d9836c1]{padding:8px;background-color:var(--suggestion-bg);cursor:pointer;transition:background-color .3s ease}.suggestions-list li[data-v-7d9836c1]:hover{background-color:var(--suggestion-hover-bg)}.no-results[data-v-7d9836c1]{color:var(--no-results-color);padding:8px}[data-v-8be8e8b9]:root{--search-border-color: #ccc;--search-background-color: #fff;--button-background-color: #007bff;--button-text-color: #fff;--no-results-text-color: #999}.search-container[data-v-8be8e8b9]{display:flex;flex-direction:column;gap:.5rem}input[type=text][data-v-8be8e8b9]{border:1px solid var(--search-border-color);border-radius:5px;padding:.5rem;width:100%;box-sizing:border-box}button[data-v-8be8e8b9]{background-color:var(--button-background-color);color:var(--button-text-color);border:none;padding:.5rem 1rem;border-radius:5px;cursor:pointer}button[aria-pressed=true][data-v-8be8e8b9]{opacity:.7}.no-results[data-v-8be8e8b9]{color:var(--no-results-text-color);font-style:italic}.filter-options[data-v-8be8e8b9]{display:flex;flex-direction:column;gap:.5rem}@media (max-width: 576px){.search-container[data-v-8be8e8b9]{gap:.3rem}input[type=text][data-v-8be8e8b9],button[data-v-8be8e8b9]{padding:.3rem}}@media (min-width: 577px) and (max-width: 768px){.search-container[data-v-8be8e8b9]{gap:.4rem}input[type=text][data-v-8be8e8b9],button[data-v-8be8e8b9]{padding:.4rem}}@media (min-width: 769px) and (max-width: 992px){.search-container[data-v-8be8e8b9]{gap:.5rem}input[type=text][data-v-8be8e8b9],button[data-v-8be8e8b9]{padding:.5rem}}@media (min-width: 993px){.search-container[data-v-8be8e8b9]{gap:.5rem}input[type=text][data-v-8be8e8b9],button[data-v-8be8e8b9]{padding:.5rem}}.search-autocomplete[data-v-5ecf543f]{position:relative;width:100%;max-width:400px}.results-list[data-v-5ecf543f]{position:absolute;width:100%;background-color:var(--results-bg, white);border:1px solid var(--results-border, #ccc);z-index:10;list-style:none;margin:0;padding:0}.results-list li[data-v-5ecf543f]{padding:10px;cursor:pointer}.results-list li[data-v-5ecf543f]:hover{background-color:var(--results-hover-bg, #f0f0f0)}.no-results[data-v-5ecf543f]{color:var(--no-results-color, #ff0000);margin-top:5px}.selectable-list[data-v-213fd947]{border:var(--list-border, 1px solid #ddd);border-radius:var(--list-border-radius, 4px)}.selectable-list-items[data-v-213fd947]{list-style:none;padding:0;margin:0}.selectable-list-item[data-v-213fd947]{padding:var(--list-item-padding, 10px 15px);margin:4px 0;cursor:pointer;transition:background-color .2s}.selectable-list-item.selected[data-v-213fd947]{background-color:var(--selected-bg, #d0eaff)}.selectable-list-item.disabled[data-v-213fd947]{color:var(--disabled-color, #ccc);cursor:not-allowed}.item-details[data-v-213fd947]{padding:var(--details-padding, 8px 12px);background-color:var(--details-bg, #f8f9fa)}.details-button[data-v-213fd947]{background:none;border:none;color:var(--button-color, #007bff);cursor:pointer;padding:0}.shape-library[data-v-f12478ca]{padding:10px;background-color:var(--library-bg)}.shape-list[data-v-f12478ca]{display:flex;flex-wrap:wrap}.shape-item[data-v-f12478ca]{margin:5px;cursor:grab}.shape-item img[data-v-f12478ca]{width:50px;height:50px}input[type=text][data-v-f12478ca]{width:100%;padding:5px;margin-bottom:10px;border:1px solid var(--input-border)}.shape-tool[data-v-f0914fde]{display:flex;flex-direction:column;gap:10px}.shape-tool.active .shape-button[data-v-f0914fde]{border:2px solid var(--shape-tool-active-border)}.shape-settings[data-v-f0914fde]{display:flex;flex-direction:column;gap:5px}.shape-button[data-v-f0914fde]{cursor:pointer}.signal-strength-indicator[data-v-7623f4d7]{display:flex;gap:var(--signal-indicator-gap, 2px);align-items:flex-end}.bar[data-v-7623f4d7]{width:var(--signal-indicator-bar-width, 6px);background-color:var(--signal-indicator-inactive-color, #dcdcdc);transition:background-color .3s}.bar.active[data-v-7623f4d7]{background-color:var(--signal-indicator-active-color, #4caf50)}.poll[data-v-ff3ebf2c]{margin:16px}.option[data-v-ff3ebf2c]{margin-bottom:8px}input[type=radio][data-v-ff3ebf2c]{accent-color:var(--primary-color)}.skeleton-loading[data-v-0ddff634]{display:inline-block;width:100%}.skeleton[data-v-0ddff634]{height:20px;background-color:var(--skeleton-bg, #e0e0e0);animation:shimmer-0ddff634 1.5s infinite}@keyframes shimmer-0ddff634{0%{background-position:-200px 0}to{background-position:200px 0}}.slider-container[data-v-ee851f84]{display:flex;align-items:center;padding:var(--slider-padding, 8px);margin:var(--slider-margin, 8px 0)}.slider-container input[type=range][data-v-ee851f84]{flex:1;margin-right:var(--slider-input-margin-right, 8px);accent-color:var(--slider-accent-color, #007bff)}.slider-container.disabled input[type=range][data-v-ee851f84]{cursor:not-allowed;opacity:.5}.slider-value[data-v-ee851f84]{font-size:var(--slider-value-font-size, 14px);color:var(--slider-value-color, #333)}.slider-poll[data-v-dd791633]{display:flex;flex-direction:column;align-items:center;margin:16px}.slider-label[data-v-dd791633]{font-size:1rem;margin-bottom:8px}.slider[data-v-dd791633]{width:100%;max-width:300px;margin:10px 0}.slider[data-v-dd791633]:disabled{opacity:.5}.results[data-v-dd791633]{font-size:.9rem;color:var(--primary-color)}.sort-control[data-v-07bb3c30]{display:flex;gap:10px}.sort-button[data-v-07bb3c30]{padding:8px;border:1px solid var(--button-border-color);border-radius:4px;cursor:pointer;transition:background-color .3s ease}.sort-button[data-v-07bb3c30]:hover{background-color:var(--button-hover-bg)}.sort-button.disabled[data-v-07bb3c30]{cursor:not-allowed;opacity:.6}.sort-button span[data-v-07bb3c30]{margin-left:5px}.sortable-list[data-v-2f34b2ff]{list-style:none;padding:0;margin:0}.sortable-list li[data-v-2f34b2ff]{padding:10px;margin:5px 0;background-color:var(--list-item-bg);border:var(--list-item-border);cursor:move}.sortable-list li.dragging[data-v-2f34b2ff]{opacity:.5}.sortable-list[aria-disabled=true] li[data-v-2f34b2ff]{cursor:not-allowed;background-color:var(--list-item-disabled-bg)}.sortable-table[data-v-339e4c57]{--table-border: 1px solid #ccc;--selected-row-bg: #f0f8ff;--table-row-hover-bg: #f5f5f5;--sort-icon-color: #007bff;--filter-input-border: 1px solid #ddd}table[data-v-339e4c57]{width:100%;border-collapse:collapse}th[data-v-339e4c57],td[data-v-339e4c57]{padding:8px 12px;border:var(--table-border);text-align:left;cursor:pointer}th[data-v-339e4c57]{position:relative}th span[data-v-339e4c57]{position:absolute;right:10px;color:var(--sort-icon-color)}th span.asc[data-v-339e4c57]:after{content:"▲"}th span.desc[data-v-339e4c57]:after{content:"▼"}tr[data-v-339e4c57]:hover{background-color:var(--table-row-hover-bg)}tr.selected[data-v-339e4c57]{background-color:var(--selected-row-bg)}.filter-input[data-v-339e4c57]{margin-bottom:10px;padding:6px;border:var(--filter-input-border);width:100%;box-sizing:border-box}.star-rating-poll[data-v-f5091b77]{display:flex;flex-direction:column;align-items:center;margin:16px}.stars[data-v-f5091b77]{display:flex}.star[data-v-f5091b77]{font-size:2rem;background:none;border:none;cursor:pointer;color:var(--secondary-color)}.star.filled[data-v-f5091b77]{color:var(--primary-color)}.star[data-v-f5091b77]:focus{outline:2px solid var(--primary-color)}.status-dots[data-v-2e464e2a]{display:inline-flex;align-items:center}.dot[data-v-2e464e2a]{width:var(--status-dot-size, 12px);height:var(--status-dot-size, 12px);border-radius:50%;transition:background-color .3s}.stepper[data-v-f866a8b6]{display:flex;justify-content:space-between;list-style:none;padding:0}.step[data-v-f866a8b6]{display:flex;align-items:center;padding:var(--step-padding, 8px 16px);transition:background-color .3s}.step.completed[data-v-f866a8b6]{background-color:var(--step-completed-bg, #4caf50);color:var(--step-completed-color, #fff)}.step.active[data-v-f866a8b6]{background-color:var(--step-active-bg, #2196f3);color:var(--step-active-color, #fff)}.step.disabled[data-v-f866a8b6]{background-color:var(--step-disabled-bg, #e0e0e0);color:var(--step-disabled-color, #757575)}.step-label[data-v-f866a8b6]{margin-left:var(--step-label-margin, 8px)}.notification-bar[data-v-d2e84c71]{padding:var(--notification-padding, 16px);border-radius:var(--notification-border-radius, 4px);margin:var(--notification-margin, 8px 0);display:flex;align-items:center;justify-content:center}.notification-bar.success[data-v-d2e84c71]{background-color:var(--notification-success-bg, #d4edda);color:var(--notification-success-color, #155724)}.notification-bar.error[data-v-d2e84c71]{background-color:var(--notification-error-bg, #f8d7da);color:var(--notification-error-color, #721c24)}.notification-bar.warning[data-v-d2e84c71]{background-color:var(--notification-warning-bg, #fff3cd);color:var(--notification-warning-color, #856404)}.notification-bar.info[data-v-d2e84c71]{background-color:var(--notification-info-bg, #d1ecf1);color:var(--notification-info-color, #0c5460)}.notification-message[data-v-d2e84c71]{font-size:var(--notification-font-size, 14px)}.tabs[data-v-71783afe]{display:flex;border-bottom:var(--tabs-border)}.tabs button[data-v-71783afe]{padding:10px 20px;background-color:var(--tab-bg);border:none;cursor:pointer;transition:background-color .3s}.tabs button.active[data-v-71783afe]{background-color:var(--tab-active-bg)}.tabs button.disabled[data-v-71783afe]{background-color:var(--tab-disabled-bg);cursor:not-allowed}.tabs button[data-v-71783afe]:hover:not(.active):not(.disabled){background-color:var(--tab-hover-bg)}.checklist[data-v-1aebe86c]{list-style-type:none;padding:0;margin:var(--checklist-margin, 16px 0)}.checklist li[data-v-1aebe86c]{display:flex;align-items:center;padding:var(--checklist-item-padding, 8px 0)}.checklist li.checked .task-label[data-v-1aebe86c]{text-decoration:line-through;color:var(--task-checked-color, #28a745)}.checklist li.unchecked .task-label[data-v-1aebe86c]{color:var(--task-unchecked-color, #6c757d)}.checklist li.partiallyComplete .task-label[data-v-1aebe86c]{color:var(--task-partially-complete-color, #ffc107)}.task-label[data-v-1aebe86c]{margin-left:var(--task-label-margin, 8px)}.text-tool[data-v-a7cdacd4]{display:flex;flex-direction:column;align-items:center}button[data-v-a7cdacd4]{cursor:pointer;padding:8px 16px;border:none;background-color:var(--button-bg);color:var(--button-text-color);transition:background-color .3s}button.active[data-v-a7cdacd4]{background-color:var(--button-active-bg)}.text-options[data-v-a7cdacd4]{margin-top:10px;display:flex;flex-direction:column;align-items:center}label[data-v-a7cdacd4],select[data-v-a7cdacd4],input[data-v-a7cdacd4]{margin-bottom:5px;color:var(--label-text-color)}input[type=color][data-v-a7cdacd4]{border:none;width:50px;height:30px}[data-v-b0ae56b0]:root{--textarea-border-color: #ccc;--textarea-background-color: #fff;--textarea-disabled-background-color: #f5f5f5;--textarea-text-color: #333}.textarea-container[data-v-b0ae56b0]{display:flex}textarea[data-v-b0ae56b0]{border:1px solid var(--textarea-border-color);border-radius:5px;padding:.5rem;width:100%;box-sizing:border-box;background-color:var(--textarea-background-color);color:var(--textarea-text-color);resize:vertical}textarea[data-v-b0ae56b0]:disabled{background-color:var(--textarea-disabled-background-color);cursor:not-allowed}@media (max-width: 576px){textarea[data-v-b0ae56b0]{padding:.3rem}}@media (min-width: 577px) and (max-width: 768px){textarea[data-v-b0ae56b0]{padding:.4rem}}@media (min-width: 769px) and (max-width: 992px){textarea[data-v-b0ae56b0]{padding:.5rem}}@media (min-width: 993px){textarea[data-v-b0ae56b0]{padding:.5rem}}.thumbs-poll[data-v-64adc4b2]{display:flex;flex-direction:column;align-items:center;margin:16px}.thumbs[data-v-64adc4b2]{display:flex}.thumb[data-v-64adc4b2]{font-size:2rem;background:none;border:none;cursor:pointer;margin:0 5px;color:var(--secondary-color)}.thumb.selected[data-v-64adc4b2]{color:var(--primary-color)}.thumb[data-v-64adc4b2]:focus{outline:2px solid var(--primary-color)}[data-v-e10b0b13]:root{--adjuster-bg-color: #f8f9fa;--adjuster-text-color: #212529;--button-bg-color: #28a745;--button-text-color: #ffffff;--timeline-bg-color: #e9ecef;--feedback-bg-color: #d1ecf1;--feedback-text-color: #0c5460}.timeline-adjuster[data-v-e10b0b13]{background-color:var(--adjuster-bg-color);color:var(--adjuster-text-color);padding:1rem}.zoom-controls[data-v-e10b0b13]{display:flex;gap:.5rem;margin-bottom:1rem}.timeline-container[data-v-e10b0b13]{background-color:var(--timeline-bg-color);overflow:hidden;white-space:nowrap;position:relative;height:50px;margin-bottom:1rem}.timeline[data-v-e10b0b13]{display:flex;transition:transform .3s ease}.time-slot[data-v-e10b0b13]{width:100px;text-align:center;line-height:50px;border-right:1px solid #ccc}.navigation[data-v-e10b0b13]{display:flex;gap:.5rem;margin-bottom:1rem}button[data-v-e10b0b13]{background-color:var(--button-bg-color);color:var(--button-text-color);border:none;padding:.5rem 1rem;cursor:pointer}.feedback[data-v-e10b0b13]{background-color:var(--feedback-bg-color);color:var(--feedback-text-color);padding:.5rem;margin-top:1rem}.timeline-list[data-v-553e4b1b]{list-style:none;padding:0;margin:0}.timeline-list li[data-v-553e4b1b]{padding:10px;border-left:var(--timeline-border);position:relative;cursor:pointer}.timeline-list li[data-v-553e4b1b]:before{content:"";position:absolute;top:0;left:-6px;width:12px;height:12px;background-color:var(--timeline-dot-bg);border-radius:50%;transition:background-color .3s}.timeline-list li.active[data-v-553e4b1b]:before{background-color:var(--timeline-active-dot-bg)}.timeline-list li.completed[data-v-553e4b1b]:before{background-color:var(--timeline-completed-dot-bg)}.timeline-list li[data-v-553e4b1b]:hover:before{background-color:var(--timeline-hover-dot-bg)}.timeline-list li.inactive[data-v-553e4b1b]:before{background-color:var(--timeline-inactive-dot-bg)}.toast[data-v-761055db]{display:flex;justify-content:space-between;align-items:center;padding:var(--toast-padding, 16px);border-radius:var(--toast-border-radius, 4px);margin-bottom:var(--toast-margin-bottom, 16px);transition:opacity .3s ease}.toast.success[data-v-761055db]{background-color:var(--toast-success-bg, #d4edda);color:var(--toast-success-color, #155724)}.toast.error[data-v-761055db]{background-color:var(--toast-error-bg, #f8d7da);color:var(--toast-error-color, #721c24)}.toast.warning[data-v-761055db]{background-color:var(--toast-warning-bg, #fff3cd);color:var(--toast-warning-color, #856404)}.toast.info[data-v-761055db]{background-color:var(--toast-info-bg, #d1ecf1);color:var(--toast-info-color, #0c5460)}.close-btn[data-v-761055db]{background:none;border:none;font-size:var(--close-btn-font-size, 16px);cursor:pointer;color:inherit}[data-v-ad0fe00a]:root{--toggle-width: 40px;--toggle-height: 20px;--toggle-bg-color: #ccc;--toggle-checked-bg-color: #4caf50;--toggle-disabled-bg-color: #e0e0e0;--toggle-slider-color: #fff}.toggle-switch[data-v-ad0fe00a]{position:relative;display:inline-block;width:var(--toggle-width);height:var(--toggle-height)}.toggle-switch input[data-v-ad0fe00a]{opacity:0;width:0;height:0}.slider[data-v-ad0fe00a]{position:absolute;cursor:pointer;top:0;left:0;right:0;bottom:0;background-color:var(--toggle-bg-color);transition:.4s;border-radius:var(--toggle-height)}.slider[data-v-ad0fe00a]:before{position:absolute;content:"";height:calc(var(--toggle-height) - 4px);width:calc(var(--toggle-height) - 4px);left:2px;bottom:2px;background-color:var(--toggle-slider-color);transition:.4s;border-radius:50%}input:checked+.slider[data-v-ad0fe00a]{background-color:var(--toggle-checked-bg-color)}input:checked+.slider[data-v-ad0fe00a]:before{transform:translate(calc(var(--toggle-width) - var(--toggle-height)))}input:disabled+.slider[data-v-ad0fe00a]{background-color:var(--toggle-disabled-bg-color);cursor:not-allowed}@media (max-width: 576px){.toggle-switch[data-v-ad0fe00a]{width:30px;height:15px}.slider[data-v-ad0fe00a]:before{height:11px;width:11px}}@media (min-width: 577px) and (max-width: 768px){.toggle-switch[data-v-ad0fe00a]{width:35px;height:17px}.slider[data-v-ad0fe00a]:before{height:13px;width:13px}}@media (min-width: 769px) and (max-width: 992px){.toggle-switch[data-v-ad0fe00a]{width:40px;height:20px}.slider[data-v-ad0fe00a]:before{height:16px;width:16px}}@media (min-width: 993px){.toggle-switch[data-v-ad0fe00a]{width:40px;height:20px}.slider[data-v-ad0fe00a]:before{height:16px;width:16px}}.treeview-list[data-v-140b42d6]{list-style:none;padding:0;margin:0}.treeview-list li[data-v-140b42d6]{padding:10px;position:relative;cursor:pointer}.treeview-list li.selected[data-v-140b42d6]{background-color:var(--treeview-selected-bg)}.treeview-list li .treeview-node[data-v-140b42d6]:hover{background-color:var(--treeview-hover-bg)}.treeview-list li.expanded[data-v-140b42d6]:before{content:"▼";position:absolute;left:-20px;top:10px}.treeview-list li[data-v-140b42d6]:not(.expanded):before{content:"►";position:absolute;left:-20px;top:10px}.undo-redo-buttons[data-v-a47f68b7]{display:flex;gap:10px}button[data-v-a47f68b7]{cursor:pointer;padding:8px 16px;border:none;background-color:var(--button-bg);color:var(--button-text-color);transition:background-color .3s}button[data-v-a47f68b7]:disabled{background-color:var(--button-disabled-bg);cursor:not-allowed}button.active[data-v-a47f68b7]{background-color:var(--button-active-bg)}.upload[data-v-97cfacf5]{padding:var(--upload-padding, 16px);border-radius:var(--upload-border-radius, 4px);margin-bottom:var(--upload-margin-bottom, 16px);display:flex;flex-direction:column;gap:var(--upload-gap, 8px)}.upload.uploading[data-v-97cfacf5]{background-color:var(--upload-uploading-bg, #e0f7fa);color:var(--upload-uploading-color, #00796b)}.upload.downloading[data-v-97cfacf5]{background-color:var(--upload-downloading-bg, #e3f2fd);color:var(--upload-downloading-color, #1976d2)}.upload.completed[data-v-97cfacf5]{background-color:var(--upload-completed-bg, #e8f5e9);color:var(--upload-completed-color, #388e3c)}.upload.paused[data-v-97cfacf5]{background-color:var(--upload-paused-bg, #fff3e0);color:var(--upload-paused-color, #f57c00)}.upload.failed[data-v-97cfacf5]{background-color:var(--upload-failed-bg, #ffebee);color:var(--upload-failed-color, #d32f2f)}.cancel-btn[data-v-97cfacf5]{background:none;border:none;font-size:var(--cancel-btn-font-size, 16px);cursor:pointer;color:inherit}[data-v-3cbe2019]:root{--success-bg-color: #d4edda;--success-text-color: #155724;--error-bg-color: #f8d7da;--error-text-color: #721c24;--warning-bg-color: #fff3cd;--warning-text-color: #856404;--border-radius: 4px;--padding: 10px}.validation-message[data-v-3cbe2019]{padding:var(--padding);border-radius:var(--border-radius);margin-bottom:10px}.success[data-v-3cbe2019]{background-color:var(--success-bg-color);color:var(--success-text-color)}.error[data-v-3cbe2019]{background-color:var(--error-bg-color);color:var(--error-text-color)}.warning[data-v-3cbe2019]{background-color:var(--warning-bg-color);color:var(--warning-text-color)}@media (max-width: 576px){.validation-message[data-v-3cbe2019]{padding:8px}}@media (min-width: 577px) and (max-width: 768px){.validation-message[data-v-3cbe2019]{padding:9px}}@media (min-width: 769px) and (max-width: 992px){.validation-message[data-v-3cbe2019]{padding:10px}}@media (min-width: 993px){.validation-message[data-v-3cbe2019]{padding:10px}}.video-container[data-v-02494477]{width:100%;max-width:800px;margin:0 auto;background-color:var(--video-bg-color)}.video-element[data-v-02494477]{width:100%;display:block}.status[data-v-02494477]{text-align:center;margin-top:1rem}.status-text[data-v-02494477]{color:var(--status-text-color)}.video-player[data-v-2d5e89b2]{width:100%;max-width:800px;margin:0 auto;background-color:var(--player-bg-color)}.video-element[data-v-2d5e89b2]{width:100%;display:block}.controls[data-v-2d5e89b2]{display:flex;justify-content:center;margin-top:1rem}.control-btn[data-v-2d5e89b2]{background-color:var(--button-bg-color);color:var(--button-text-color);border:none;padding:.5rem;margin:0 .5rem;cursor:pointer}.virtualized-list[data-v-a52d90b7]{height:400px;overflow-y:auto;border:1px solid var(--list-border-color)}.list-item[data-v-a52d90b7]{height:50px;display:flex;align-items:center;padding:0 10px;border-bottom:1px solid var(--item-border-color)}.loading-indicator[data-v-a52d90b7],.end-of-list[data-v-a52d90b7]{text-align:center;padding:10px;color:var(--indicator-color)}.focus-indicator[data-v-3d5d03e6]{padding:var(--focus-indicator-padding, 8px);border-radius:var(--focus-indicator-border-radius, 4px);margin-bottom:var(--focus-indicator-margin-bottom, 8px);transition:box-shadow .3s ease;outline:none}.focus-indicator.focused[data-v-3d5d03e6]{box-shadow:var(--focus-indicator-focused-shadow, 0 0 0 3px #007bff);background-color:var(--focus-indicator-focused-bg, #f0f8ff)}.focus-indicator[data-v-3d5d03e6]:not(.focused){background-color:var(--focus-indicator-unfocused-bg, #ffffff)}.winning-hand-display[data-v-65bb899c]{display:flex;flex-direction:column;align-items:center;gap:var(--card-gap);transition:opacity .3s}.hidden[data-v-65bb899c]{opacity:0;pointer-events:none}.cards[data-v-65bb899c]{display:flex;flex-wrap:wrap;justify-content:center;gap:var(--card-gap)}.card[data-v-65bb899c]{padding:var(--card-padding);border:var(--card-border);border-radius:var(--card-border-radius);background-color:var(--card-bg-color)}.community-card[data-v-65bb899c]{background-color:var(--community-card-bg-color)}.winner[data-v-65bb899c]{border-color:var(--winner-border-color)}.yes-no-poll[data-v-5d57e636]{display:flex;flex-direction:column;align-items:center;margin:16px}.options[data-v-5d57e636]{display:flex}.option[data-v-5d57e636]{font-size:1.5rem;background:none;border:1px solid var(--secondary-color);border-radius:4px;cursor:pointer;margin:0 5px;padding:8px 16px;color:var(--secondary-color)}.option.selected[data-v-5d57e636]{background-color:var(--primary-color);color:#fff}.option[data-v-5d57e636]:focus{outline:2px solid var(--primary-color)}.zoom-tool[data-v-e6c5dde0]{display:flex;flex-direction:column;align-items:center}.zoom-controls[data-v-e6c5dde0]{display:flex;align-items:center;margin-bottom:10px}button[data-v-e6c5dde0]{cursor:pointer;padding:8px 16px;border:none;background-color:var(--button-bg);color:var(--button-text-color);transition:background-color .3s}.zoom-level[data-v-e6c5dde0]{margin:0 10px;font-weight:700;color:var(--zoom-text-color)} diff --git a/pkgs/experimental/layout_engine_atoms/src/layout_engine_atoms/runtime/vue/assets/swarma-vue/vue.js b/pkgs/experimental/layout_engine_atoms/src/layout_engine_atoms/runtime/vue/assets/swarma-vue/vue.js new file mode 100644 index 0000000000..28b8c6cef4 --- /dev/null +++ b/pkgs/experimental/layout_engine_atoms/src/layout_engine_atoms/runtime/vue/assets/swarma-vue/vue.js @@ -0,0 +1,15590 @@ +var Al = Object.defineProperty; +var El = (t, e, s) => e in t ? Al(t, e, { enumerable: !0, configurable: !0, writable: !0, value: s }) : t[e] = s; +var B = (t, e, s) => El(t, typeof e != "symbol" ? e + "" : e, s); +import { defineComponent as C, ref as m, onMounted as Ce, createElementBlock as c, openBlock as d, createElementVNode as f, createCommentVNode as P, toDisplayString as w, normalizeClass as q, renderSlot as ie, Fragment as I, renderList as L, withDirectives as R, vModelText as H, vModelSelect as ye, normalizeStyle as ne, computed as W, watch as fs, createStaticVNode as Sl, onUnmounted as Di, withModifiers as de, withKeys as Rs, vShow as lo, createBlock as uo, Transition as co, withCtx as Ri, createTextVNode as Fe, reactive as li, onBeforeUnmount as Tl, vModelCheckbox as _l, createVNode as fo, TransitionGroup as Nl, vModelRadio as Il, resolveComponent as Ll } from "vue"; +const ql = C({ + name: "360DegreeImageViewer", + props: { + images: { + type: Array, + required: !0 + }, + rotationSpeed: { + type: Number, + default: 100 + } + }, + setup(t) { + const e = m(0), s = m(!1), r = m(!0), i = m(t.images[e.value]), a = () => { + s.value = !0, u(); + }, n = () => { + s.value = !1; + }, o = () => { + s.value ? n() : a(); + }, u = () => { + s.value && setTimeout(() => { + e.value = (e.value + 1) % t.images.length, i.value = t.images[e.value], u(); + }, t.rotationSpeed); + }, p = () => { + const b = document.querySelector(".image-container img"); + b.style.transform = "scale(1.5)"; + }, g = () => { + const b = document.querySelector(".image-container img"); + b.style.transform = "scale(1)"; + }, k = (b) => { + b.preventDefault(), n(); + }, y = (b) => { + b.preventDefault(), n(); + }; + return Ce(() => { + r.value = !1, a(); + }), { currentImage: i, rotating: s, loading: r, toggleRotation: o, zoomIn: p, zoomOut: g, onMouseDown: k, onTouchStart: y }; + } +}), A = (t, e) => { + const s = t.__vccOpts || t; + for (const [r, i] of e) + s[r] = i; + return s; +}, Ol = { + class: "image-viewer", + role: "region", + "aria-label": "360-degree image viewer" +}, Pl = ["src"], Dl = { + key: 0, + class: "loading-indicator" +}; +function Rl(t, e, s, r, i, a) { + return d(), c("div", Ol, [ + f("div", { + class: "image-container", + onMousedown: e[0] || (e[0] = (...n) => t.onMouseDown && t.onMouseDown(...n)), + onTouchstart: e[1] || (e[1] = (...n) => t.onTouchStart && t.onTouchStart(...n)) + }, [ + f("img", { + src: t.currentImage, + alt: "360-degree view" + }, null, 8, Pl) + ], 32), + t.loading ? (d(), c("div", Dl, "Loading...")) : P("", !0), + f("button", { + onClick: e[2] || (e[2] = (...n) => t.toggleRotation && t.toggleRotation(...n)), + class: "control-button" + }, w(t.rotating ? "Pause" : "Rotate"), 1), + f("button", { + onClick: e[3] || (e[3] = (...n) => t.zoomIn && t.zoomIn(...n)), + class: "control-button" + }, "Zoom In"), + f("button", { + onClick: e[4] || (e[4] = (...n) => t.zoomOut && t.zoomOut(...n)), + class: "control-button" + }, "Zoom Out") + ]); +} +const B5 = /* @__PURE__ */ A(ql, [["render", Rl], ["__scopeId", "data-v-0fbe72d7"]]), Bl = C({ + name: "Accordion", + props: { + defaultOpen: { + type: Boolean, + default: !1 + } + }, + setup(t) { + const e = m(t.defaultOpen), s = m(!1); + return { isOpen: e, toggleAccordion: () => { + e.value = !e.value; + }, isHovered: s }; + } +}), Ml = ["aria-expanded"], Fl = { + key: 0, + class: "accordion-content" +}; +function Ul(t, e, s, r, i, a) { + return d(), c("div", { + class: "accordion", + onMouseenter: e[1] || (e[1] = (n) => t.isHovered = !0), + onMouseleave: e[2] || (e[2] = (n) => t.isHovered = !1) + }, [ + f("button", { + class: q(["accordion-header", { hovered: t.isHovered }]), + "aria-expanded": t.isOpen, + onClick: e[0] || (e[0] = (...n) => t.toggleAccordion && t.toggleAccordion(...n)) + }, [ + ie(t.$slots, "header", {}, void 0, !0) + ], 10, Ml), + t.isOpen ? (d(), c("div", Fl, [ + ie(t.$slots, "content", {}, void 0, !0) + ])) : P("", !0) + ], 32); +} +const M5 = /* @__PURE__ */ A(Bl, [["render", Ul], ["__scopeId", "data-v-062ad5b7"]]), jl = C({ + name: "ActionableList", + props: { + items: { + type: Array, + required: !0 + } + }, + setup() { + return { hoveredIndex: m(null), triggerAction: (s) => { + console.log(`Action triggered for item at index: ${s}`); + } }; + } +}), Vl = { + class: "actionable-list", + role: "list" +}, Hl = ["onMouseenter"], zl = ["onClick", "disabled", "aria-disabled"], Gl = { + key: 1, + class: "loading-spinner" +}; +function Kl(t, e, s, r, i, a) { + return d(), c("ul", Vl, [ + (d(!0), c(I, null, L(t.items, (n, o) => (d(), c("li", { + key: o, + class: q(["actionable-list-item", { hovered: t.hoveredIndex === o, disabled: n.disabled, loading: n.loading }]), + onMouseenter: (u) => t.hoveredIndex = o, + onMouseleave: e[0] || (e[0] = (u) => t.hoveredIndex = null) + }, [ + f("span", null, w(n.label), 1), + !n.disabled && !n.loading ? (d(), c("button", { + key: 0, + onClick: (u) => t.triggerAction(o), + disabled: n.disabled, + "aria-disabled": n.disabled ? "true" : "false", + class: "action-button" + }, w(n.actionLabel), 9, zl)) : P("", !0), + n.loading ? (d(), c("span", Gl, "Loading...")) : P("", !0) + ], 42, Hl))), 128)) + ]); +} +const F5 = /* @__PURE__ */ A(jl, [["render", Kl], ["__scopeId", "data-v-4a589b9f"]]), Wl = C({ + name: "ActivityIndicators", + props: { + type: { + type: String, + default: "loading", + validator: (t) => ["loading", "success", "error"].includes(t) + }, + message: { + type: String, + default: "Loading..." + } + } +}), Zl = { + key: 0, + class: "spinner" +}, Xl = { key: 1 }; +function Yl(t, e, s, r, i, a) { + return d(), c("div", { + class: q(["activity-indicator", t.type]), + role: "status", + "aria-live": "polite" + }, [ + t.type === "loading" ? (d(), c("span", Zl)) : (d(), c("span", Xl, w(t.message), 1)) + ], 2); +} +const U5 = /* @__PURE__ */ A(Wl, [["render", Yl], ["__scopeId", "data-v-a4d57fae"]]), Ql = C({ + name: "AdminViewScheduler", + props: { + feedbackMessage: { + type: String, + required: !1, + default: "" + }, + addNewEvent: { + type: Function, + required: !1, + default: (t) => { + console.log("Default addNewEvent function", t); + } + }, + editEvent: { + type: Function, + required: !1, + default: (t) => { + console.log(`Default editEvent function: Editing ${t.title}`); + } + }, + deleteEvent: { + type: Function, + required: !1, + default: (t) => { + console.log(`Default deleteEvent function: Deleting event with id ${t}`); + } + } + }, + setup(t) { + const e = m([ + { id: 1, title: "Team Meeting", date: "2024-10-21" }, + { id: 2, title: "Project Deadline", date: "2024-10-25" } + ]), s = m(null), r = m(""), i = m(""), a = m(e.value.length + 1), n = (b) => { + t.addNewEvent(b), e.value.push(b), a.value++; + }, o = (b) => { + const v = e.value.findIndex((E) => E.id === b.id); + v !== -1 && (e.value[v] = { ...b }, t.editEvent(b)); + }, u = (b) => { + e.value = e.value.filter((v) => v.id !== b), t.deleteEvent(b); + }, p = (b) => { + s.value = b; + const v = e.value.find((E) => E.id === b); + v && (r.value = v.title, i.value = v.date); + }, g = (b) => { + s.value === b && (o({ + id: b, + title: r.value, + date: i.value + }), s.value = null); + }, k = () => { + s.value = null; + }, y = (b) => s.value === b; + return { + events: e, + newEventId: a, + editedTitle: r, + editedDate: i, + feedbackMessage: t.feedbackMessage, + handleAddNewEvent: n, + handleEditEvent: o, + handleDeleteEvent: u, + startEdit: p, + saveEdit: g, + cancelEdit: k, + isEditing: y + }; + } +}), Jl = { key: 0 }, xl = { key: 0 }, eu = ["onClick"], tu = ["onClick"], su = { key: 1 }, nu = ["onClick"]; +function ru(t, e, s, r, i, a) { + return d(), c("div", null, [ + e[4] || (e[4] = f("h2", null, "Event Scheduler", -1)), + t.feedbackMessage ? (d(), c("p", Jl, w(t.feedbackMessage), 1)) : P("", !0), + (d(!0), c(I, null, L(t.events, (n) => (d(), c("div", { + key: n.id, + class: "event" + }, [ + t.isEditing(n.id) ? (d(), c("div", su, [ + R(f("input", { + "onUpdate:modelValue": e[0] || (e[0] = (o) => t.editedTitle = o), + placeholder: "Edit title" + }, null, 512), [ + [H, t.editedTitle] + ]), + R(f("input", { + "onUpdate:modelValue": e[1] || (e[1] = (o) => t.editedDate = o), + type: "date", + placeholder: "Edit date" + }, null, 512), [ + [H, t.editedDate] + ]), + f("button", { + onClick: (o) => t.saveEdit(n.id) + }, "Save", 8, nu), + f("button", { + onClick: e[2] || (e[2] = (...o) => t.cancelEdit && t.cancelEdit(...o)) + }, "Cancel") + ])) : (d(), c("div", xl, [ + f("h3", null, w(n.title), 1), + f("p", null, w(n.date), 1), + f("button", { + onClick: (o) => t.startEdit(n.id) + }, "Edit", 8, eu), + f("button", { + onClick: (o) => t.handleDeleteEvent(n.id) + }, "Delete", 8, tu) + ])) + ]))), 128)), + f("button", { + onClick: e[3] || (e[3] = (n) => t.handleAddNewEvent({ id: t.newEventId, title: "New Event", date: "2024-11-01" })) + }, " Add New Event ") + ]); +} +const j5 = /* @__PURE__ */ A(Ql, [["render", ru], ["__scopeId", "data-v-acf13d1f"]]), iu = C({ + name: "AudioPlayer", + props: { + src: { + type: String, + required: !0 + } + }, + setup() { + const t = m(null), e = m(!1), s = m(!1), r = m(1); + return { isPlaying: e, isMuted: s, volume: r, togglePlay: () => { + const u = t.value; + u && (e.value ? u.pause() : u.play(), e.value = !e.value); + }, toggleMute: () => { + const u = t.value; + u && (u.muted = !u.muted, s.value = u.muted); + }, changeVolume: () => { + const u = t.value; + u && (u.volume = r.value); + }, onLoadedData: () => { + const u = t.value; + u && (r.value = u.volume); + } }; + } +}), au = { + class: "audio-player", + role: "region", + "aria-label": "Audio player" +}, ou = ["src"]; +function lu(t, e, s, r, i, a) { + return d(), c("div", au, [ + f("audio", { + ref: "audioElement", + src: t.src, + onLoadeddata: e[0] || (e[0] = (...n) => t.onLoadedData && t.onLoadedData(...n)) + }, null, 40, ou), + f("button", { + onClick: e[1] || (e[1] = (...n) => t.togglePlay && t.togglePlay(...n)), + class: "control-button", + "aria-label": "Play/Pause" + }, w(t.isPlaying ? "Pause" : "Play"), 1), + f("button", { + onClick: e[2] || (e[2] = (...n) => t.toggleMute && t.toggleMute(...n)), + class: "control-button", + "aria-label": "Mute/Unmute" + }, w(t.isMuted ? "Unmute" : "Mute"), 1), + R(f("input", { + type: "range", + class: "volume-control", + min: "0", + max: "1", + step: "0.1", + "onUpdate:modelValue": e[3] || (e[3] = (n) => t.volume = n), + onInput: e[4] || (e[4] = (...n) => t.changeVolume && t.changeVolume(...n)), + "aria-label": "Volume control" + }, null, 544), [ + [H, t.volume] + ]) + ]); +} +const V5 = /* @__PURE__ */ A(iu, [["render", lu], ["__scopeId", "data-v-f1cd7384"]]), uu = C({ + name: "AudioPlayerAdvanced", + props: { + src: { + type: String, + required: !0 + } + }, + setup() { + const t = m(null), e = m(!1), s = m(!1), r = m(1), i = m(0), a = m(0), n = m(1), o = [0.5, 1, 1.5, 2], u = () => { + const v = t.value; + v && (e.value ? v.pause() : v.play(), e.value = !e.value); + }, p = () => { + const v = t.value; + v && (v.muted = !v.muted, s.value = v.muted); + }, g = () => { + const v = t.value; + v && (v.volume = r.value); + }, k = () => { + const v = t.value; + v && (v.currentTime = i.value); + }, y = () => { + const v = t.value; + v && (v.playbackRate = n.value); + }, b = () => { + const v = t.value; + v && (r.value = v.volume, a.value = v.duration); + }; + return Ce(() => { + const v = t.value; + v && v.addEventListener("timeupdate", () => { + i.value = v.currentTime; + }); + }), { + isPlaying: e, + isMuted: s, + volume: r, + currentTime: i, + duration: a, + playbackRate: n, + playbackRates: o, + togglePlay: u, + toggleMute: p, + changeVolume: g, + seekAudio: k, + changeSpeed: y, + onLoadedData: b + }; + } +}), du = { + class: "audio-player-advanced", + role: "region", + "aria-label": "Advanced audio player" +}, cu = ["src"], fu = ["max"], pu = ["value"]; +function hu(t, e, s, r, i, a) { + return d(), c("div", du, [ + f("audio", { + ref: "audioElement", + src: t.src, + onLoadeddata: e[0] || (e[0] = (...n) => t.onLoadedData && t.onLoadedData(...n)) + }, null, 40, cu), + f("button", { + onClick: e[1] || (e[1] = (...n) => t.togglePlay && t.togglePlay(...n)), + class: "control-button", + "aria-label": "Play/Pause" + }, w(t.isPlaying ? "Pause" : "Play"), 1), + f("button", { + onClick: e[2] || (e[2] = (...n) => t.toggleMute && t.toggleMute(...n)), + class: "control-button", + "aria-label": "Mute/Unmute" + }, w(t.isMuted ? "Unmute" : "Mute"), 1), + R(f("input", { + type: "range", + class: "volume-control", + min: "0", + max: "1", + step: "0.1", + "onUpdate:modelValue": e[3] || (e[3] = (n) => t.volume = n), + onInput: e[4] || (e[4] = (...n) => t.changeVolume && t.changeVolume(...n)), + "aria-label": "Volume control" + }, null, 544), [ + [H, t.volume] + ]), + R(f("input", { + type: "range", + class: "seek-bar", + max: t.duration, + "onUpdate:modelValue": e[5] || (e[5] = (n) => t.currentTime = n), + onInput: e[6] || (e[6] = (...n) => t.seekAudio && t.seekAudio(...n)), + "aria-label": "Seek control" + }, null, 40, fu), [ + [H, t.currentTime] + ]), + R(f("select", { + "onUpdate:modelValue": e[7] || (e[7] = (n) => t.playbackRate = n), + onChange: e[8] || (e[8] = (...n) => t.changeSpeed && t.changeSpeed(...n)), + "aria-label": "Playback speed control" + }, [ + (d(!0), c(I, null, L(t.playbackRates, (n) => (d(), c("option", { + key: n, + value: n + }, w(n) + "x", 9, pu))), 128)) + ], 544), [ + [ye, t.playbackRate] + ]) + ]); +} +const H5 = /* @__PURE__ */ A(uu, [["render", hu], ["__scopeId", "data-v-4192c021"]]), gu = C({ + name: "AudioPlayerAdvanced", + props: { + src: { + type: String, + required: !0 + } + }, + setup() { + const t = m(null), e = m(!1), s = m(!1), r = m(1), i = m(0), a = m(0), n = m(1), o = [0.5, 1, 1.5, 2], u = () => { + const v = t.value; + v && (e.value ? v.pause() : v.play(), e.value = !e.value); + }, p = () => { + const v = t.value; + v && (v.muted = !v.muted, s.value = v.muted); + }, g = () => { + const v = t.value; + v && (v.volume = r.value); + }, k = () => { + const v = t.value; + v && (v.currentTime = i.value); + }, y = () => { + const v = t.value; + v && (v.playbackRate = n.value); + }, b = () => { + const v = t.value; + v && (r.value = v.volume, a.value = v.duration); + }; + return Ce(() => { + const v = t.value; + v && v.addEventListener("timeupdate", () => { + i.value = v.currentTime; + }); + }), { + isPlaying: e, + isMuted: s, + volume: r, + currentTime: i, + duration: a, + playbackRate: n, + playbackRates: o, + togglePlay: u, + toggleMute: p, + changeVolume: g, + seekAudio: k, + changeSpeed: y, + onLoadedData: b + }; + } +}), mu = { + class: "audio-player-advanced", + role: "region", + "aria-label": "Advanced audio player" +}, vu = ["src"], bu = ["max"], yu = ["value"]; +function $u(t, e, s, r, i, a) { + return d(), c("div", mu, [ + f("audio", { + ref: "audioElement", + src: t.src, + onLoadeddata: e[0] || (e[0] = (...n) => t.onLoadedData && t.onLoadedData(...n)) + }, null, 40, vu), + f("button", { + onClick: e[1] || (e[1] = (...n) => t.togglePlay && t.togglePlay(...n)), + class: "control-button", + "aria-label": "Play/Pause" + }, w(t.isPlaying ? "Pause" : "Play"), 1), + f("button", { + onClick: e[2] || (e[2] = (...n) => t.toggleMute && t.toggleMute(...n)), + class: "control-button", + "aria-label": "Mute/Unmute" + }, w(t.isMuted ? "Unmute" : "Mute"), 1), + R(f("input", { + type: "range", + class: "volume-control", + min: "0", + max: "1", + step: "0.1", + "onUpdate:modelValue": e[3] || (e[3] = (n) => t.volume = n), + onInput: e[4] || (e[4] = (...n) => t.changeVolume && t.changeVolume(...n)), + "aria-label": "Volume control" + }, null, 544), [ + [H, t.volume] + ]), + R(f("input", { + type: "range", + class: "seek-bar", + max: t.duration, + "onUpdate:modelValue": e[5] || (e[5] = (n) => t.currentTime = n), + onInput: e[6] || (e[6] = (...n) => t.seekAudio && t.seekAudio(...n)), + "aria-label": "Seek control" + }, null, 40, bu), [ + [H, t.currentTime] + ]), + R(f("select", { + "onUpdate:modelValue": e[7] || (e[7] = (n) => t.playbackRate = n), + onChange: e[8] || (e[8] = (...n) => t.changeSpeed && t.changeSpeed(...n)), + "aria-label": "Playback speed control" + }, [ + (d(!0), c(I, null, L(t.playbackRates, (n) => (d(), c("option", { + key: n, + value: n + }, w(n) + "x", 9, yu))), 128)) + ], 544), [ + [ye, t.playbackRate] + ]) + ]); +} +const z5 = /* @__PURE__ */ A(gu, [["render", $u], ["__scopeId", "data-v-81b5aab6"]]), ku = C({ + name: "Avatar", + props: { + imageSrc: { + type: String, + default: "" + }, + initials: { + type: String, + default: "A" + }, + ariaLabel: { + type: String, + default: "User Avatar" + } + } +}), wu = ["aria-label"], Cu = { + key: 1, + class: "avatar placeholder" +}; +function Au(t, e, s, r, i, a) { + return d(), c("div", { + class: "avatar-container", + role: "img", + "aria-label": t.ariaLabel + }, [ + t.imageSrc ? (d(), c("div", { + key: 0, + class: "avatar", + style: ne({ backgroundImage: `url(${t.imageSrc})` }) + }, null, 4)) : (d(), c("div", Cu, w(t.initials), 1)) + ], 8, wu); +} +const G5 = /* @__PURE__ */ A(ku, [["render", Au], ["__scopeId", "data-v-703ea8e3"]]), Eu = C({ + name: "Badge", + props: { + type: { + type: String, + default: "default" + } + }, + setup(t) { + return { badgeClass: W(() => `badge ${t.type}`) }; + } +}); +function Su(t, e, s, r, i, a) { + return d(), c("span", { + class: q(t.badgeClass), + role: "status", + "aria-live": "polite" + }, [ + ie(t.$slots, "default", {}, void 0, !0) + ], 2); +} +const K5 = /* @__PURE__ */ A(Eu, [["render", Su], ["__scopeId", "data-v-373bc344"]]), Tu = C({ + name: "BadgeWithCounts", + props: { + count: { + type: Number, + default: 0 + } + }, + setup(t) { + return { badgeClass: W(() => `badge ${t.count > 0 ? "active" : "zero"}`) }; + } +}), _u = { + key: 0, + class: "overflow" +}, Nu = { key: 1 }; +function Iu(t, e, s, r, i, a) { + return d(), c("span", { + class: q(t.badgeClass), + role: "status", + "aria-live": "polite" + }, [ + t.count > 99 ? (d(), c("span", _u, "99+")) : (d(), c("span", Nu, w(t.count), 1)) + ], 2); +} +const W5 = /* @__PURE__ */ A(Tu, [["render", Iu], ["__scopeId", "data-v-42553945"]]), Lu = C({ + name: "BatteryLevelIndicator", + props: { + level: { + type: Number, + required: !0 + }, + charging: { + type: Boolean, + default: !1 + } + }, + setup(t) { + return { batteryState: W(() => t.charging ? "charging" : t.level > 80 ? "full" : t.level > 20 ? "low" : "critical") }; + } +}), qu = ["aria-valuenow"], Ou = { + key: 0, + class: "charging-icon" +}; +function Pu(t, e, s, r, i, a) { + return d(), c("div", { + class: "battery-container", + role: "progressbar", + "aria-valuemin": "0", + "aria-valuemax": "100", + "aria-valuenow": t.level + }, [ + f("div", { + class: q(["battery", t.batteryState]), + style: ne({ width: `${t.level}%` }) + }, null, 6), + t.charging ? (d(), c("div", Ou, "⚡")) : P("", !0) + ], 8, qu); +} +const Z5 = /* @__PURE__ */ A(Lu, [["render", Pu], ["__scopeId", "data-v-b2d4d309"]]), Du = C({ + name: "BetSlider", + props: { + min: { + type: Number, + default: 0 + }, + max: { + type: Number, + default: 100 + }, + step: { + type: Number, + default: 1 + }, + disabled: { + type: Boolean, + default: !1 + } + }, + setup(t) { + const e = m(t.min), s = () => { + e.value > t.max && (e.value = t.max); + }; + return fs(e, s), { + bet: e, + updateBet: s + }; + } +}), Ru = { + class: "bet-slider", + "aria-label": "Bet Slider" +}, Bu = ["min", "max", "step", "disabled"], Mu = ["min", "max", "disabled"], Fu = { + key: 0, + class: "feedback" +}; +function Uu(t, e, s, r, i, a) { + return d(), c("div", Ru, [ + R(f("input", { + type: "range", + min: t.min, + max: t.max, + step: t.step, + "onUpdate:modelValue": e[0] || (e[0] = (n) => t.bet = n), + disabled: t.disabled, + onInput: e[1] || (e[1] = (...n) => t.updateBet && t.updateBet(...n)), + class: "slider" + }, null, 40, Bu), [ + [H, t.bet] + ]), + R(f("input", { + type: "number", + min: t.min, + max: t.max, + "onUpdate:modelValue": e[2] || (e[2] = (n) => t.bet = n), + disabled: t.disabled, + class: "bet-input", + onChange: e[3] || (e[3] = (...n) => t.updateBet && t.updateBet(...n)) + }, null, 40, Mu), [ + [ + H, + t.bet, + void 0, + { number: !0 } + ] + ]), + t.bet >= t.max ? (d(), c("div", Fu, "Max Bet Reached")) : P("", !0) + ]); +} +const X5 = /* @__PURE__ */ A(Du, [["render", Uu], ["__scopeId", "data-v-2449a36a"]]), ju = C({ + name: "BottomNavigationBar", + props: { + items: { + type: Array, + required: !0 + } + }, + methods: { + onSelect(t) { + this.$emit("update:items", this.items.map((e) => ({ + ...e, + selected: e.label === t.label + }))); + }, + onHover(t) { + console.log(`${t.label} is hovered`); + } + } +}), Vu = { + class: "bottom-navigation-bar", + role: "navigation", + "aria-label": "Bottom Navigation" +}, Hu = { class: "nav-items" }, zu = ["onMouseover", "onClick", "aria-disabled"]; +function Gu(t, e, s, r, i, a) { + return d(), c("nav", Vu, [ + f("ul", Hu, [ + (d(!0), c(I, null, L(t.items, (n) => (d(), c("li", { + key: n.label, + class: q({ selected: n.selected, disabled: n.disabled }), + onMouseover: (o) => n.disabled ? null : t.onHover(n), + onClick: (o) => n.disabled ? null : t.onSelect(n), + "aria-disabled": n.disabled, + tabindex: "0" + }, [ + f("span", null, w(n.label), 1) + ], 42, zu))), 128)) + ]) + ]); +} +const Y5 = /* @__PURE__ */ A(ju, [["render", Gu], ["__scopeId", "data-v-6c91e3f1"]]), Ku = C({ + name: "BreadcrumbWithDropdowns", + props: { + breadcrumbs: { + type: Array, + required: !0 + } + }, + data() { + return { + openDropdownIndex: null + }; + }, + methods: { + navigateTo(t) { + window.location.href = t; + }, + toggleDropdown(t) { + this.openDropdownIndex = this.openDropdownIndex === t ? null : t; + }, + isDropdownOpen(t) { + return this.openDropdownIndex === t; + } + } +}), Wu = { + "aria-label": "Breadcrumb", + class: "breadcrumb" +}, Zu = { class: "breadcrumb-list" }, Xu = ["onClick", "aria-current"], Yu = { + key: 1, + class: "dropdown" +}, Qu = ["onClick", "aria-expanded"], Ju = { + key: 0, + class: "dropdown-menu" +}, xu = ["onClick"]; +function ed(t, e, s, r, i, a) { + return d(), c("nav", Wu, [ + f("ol", Zu, [ + (d(!0), c(I, null, L(t.breadcrumbs, (n, o) => (d(), c("li", { + key: o, + class: "breadcrumb-item" + }, [ + n.dropdown ? (d(), c("div", Yu, [ + f("button", { + onClick: (u) => t.toggleDropdown(o), + "aria-expanded": t.isDropdownOpen(o), + "aria-haspopup": "true" + }, w(n.name), 9, Qu), + t.isDropdownOpen(o) ? (d(), c("ul", Ju, [ + (d(!0), c(I, null, L(n.dropdown, (u, p) => (d(), c("li", { + key: p, + onClick: (g) => t.navigateTo(u.link) + }, w(u.name), 9, xu))), 128)) + ])) : P("", !0) + ])) : (d(), c("span", { + key: 0, + class: q({ "breadcrumb-link": n.link }), + onClick: (u) => n.link ? t.navigateTo(n.link) : null, + "aria-current": o === t.breadcrumbs.length - 1 ? "page" : void 0 + }, w(n.name), 11, Xu)) + ]))), 128)) + ]) + ]); +} +const Q5 = /* @__PURE__ */ A(Ku, [["render", ed], ["__scopeId", "data-v-00e68632"]]), td = C({ + name: "Breadcrumbs", + props: { + breadcrumbs: { + type: Array, + required: !0 + }, + activeIndex: { + type: Number, + default: 0 + } + }, + methods: { + navigateTo(t) { + t && (window.location.href = t); + } + } +}), sd = { + "aria-label": "Breadcrumb", + class: "breadcrumbs" +}, nd = { class: "breadcrumbs-list" }, rd = { + key: 0, + "aria-current": "page", + class: "breadcrumbs-link" +}, id = ["onClick"]; +function ad(t, e, s, r, i, a) { + return d(), c("nav", sd, [ + f("ol", nd, [ + (d(!0), c(I, null, L(t.breadcrumbs, (n, o) => (d(), c("li", { + key: o, + class: q(["breadcrumbs-item", { active: o === t.activeIndex }]) + }, [ + o === t.activeIndex ? (d(), c("span", rd, w(n.name), 1)) : (d(), c("span", { + key: 1, + class: "breadcrumbs-link", + onClick: (u) => t.navigateTo(n.link) + }, w(n.name), 9, id)) + ], 2))), 128)) + ]) + ]); +} +const J5 = /* @__PURE__ */ A(td, [["render", ad], ["__scopeId", "data-v-de61de6d"]]), od = C({ + name: "BrushTool", + setup() { + const t = m(!1), e = m(5), s = m("#000000"), r = m(1); + return { + isActive: t, + brushSize: e, + brushColor: s, + brushOpacity: r, + toggleActive: () => { + t.value = !t.value; + } + }; + } +}), ld = ["aria-pressed"], ud = { + key: 0, + class: "brush-settings" +}; +function dd(t, e, s, r, i, a) { + return d(), c("div", { + class: q(["brush-tool", { active: t.isActive }]) + }, [ + f("button", { + onClick: e[0] || (e[0] = (...n) => t.toggleActive && t.toggleActive(...n)), + "aria-pressed": t.isActive + }, "Brush Tool", 8, ld), + t.isActive ? (d(), c("div", ud, [ + e[4] || (e[4] = f("label", { for: "brushSize" }, "Brush Size", -1)), + R(f("input", { + id: "brushSize", + type: "range", + "onUpdate:modelValue": e[1] || (e[1] = (n) => t.brushSize = n), + min: "1", + max: "20", + "aria-valuemin": "1", + "aria-valuemax": "20", + "aria-valuenow": "brushSize" + }, null, 512), [ + [H, t.brushSize] + ]), + e[5] || (e[5] = f("label", { for: "brushColor" }, "Brush Color", -1)), + R(f("input", { + id: "brushColor", + type: "color", + "onUpdate:modelValue": e[2] || (e[2] = (n) => t.brushColor = n) + }, null, 512), [ + [H, t.brushColor] + ]), + e[6] || (e[6] = f("label", { for: "brushOpacity" }, "Brush Opacity", -1)), + R(f("input", { + id: "brushOpacity", + type: "range", + "onUpdate:modelValue": e[3] || (e[3] = (n) => t.brushOpacity = n), + min: "0.1", + max: "1", + step: "0.1", + "aria-valuemin": "0.1", + "aria-valuemax": "1", + "aria-valuenow": "brushOpacity" + }, null, 512), [ + [H, t.brushOpacity] + ]) + ])) : P("", !0) + ], 2); +} +const x5 = /* @__PURE__ */ A(od, [["render", dd], ["__scopeId", "data-v-aa617421"]]), cd = C({ + name: "Button", + props: { + type: { + type: String, + default: "primary" + }, + disabled: { + type: Boolean, + default: !1 + } + }, + setup(t) { + const e = m(!1), s = m(!1), r = W(() => t.disabled ? "disabled" : s.value ? "active" : e.value ? "hover" : t.type); + return { + isHover: e, + isActive: s, + buttonType: r + }; + } +}), fd = ["aria-disabled", "disabled"]; +function pd(t, e, s, r, i, a) { + return d(), c("button", { + class: q(["button", t.buttonType, { disabled: t.disabled }]), + "aria-disabled": t.disabled, + disabled: t.disabled, + onMouseover: e[0] || (e[0] = (n) => t.isHover = !0), + onMouseleave: e[1] || (e[1] = (n) => t.isHover = !1), + onMousedown: e[2] || (e[2] = (n) => t.isActive = !0), + onMouseup: e[3] || (e[3] = (n) => t.isActive = !1) + }, [ + ie(t.$slots, "default", {}, void 0, !0) + ], 42, fd); +} +const eT = /* @__PURE__ */ A(cd, [["render", pd], ["__scopeId", "data-v-3457f604"]]), hd = C({ + name: "CalendarView", + props: { + currentView: { + type: String, + required: !0, + validator: (t) => ["day", "week", "month", "year", "agenda"].includes(t) + } + }, + setup(t) { + const e = m(t.currentView), s = W(() => { + switch (e.value) { + case "day": + return "Day View"; + case "week": + return "Week View"; + case "month": + return "Month View"; + case "year": + return "Year View"; + case "agenda": + return "Agenda View"; + } + }); + return { + currentView: e, + currentViewTitle: s, + goToPrevious: () => { + }, + goToNext: () => { + } + }; + } +}), gd = { class: "calendar-view" }, md = { + class: "calendar-header", + "aria-label": "Calendar Navigation" +}; +function vd(t, e, s, r, i, a) { + return d(), c("div", gd, [ + f("div", md, [ + f("button", { + onClick: e[0] || (e[0] = (...n) => t.goToPrevious && t.goToPrevious(...n)), + "aria-label": "Previous" + }, "Prev"), + f("h2", null, w(t.currentViewTitle), 1), + f("button", { + onClick: e[1] || (e[1] = (...n) => t.goToNext && t.goToNext(...n)), + "aria-label": "Next" + }, "Next"), + R(f("select", { + "onUpdate:modelValue": e[2] || (e[2] = (n) => t.currentView = n), + "aria-label": "Change View" + }, [...e[3] || (e[3] = [ + Sl('', 5) + ])], 512), [ + [ye, t.currentView] + ]) + ]), + e[4] || (e[4] = f("div", { + class: "calendar-content", + role: "grid" + }, null, -1)) + ]); +} +const tT = /* @__PURE__ */ A(hd, [["render", vd], ["__scopeId", "data-v-f2b44818"]]), bd = C({ + name: "CallButton", + props: { + disabled: { + type: Boolean, + default: !1 + } + } +}), yd = ["disabled"]; +function $d(t, e, s, r, i, a) { + return d(), c("button", { + class: "call-button", + disabled: t.disabled, + "aria-label": "Call Bet" + }, " Call ", 8, yd); +} +const sT = /* @__PURE__ */ A(bd, [["render", $d], ["__scopeId", "data-v-cbb8bf14"]]), kd = C({ + name: "Canvas", + setup() { + const t = m(null), e = m(window.innerWidth), s = m(window.innerHeight), r = m(5), i = m("#000000"); + let a = !1; + const n = (g) => { + a = !0, u(g); + }, o = () => { + var k; + a = !1; + const g = (k = t.value) == null ? void 0 : k.getContext("2d"); + g && g.beginPath(); + }, u = (g) => { + if (!a || !t.value) return; + const k = t.value.getContext("2d"); + if (!k) return; + const y = t.value.getBoundingClientRect(), b = "touches" in g ? g.touches[0].clientX - y.left : g.clientX - y.left, v = "touches" in g ? g.touches[0].clientY - y.top : g.clientY - y.top; + k.lineWidth = r.value, k.lineCap = "round", k.strokeStyle = i.value, k.lineTo(b, v), k.stroke(), k.beginPath(), k.moveTo(b, v); + }, p = () => { + var k; + const g = (k = t.value) == null ? void 0 : k.getContext("2d"); + g && g.clearRect(0, 0, e.value, s.value); + }; + return Ce(() => { + window.addEventListener("resize", () => { + e.value = window.innerWidth, s.value = window.innerHeight; + }); + }), { + canvas: t, + canvasWidth: e, + canvasHeight: s, + brushSize: r, + brushColor: i, + startDrawing: n, + stopDrawing: o, + draw: u, + clearCanvas: p + }; + } +}), wd = ["width", "height"], Cd = { class: "controls" }; +function Ad(t, e, s, r, i, a) { + return d(), c("div", { + class: "canvas-container", + onMousedown: e[3] || (e[3] = (...n) => t.startDrawing && t.startDrawing(...n)), + onMouseup: e[4] || (e[4] = (...n) => t.stopDrawing && t.stopDrawing(...n)), + onMousemove: e[5] || (e[5] = (...n) => t.draw && t.draw(...n)), + onTouchstart: e[6] || (e[6] = (...n) => t.startDrawing && t.startDrawing(...n)), + onTouchend: e[7] || (e[7] = (...n) => t.stopDrawing && t.stopDrawing(...n)), + onTouchmove: e[8] || (e[8] = (...n) => t.draw && t.draw(...n)) + }, [ + f("canvas", { + ref: "canvas", + width: t.canvasWidth, + height: t.canvasHeight, + "aria-label": "Interactive Drawing Canvas" + }, null, 8, wd), + f("div", Cd, [ + f("button", { + onClick: e[0] || (e[0] = (...n) => t.clearCanvas && t.clearCanvas(...n)), + "aria-label": "Clear Canvas" + }, "Clear"), + e[9] || (e[9] = f("label", { for: "brushSize" }, "Brush Size", -1)), + R(f("input", { + id: "brushSize", + type: "range", + "onUpdate:modelValue": e[1] || (e[1] = (n) => t.brushSize = n), + min: "1", + max: "10", + "aria-valuemin": "1", + "aria-valuemax": "10", + "aria-valuenow": "brushSize" + }, null, 512), [ + [H, t.brushSize] + ]), + e[10] || (e[10] = f("label", { for: "brushColor" }, "Brush Color", -1)), + R(f("input", { + id: "brushColor", + type: "color", + "onUpdate:modelValue": e[2] || (e[2] = (n) => t.brushColor = n) + }, null, 512), [ + [H, t.brushColor] + ]) + ]) + ], 32); +} +const nT = /* @__PURE__ */ A(kd, [["render", Ad], ["__scopeId", "data-v-ca98a860"]]), Ed = C({ + name: "Captcha", + props: { + captchaText: { + type: String, + default: "Please solve the captcha" + } + }, + setup(t) { + const e = m(!1), s = m(!1); + return { + isSolved: e, + hasError: s, + solveCaptcha: () => { + t.captchaText === "Please solve the captcha" ? (e.value = !0, s.value = !1) : s.value = !0; + } + }; + } +}), Sd = { class: "captcha-container" }, Td = { key: 0 }, _d = { key: 1 }, Nd = ["disabled"]; +function Id(t, e, s, r, i, a) { + return d(), c("div", Sd, [ + f("div", { + class: q(["captcha", { "captcha--error": t.hasError, "captcha--solved": t.isSolved }]) + }, [ + t.isSolved ? (d(), c("span", _d, "✔ Solved")) : (d(), c("span", Td, w(t.captchaText), 1)) + ], 2), + f("button", { + onClick: e[0] || (e[0] = (...n) => t.solveCaptcha && t.solveCaptcha(...n)), + disabled: t.isSolved, + "aria-label": "solve captcha" + }, "Solve", 8, Nd) + ]); +} +const rT = /* @__PURE__ */ A(Ed, [["render", Id], ["__scopeId", "data-v-e6fd93f9"]]), Ld = C({ + name: "CardActions", + props: { + actions: { + type: Array, + required: !0 + } + }, + setup() { + return { hoveredIndex: m(-1) }; + } +}), qd = { class: "card-actions" }, Od = ["disabled", "onMouseover", "onClick"]; +function Pd(t, e, s, r, i, a) { + return d(), c("div", qd, [ + (d(!0), c(I, null, L(t.actions, (n, o) => (d(), c("button", { + key: o, + class: q({ hovered: t.hoveredIndex === o, disabled: n.disabled }), + disabled: n.disabled, + onMouseover: (u) => t.hoveredIndex = o, + onMouseleave: e[0] || (e[0] = (u) => t.hoveredIndex = -1), + onClick: n.onClick + }, w(n.label), 43, Od))), 128)) + ]); +} +const iT = /* @__PURE__ */ A(Ld, [["render", Pd], ["__scopeId", "data-v-10a84f95"]]), Dd = C({ + name: "CardBadge", + props: { + content: { + type: [String, Number], + required: !0 + }, + status: { + type: String, + default: "default" + } + }, + setup(t) { + const e = m(!1); + return { statusClass: W(() => ({ + default: "badge-default", + active: "badge-active", + inactive: "badge-inactive", + hovered: e.value ? "badge-hovered" : "" + })[t.status]), isHovered: e }; + } +}); +function Rd(t, e, s, r, i, a) { + return d(), c("span", { + class: q(["card-badge", t.statusClass]), + onMouseover: e[0] || (e[0] = (n) => t.isHovered = !0), + onMouseleave: e[1] || (e[1] = (n) => t.isHovered = !1) + }, w(t.content), 35); +} +const aT = /* @__PURE__ */ A(Dd, [["render", Rd], ["__scopeId", "data-v-f22b6cfb"]]), Bd = C({ + name: "CardBody", + props: { + expanded: { + type: Boolean, + default: !0 + }, + collapsible: { + type: Boolean, + default: !1 + }, + ariaLabel: { + type: String, + default: "Card Body" + } + }, + setup(t) { + const e = m(t.expanded); + return { isExpanded: e, toggleExpand: () => { + e.value = !e.value; + } }; + } +}), Md = ["aria-expanded", "aria-label"]; +function Fd(t, e, s, r, i, a) { + return d(), c("section", { + class: "card-body", + "aria-expanded": t.expanded, + "aria-label": t.ariaLabel + }, [ + f("div", { + class: q(["card-body__content", { "card-body__content--collapsed": !t.expanded }]) + }, [ + ie(t.$slots, "default", {}, void 0, !0) + ], 2), + t.collapsible ? (d(), c("button", { + key: 0, + class: "card-body__toggle", + onClick: e[0] || (e[0] = (...n) => t.toggleExpand && t.toggleExpand(...n)), + "aria-controls": "card-body-content" + }, w(t.expanded ? "Collapse" : "Expand"), 1)) : P("", !0) + ], 8, Md); +} +const oT = /* @__PURE__ */ A(Bd, [["render", Fd], ["__scopeId", "data-v-b7b4641a"]]), Ud = C({ + name: "CardFooter", + props: { + alignment: { + type: String, + default: "flex-start" + } + } +}); +function jd(t, e, s, r, i, a) { + return d(), c("footer", { + class: "card-footer", + style: ne({ justifyContent: t.alignment }) + }, [ + ie(t.$slots, "default", {}, void 0, !0) + ], 4); +} +const lT = /* @__PURE__ */ A(Ud, [["render", jd], ["__scopeId", "data-v-7d9792f3"]]), Vd = C({ + name: "CardHeader", + props: { + title: { + type: String, + required: !0 + }, + subtitle: { + type: String, + default: "" + }, + image: { + type: String, + default: "" + }, + icon: { + type: String, + default: "" + }, + ariaLabel: { + type: String, + default: "Card Header" + } + } +}), Hd = ["aria-label"], zd = ["src"], Gd = { class: "card-header__text" }, Kd = { class: "card-header__title" }, Wd = { class: "card-header__subtitle" }; +function Zd(t, e, s, r, i, a) { + return d(), c("header", { + class: "card-header", + "aria-label": t.ariaLabel + }, [ + t.image ? (d(), c("img", { + key: 0, + src: t.image, + alt: "", + class: "card-header__image" + }, null, 8, zd)) : P("", !0), + t.icon ? (d(), c("i", { + key: 1, + class: q(`card-header__icon ${t.icon}`), + "aria-hidden": "true" + }, null, 2)) : P("", !0), + f("div", Gd, [ + f("h1", Kd, w(t.title), 1), + f("h2", Wd, w(t.subtitle), 1) + ]) + ], 8, Hd); +} +const uT = /* @__PURE__ */ A(Vd, [["render", Zd], ["__scopeId", "data-v-4b83e7e6"]]), Xd = C({ + name: "CardImage", + props: { + src: { + type: String, + required: !0 + }, + caption: { + type: String, + default: "" + }, + overlay: { + type: String, + default: "" + } + }, + setup() { + return { hover: m(!1) }; + } +}), Yd = { + key: 0, + class: "caption" +}, Qd = { + key: 1, + class: "overlay" +}; +function Jd(t, e, s, r, i, a) { + return d(), c("div", { + class: "card-image", + style: ne({ backgroundImage: `url(${t.src})` }), + onMouseover: e[0] || (e[0] = (n) => t.hover = !0), + onMouseleave: e[1] || (e[1] = (n) => t.hover = !1) + }, [ + t.caption ? (d(), c("div", Yd, w(t.caption), 1)) : P("", !0), + t.hover && t.overlay ? (d(), c("div", Qd, w(t.overlay), 1)) : P("", !0) + ], 36); +} +const dT = /* @__PURE__ */ A(Xd, [["render", Jd], ["__scopeId", "data-v-62e35ada"]]), xd = C({ + name: "CardbasedList", + props: { + cards: { + type: Array, + required: !0 + } + }, + setup(t) { + const e = m(null), s = m(null); + return { hoveredIndex: e, selectedIndex: s, selectCard: (i) => { + t.cards[i].disabled && (s.value = i); + } }; + } +}), ec = { + class: "cardbased-list", + role: "list" +}, tc = ["onMouseenter", "onClick", "aria-disabled"], sc = { class: "card-content" }; +function nc(t, e, s, r, i, a) { + return d(), c("div", ec, [ + (d(!0), c(I, null, L(t.cards, (n, o) => (d(), c("div", { + key: o, + class: q(["card", { hovered: t.hoveredIndex === o, selected: t.selectedIndex === o, disabled: n.disabled }]), + onMouseenter: (u) => t.hoveredIndex = o, + onMouseleave: e[0] || (e[0] = (u) => t.hoveredIndex = null), + onClick: (u) => t.selectCard(o), + "aria-disabled": n.disabled ? "true" : "false" + }, [ + f("div", sc, [ + f("h3", null, w(n.title), 1), + f("p", null, w(n.description), 1) + ]) + ], 42, tc))), 128)) + ]); +} +const cT = /* @__PURE__ */ A(xd, [["render", nc], ["__scopeId", "data-v-342a232d"]]), rc = C({ + name: "Carousel", + props: { + slides: { + type: Array, + required: !0 + }, + interval: { + type: Number, + default: 3e3 + } + }, + setup(t) { + const e = m(0); + let s = null; + const r = () => { + e.value = (e.value + 1) % t.slides.length; + }, i = () => { + e.value = (e.value - 1 + t.slides.length) % t.slides.length; + }, a = () => { + s = window.setInterval(r, t.interval); + }, n = () => { + s !== null && (clearInterval(s), s = null); + }; + return Ce(a), Di(n), { + currentIndex: e, + nextSlide: r, + prevSlide: i, + autoPlay: a, + pause: n + }; + } +}), ic = { + class: "carousel", + role: "region", + "aria-label": "Image carousel" +}, ac = ["aria-hidden"], oc = ["src", "alt"]; +function lc(t, e, s, r, i, a) { + return d(), c("div", ic, [ + f("div", { + class: "carousel-inner", + onMouseenter: e[0] || (e[0] = (...n) => t.pause && t.pause(...n)), + onMouseleave: e[1] || (e[1] = (...n) => t.autoPlay && t.autoPlay(...n)) + }, [ + (d(!0), c(I, null, L(t.slides, (n, o) => (d(), c("div", { + key: o, + class: q(["carousel-item", { active: o === t.currentIndex }]), + "aria-hidden": o !== t.currentIndex + }, [ + f("img", { + src: n.src, + alt: n.alt + }, null, 8, oc) + ], 10, ac))), 128)) + ], 32), + f("button", { + class: "carousel-control prev", + onClick: e[2] || (e[2] = (...n) => t.prevSlide && t.prevSlide(...n)), + "aria-label": "Previous slide" + }, "‹"), + f("button", { + class: "carousel-control next", + onClick: e[3] || (e[3] = (...n) => t.nextSlide && t.nextSlide(...n)), + "aria-label": "Next slide" + }, "›") + ]); +} +const fT = /* @__PURE__ */ A(rc, [["render", lc], ["__scopeId", "data-v-0a83c48d"]]), uc = C({ + name: "ChatBubble", + props: { + read: { + type: Boolean, + default: !1 + }, + unread: { + type: Boolean, + default: !1 + }, + active: { + type: Boolean, + default: !1 + } + }, + setup() { + return { isHovered: m(!1) }; + } +}); +function dc(t, e, s, r, i, a) { + return d(), c("div", { + class: q(["chat-bubble", { read: t.read, unread: t.unread, hover: t.isHovered, active: t.active }]), + role: "alert", + "aria-live": "polite", + onMouseover: e[0] || (e[0] = (n) => t.isHovered = !0), + onMouseleave: e[1] || (e[1] = (n) => t.isHovered = !1) + }, [ + ie(t.$slots, "default", {}, void 0, !0) + ], 34); +} +const pT = /* @__PURE__ */ A(uc, [["render", dc], ["__scopeId", "data-v-5ed7d014"]]), cc = C({ + name: "CheckList", + props: { + items: { + type: Array, + required: !0 + } + }, + setup(t) { + return { toggleCheck: (s) => { + const r = t.items[s]; + r.disabled || (r.checked = !r.checked, r.indeterminate && (r.indeterminate = !1)); + } }; + } +}), fc = { + class: "checklist", + role: "group" +}, pc = ["id", "checked", ".indeterminate", "disabled", "onChange"], hc = ["for"]; +function gc(t, e, s, r, i, a) { + return d(), c("div", fc, [ + (d(!0), c(I, null, L(t.items, (n, o) => (d(), c("div", { + key: o, + class: q(["checklist-item", { checked: n.checked, indeterminate: n.indeterminate, disabled: n.disabled }]) + }, [ + f("input", { + type: "checkbox", + id: "checkbox-" + o, + checked: n.checked, + ".indeterminate": n.indeterminate, + disabled: n.disabled, + onChange: (u) => t.toggleCheck(o) + }, null, 40, pc), + f("label", { + for: "checkbox-" + o + }, w(n.label), 9, hc) + ], 2))), 128)) + ]); +} +const hT = /* @__PURE__ */ A(cc, [["render", gc], ["__scopeId", "data-v-daf9afeb"]]), mc = C({ + name: "Checkbox", + props: { + checked: { + type: Boolean, + default: !1 + }, + disabled: { + type: Boolean, + default: !1 + } + } +}), vc = { class: "checkbox-container" }, bc = ["checked", "disabled", "aria-checked", "aria-disabled"]; +function yc(t, e, s, r, i, a) { + return d(), c("div", vc, [ + f("input", { + type: "checkbox", + checked: t.checked, + disabled: t.disabled, + onChange: e[0] || (e[0] = (n) => t.$emit("update:checked", n.target.checked)), + "aria-checked": t.checked, + "aria-disabled": t.disabled + }, null, 40, bc), + f("label", { + class: q({ "checkbox--disabled": t.disabled }) + }, [ + ie(t.$slots, "default", {}, void 0, !0) + ], 2) + ]); +} +const gT = /* @__PURE__ */ A(mc, [["render", yc], ["__scopeId", "data-v-7926a921"]]), $c = C({ + name: "Chips", + props: { + selectable: { + type: Boolean, + default: !1 + }, + removable: { + type: Boolean, + default: !1 + }, + grouped: { + type: Boolean, + default: !1 + } + }, + setup() { + const t = m([ + { label: "Chip 1", selected: !1 }, + { label: "Chip 2", selected: !1 }, + { label: "Chip 3", selected: !1 } + ]); + return { chips: t, toggleSelect: (r) => { + t.value[r].selected = !t.value[r].selected; + }, removeChip: (r) => { + t.value.splice(r, 1); + } }; + } +}), kc = { + class: "chip-container", + role: "list" +}, wc = ["onClick", "aria-pressed"], Cc = ["onClick"]; +function Ac(t, e, s, r, i, a) { + return d(), c("div", kc, [ + (d(!0), c(I, null, L(t.chips, (n, o) => (d(), c("div", { + key: o, + class: q(["chip", { selectable: t.selectable, removable: t.removable }]), + onClick: (u) => t.toggleSelect(o), + role: "listitem", + tabindex: "0", + "aria-pressed": n.selected + }, [ + f("span", null, w(n.label), 1), + t.removable ? (d(), c("button", { + key: 0, + class: "remove-button", + "aria-label": "Remove chip", + onClick: de((u) => t.removeChip(o), ["stop"]) + }, " × ", 8, Cc)) : P("", !0) + ], 10, wc))), 128)) + ]); +} +const mT = /* @__PURE__ */ A($c, [["render", Ac], ["__scopeId", "data-v-8a744eae"]]), Ec = C({ + name: "CollapsibleMenuList", + props: { + items: { + type: Array, + required: !0 + } + }, + setup(t) { + return { toggleExpand: (i) => { + t.items[i].expanded = !t.items[i].expanded; + }, hoverItem: (i) => { + t.items[i].active = !0; + }, unhoverItem: (i) => { + t.items[i].active = !1; + } }; + } +}), Sc = { + class: "collapsible-menu-list", + role: "menu" +}, Tc = ["onMouseenter", "onMouseleave"], _c = ["aria-expanded", "onClick"], Nc = { + key: 0, + class: "submenu", + role: "menu" +}; +function Ic(t, e, s, r, i, a) { + return d(), c("ul", Sc, [ + (d(!0), c(I, null, L(t.items, (n, o) => (d(), c("li", { + key: o, + class: q(["menu-item", { expanded: n.expanded, active: n.active }]), + onMouseenter: (u) => t.hoverItem(o), + onMouseleave: (u) => t.unhoverItem(o) + }, [ + f("button", { + class: "menu-button", + "aria-expanded": n.expanded, + onClick: (u) => t.toggleExpand(o) + }, w(n.label), 9, _c), + n.expanded ? (d(), c("ul", Nc, [ + (d(!0), c(I, null, L(n.subItems, (u, p) => (d(), c("li", { + key: p, + role: "menuitem" + }, w(u), 1))), 128)) + ])) : P("", !0) + ], 42, Tc))), 128)) + ]); +} +const vT = /* @__PURE__ */ A(Ec, [["render", Ic], ["__scopeId", "data-v-e135c15d"]]), Lc = C({ + name: "ColorPicker", + setup() { + const t = m(!1), e = m("#000000"), s = m(e.value), r = m(50), i = m(1), a = m([]), n = () => { + t.value = !t.value; + }, o = () => { + /^#[0-9A-F]{6}$/i.test(s.value) && (e.value = s.value, u(s.value)); + }, u = (g) => { + a.value.includes(g) || (a.value.push(g), a.value.length > 5 && a.value.shift()); + }, p = (g) => { + e.value = g, s.value = g; + }; + return fs(e, (g) => { + s.value = g, u(g); + }), { + isActive: t, + selectedColor: e, + hexCode: s, + brightness: r, + opacity: i, + colorHistory: a, + toggleActive: n, + updateColorFromHex: o, + selectColorFromHistory: p + }; + } +}), qc = ["aria-pressed"], Oc = { + key: 0, + class: "color-settings" +}, Pc = { + class: "color-history", + "aria-label": "Color History" +}, Dc = ["onClick"]; +function Rc(t, e, s, r, i, a) { + return d(), c("div", { + class: q(["color-picker", { active: t.isActive }]) + }, [ + f("button", { + onClick: e[0] || (e[0] = (...n) => t.toggleActive && t.toggleActive(...n)), + "aria-pressed": t.isActive, + class: "color-button" + }, "Color Picker", 8, qc), + t.isActive ? (d(), c("div", Oc, [ + R(f("input", { + type: "color", + "onUpdate:modelValue": e[1] || (e[1] = (n) => t.selectedColor = n), + "aria-label": "Select Color" + }, null, 512), [ + [H, t.selectedColor] + ]), + e[6] || (e[6] = f("label", { for: "hexCode" }, "Hex Code", -1)), + R(f("input", { + id: "hexCode", + type: "text", + "onUpdate:modelValue": e[2] || (e[2] = (n) => t.hexCode = n), + onInput: e[3] || (e[3] = (...n) => t.updateColorFromHex && t.updateColorFromHex(...n)), + "aria-live": "polite" + }, null, 544), [ + [H, t.hexCode] + ]), + e[7] || (e[7] = f("label", { for: "brightness" }, "Brightness", -1)), + R(f("input", { + id: "brightness", + type: "range", + "onUpdate:modelValue": e[4] || (e[4] = (n) => t.brightness = n), + min: "0", + max: "100", + "aria-valuemin": "0", + "aria-valuemax": "100", + "aria-valuenow": "brightness" + }, null, 512), [ + [H, t.brightness] + ]), + e[8] || (e[8] = f("label", { for: "opacity" }, "Opacity", -1)), + R(f("input", { + id: "opacity", + type: "range", + "onUpdate:modelValue": e[5] || (e[5] = (n) => t.opacity = n), + min: "0", + max: "1", + step: "0.01", + "aria-valuemin": "0", + "aria-valuemax": "1", + "aria-valuenow": "opacity" + }, null, 512), [ + [H, t.opacity] + ]), + f("div", Pc, [ + (d(!0), c(I, null, L(t.colorHistory, (n) => (d(), c("span", { + key: n, + style: ne({ backgroundColor: n }), + class: "color-swatch", + onClick: (o) => t.selectColorFromHistory(n) + }, null, 12, Dc))), 128)) + ]) + ])) : P("", !0) + ], 2); +} +const bT = /* @__PURE__ */ A(Lc, [["render", Rc], ["__scopeId", "data-v-bef9cfe2"]]), Bc = C({ + name: "ColumnVisibilityToggle", + props: { + columns: { + type: Array, + default: () => [] + } + }, + setup(t, { emit: e }) { + return { + toggleColumnVisibility: (r) => { + t.columns[r].visible = !t.columns[r].visible, e("update:columns", t.columns); + } + }; + } +}), Mc = { class: "column-visibility-toggle" }, Fc = ["onClick", "aria-pressed"]; +function Uc(t, e, s, r, i, a) { + return d(), c("div", Mc, [ + f("ul", null, [ + (d(!0), c(I, null, L(t.columns, (n, o) => (d(), c("li", { + key: n.name + }, [ + f("button", { + onClick: (u) => t.toggleColumnVisibility(o), + "aria-pressed": n.visible + }, w(n.visible ? "Hide" : "Show") + " " + w(n.name), 9, Fc) + ]))), 128)) + ]) + ]); +} +const yT = /* @__PURE__ */ A(Bc, [["render", Uc], ["__scopeId", "data-v-b2e7ed29"]]), jc = C({ + name: "CommandPalette", + props: { + isOpen: { + type: Boolean, + default: !1 + } + }, + setup() { + const t = m(""), e = m([ + { id: 1, name: "Command 1" }, + { id: 2, name: "Command 2" }, + { id: 3, name: "Command 3" } + ]), s = m(0), r = W( + () => e.value.filter( + (o) => o.name.toLowerCase().includes(t.value.toLowerCase()) + ) + ), i = () => { + s.value < r.value.length - 1 && s.value++; + }, a = () => { + s.value > 0 && s.value--; + }, n = (o) => { + console.log("Selected command:", o.name); + }; + return Ce(() => { + r.value.length > 0 && (s.value = 0); + }), { searchQuery: t, filteredCommands: r, activeIndex: s, focusNext: i, focusPrev: a, selectCommand: n }; + } +}), Vc = { + class: "command-palette", + role: "dialog", + "aria-modal": "true", + "aria-labelledby": "command-palette-title" +}, Hc = { class: "command-palette-content" }, zc = { + id: "command-list", + class: "command-list", + role: "listbox" +}, Gc = ["onClick", "onKeydown"]; +function Kc(t, e, s, r, i, a) { + return R((d(), c("div", Vc, [ + f("div", Hc, [ + R(f("input", { + type: "text", + "onUpdate:modelValue": e[0] || (e[0] = (n) => t.searchQuery = n), + class: "command-palette-input", + placeholder: "Type a command...", + "aria-controls": "command-list", + onKeydown: [ + e[1] || (e[1] = Rs(de((...n) => t.focusNext && t.focusNext(...n), ["prevent"]), ["arrow-down"])), + e[2] || (e[2] = Rs(de((...n) => t.focusPrev && t.focusPrev(...n), ["prevent"]), ["arrow-up"])) + ] + }, null, 544), [ + [H, t.searchQuery] + ]), + f("ul", zc, [ + (d(!0), c(I, null, L(t.filteredCommands, (n, o) => (d(), c("li", { + key: n.id, + class: q({ active: o === t.activeIndex }), + role: "option", + tabindex: "0", + onClick: (u) => t.selectCommand(n), + onKeydown: [ + Rs(de((u) => t.selectCommand(n), ["prevent"]), ["enter"]), + Rs(de((u) => t.selectCommand(n), ["prevent"]), ["space"]) + ] + }, w(n.name), 43, Gc))), 128)) + ]) + ]) + ], 512)), [ + [lo, t.isOpen] + ]); +} +const $T = /* @__PURE__ */ A(jc, [["render", Kc], ["__scopeId", "data-v-a21814e7"]]), Wc = C({ + name: "CommunityCards", + props: { + cards: { + type: Array, + required: !0 + } + } +}), Zc = { + class: "community-cards", + role: "group", + "aria-label": "Community Cards" +}, Xc = { + key: 0, + x: "50", + y: "75", + "text-anchor": "middle", + class: "card-text" +}; +function Yc(t, e, s, r, i, a) { + return d(), c("div", Zc, [ + (d(!0), c(I, null, L(t.cards, (n) => (d(), c("svg", { + key: n.id, + class: q("card " + n.state), + viewBox: "0 0 100 150", + xmlns: "http://www.w3.org/2000/svg", + "aria-hidden": "true" + }, [ + e[0] || (e[0] = f("rect", { + width: "100", + height: "150", + rx: "10", + ry: "10", + class: "card-background" + }, null, -1)), + n.state === "revealed" ? (d(), c("text", Xc, w(n.label), 1)) : P("", !0) + ], 2))), 128)) + ]); +} +const kT = /* @__PURE__ */ A(Wc, [["render", Yc], ["__scopeId", "data-v-5f3f241e"]]), Qc = C({ + name: "ContextualList", + props: { + items: { + type: Array, + required: !0 + } + }, + setup(t) { + return { triggerAction: (r) => { + t.items[r].actionTriggered = !0; + }, dismissItem: (r) => { + t.items[r].dismissed = !0; + } }; + } +}), Jc = { + class: "contextual-list", + role: "list" +}, xc = ["onClick"], ef = ["onClick"]; +function tf(t, e, s, r, i, a) { + return d(), c("div", Jc, [ + (d(!0), c(I, null, L(t.items, (n, o) => (d(), c("div", { + key: o, + class: q(["list-item", { actionTriggered: n.actionTriggered, dismissed: n.dismissed }]) + }, [ + f("span", null, w(n.label), 1), + f("button", { + onClick: (u) => t.triggerAction(o) + }, "Action", 8, xc), + f("button", { + onClick: (u) => t.dismissItem(o) + }, "Dismiss", 8, ef) + ], 2))), 128)) + ]); +} +const wT = /* @__PURE__ */ A(Qc, [["render", tf], ["__scopeId", "data-v-85d407db"]]), sf = C({ + name: "ContextualNavigation", + props: { + menuItems: { + type: Array, + required: !0 + } + }, + setup() { + const t = m(!1); + return { + isVisible: t, + toggleMenu: () => { + t.value = !t.value; + } + }; + } +}), nf = ["aria-hidden"], rf = ["aria-expanded"], af = { key: 0 }, of = { key: 1 }, lf = { + key: 0, + class: "contextual-menu", + role: "menu" +}, uf = ["href"]; +function df(t, e, s, r, i, a) { + return d(), c("div", { + class: "contextual-navigation", + "aria-hidden": !t.isVisible + }, [ + f("button", { + onClick: e[0] || (e[0] = (...n) => t.toggleMenu && t.toggleMenu(...n)), + class: "contextual-toggle", + "aria-expanded": t.isVisible + }, [ + t.isVisible ? (d(), c("span", af, "Close Menu")) : (d(), c("span", of, "Open Menu")) + ], 8, rf), + t.isVisible ? (d(), c("div", lf, [ + f("ul", null, [ + (d(!0), c(I, null, L(t.menuItems, (n, o) => (d(), c("li", { + key: o, + role: "menuitem" + }, [ + f("a", { + href: n.link + }, w(n.name), 9, uf) + ]))), 128)) + ]) + ])) : P("", !0) + ], 8, nf); +} +const CT = /* @__PURE__ */ A(sf, [["render", df], ["__scopeId", "data-v-12a7fc2e"]]), cf = C({ + name: "CountdownTimer", + props: { + duration: { + type: Number, + required: !0 + } + }, + setup(t) { + const e = m(t.duration), s = m(!1), r = m(null), i = W(() => { + const p = Math.floor(e.value / 60).toString().padStart(2, "0"), g = (e.value % 60).toString().padStart(2, "0"); + return `${p}:${g}`; + }), a = W(() => e.value <= 0 ? "completed" : s.value ? "paused" : "running"), n = W(() => e.value <= 0), o = () => { + n.value || (s.value = !s.value, s.value || u()); + }, u = () => { + r.value !== null && clearInterval(r.value), r.value = window.setInterval(() => { + !s.value && e.value > 0 && (e.value -= 1); + }, 1e3); + }; + return Ce(() => { + u(); + }), Di(() => { + r.value !== null && clearInterval(r.value); + }), { formattedTime: i, timerState: a, isPaused: s, togglePause: o, isCompleted: n }; + } +}), ff = ["aria-live"]; +function pf(t, e, s, r, i, a) { + return d(), c("div", { + class: "countdown-timer", + role: "timer", + "aria-live": t.isCompleted ? "off" : "assertive" + }, [ + f("span", { + class: q(t.timerState) + }, w(t.formattedTime), 3), + f("button", { + onClick: e[0] || (e[0] = (...n) => t.togglePause && t.togglePause(...n)), + "aria-label": "Pause or resume countdown" + }, w(t.isPaused ? "Resume" : "Pause"), 1) + ], 8, ff); +} +const AT = /* @__PURE__ */ A(cf, [["render", pf], ["__scopeId", "data-v-c87bdf2c"]]), hf = C({ + name: "DarkModeToggle", + setup() { + const t = m(!1); + return { isDarkMode: t, toggleMode: () => { + t.value = !t.value, document.documentElement.setAttribute("data-theme", t.value ? "dark" : "light"); + } }; + } +}), gf = ["aria-pressed"], mf = { + key: 0, + "aria-hidden": "true" +}, vf = { + key: 1, + "aria-hidden": "true" +}, bf = { class: "sr-only" }; +function yf(t, e, s, r, i, a) { + return d(), c("button", { + class: "dark-mode-toggle", + "aria-pressed": t.isDarkMode, + onClick: e[0] || (e[0] = (...n) => t.toggleMode && t.toggleMode(...n)) + }, [ + t.isDarkMode ? (d(), c("span", mf, "🌙")) : (d(), c("span", vf, "☀️")), + f("span", bf, w(t.isDarkMode ? "Switch to light mode" : "Switch to dark mode"), 1) + ], 8, gf); +} +const ET = /* @__PURE__ */ A(hf, [["render", yf], ["__scopeId", "data-v-744ba664"]]), $f = C({ + name: "DataExportButton", + props: { + availableFormats: { + type: Array, + default: () => ["csv", "excel", "pdf"] + }, + dataAvailable: { + type: Boolean, + default: !0 + } + }, + setup(t) { + const e = m(!1); + return { + isExporting: e, + startExport: async (r) => { + if (!(!t.dataAvailable || e.value)) { + console.log(r), e.value = !0; + try { + await new Promise((i) => setTimeout(i, 2e3)); + } finally { + e.value = !1; + } + } + } + }; + } +}), kf = { class: "data-export-button" }, wf = ["onClick", "disabled"], Cf = { + key: 0, + class: "loading-indicator" +}; +function Af(t, e, s, r, i, a) { + return d(), c("div", kf, [ + (d(!0), c(I, null, L(t.availableFormats, (n) => (d(), c("button", { + key: n, + onClick: (o) => t.startExport(n), + disabled: !t.dataAvailable || t.isExporting + }, " Export as " + w(n.toUpperCase()), 9, wf))), 128)), + t.isExporting ? (d(), c("div", Cf, "Exporting...")) : P("", !0) + ]); +} +const ST = /* @__PURE__ */ A($f, [["render", Af], ["__scopeId", "data-v-0af20d1b"]]), Ef = C({ + name: "DataFilterPanel", + props: { + filters: { + type: Array, + required: !0 + }, + disabled: Boolean + }, + setup(t) { + const e = m([...t.filters]), s = m(!0), r = () => { + s.value = !s.value; + }, i = () => { + }, a = () => { + e.value = t.filters.map((n) => ({ ...n, value: null })); + }; + return fs(e, i, { deep: !0 }), { + activeFilters: e, + isPanelCollapsed: s, + togglePanel: r, + clearFilters: a + }; + } +}), Sf = { + key: 0, + class: "filters" +}, Tf = { key: 0 }, _f = ["onUpdate:modelValue", "disabled"], Nf = { key: 1 }, If = ["onUpdate:modelValue", "disabled"], Lf = ["value"], qf = { key: 2 }, Of = ["onUpdate:modelValue", "disabled"], Pf = ["disabled"]; +function Df(t, e, s, r, i, a) { + return d(), c("div", { + class: q(["data-filter-panel", { disabled: t.disabled }]) + }, [ + f("button", { + onClick: e[0] || (e[0] = (...n) => t.togglePanel && t.togglePanel(...n)) + }, w(t.isPanelCollapsed ? "Show Filters" : "Hide Filters"), 1), + t.isPanelCollapsed ? P("", !0) : (d(), c("div", Sf, [ + (d(!0), c(I, null, L(t.activeFilters, (n, o) => (d(), c("div", { + key: o, + class: "filter" + }, [ + f("label", null, w(n.label), 1), + n.type === "text" ? (d(), c("div", Tf, [ + R(f("input", { + type: "text", + "onUpdate:modelValue": (u) => n.value = u, + disabled: t.disabled + }, null, 8, _f), [ + [H, n.value] + ]) + ])) : n.type === "dropdown" ? (d(), c("div", Nf, [ + R(f("select", { + "onUpdate:modelValue": (u) => n.value = u, + disabled: t.disabled + }, [ + (d(!0), c(I, null, L(n.options, (u) => (d(), c("option", { + key: u, + value: u + }, w(u), 9, Lf))), 128)) + ], 8, If), [ + [ye, n.value] + ]) + ])) : n.type === "date" ? (d(), c("div", qf, [ + R(f("input", { + type: "date", + "onUpdate:modelValue": (u) => n.value = u, + disabled: t.disabled + }, null, 8, Of), [ + [H, n.value] + ]) + ])) : P("", !0) + ]))), 128)), + f("button", { + onClick: e[1] || (e[1] = (...n) => t.clearFilters && t.clearFilters(...n)), + disabled: t.disabled + }, "Clear Filters", 8, Pf) + ])) + ], 2); +} +const TT = /* @__PURE__ */ A(Ef, [["render", Df], ["__scopeId", "data-v-2739f835"]]), Rf = C({ + name: "DataGrid", + props: { + headers: { + type: Array, + required: !0 + }, + data: { + type: Array, + required: !0 + }, + paginationEnabled: { + type: Boolean, + default: !1 + }, + searchEnabled: { + type: Boolean, + default: !1 + }, + resizable: { + type: Boolean, + default: !1 + }, + itemsPerPage: { + type: Number, + default: 10 + } + }, + setup(t) { + const e = m(1), s = m(""), r = W(() => { + let o = t.data; + if (t.searchEnabled && s.value && (o = o.filter( + (u) => u.some((p) => p.toLowerCase().includes(s.value.toLowerCase())) + )), t.paginationEnabled) { + const u = (e.value - 1) * t.itemsPerPage; + return o.slice(u, u + t.itemsPerPage); + } + return o; + }), i = W(() => Math.ceil(t.data.length / t.itemsPerPage)); + return { page: e, searchQuery: s, filteredData: r, totalPages: i, prevPage: () => { + e.value > 1 && (e.value -= 1); + }, nextPage: () => { + e.value < i.value && (e.value += 1); + } }; + } +}), Bf = { + class: "data-grid", + role: "grid" +}, Mf = { + key: 0, + class: "search-bar" +}, Ff = { + key: 1, + class: "pagination" +}, Uf = ["disabled"], jf = ["disabled"]; +function Vf(t, e, s, r, i, a) { + return d(), c("div", Bf, [ + t.searchEnabled ? (d(), c("div", Mf, [ + R(f("input", { + type: "text", + "onUpdate:modelValue": e[0] || (e[0] = (n) => t.searchQuery = n), + placeholder: "Search...", + "aria-label": "Search Data Grid" + }, null, 512), [ + [H, t.searchQuery] + ]) + ])) : P("", !0), + f("table", null, [ + f("thead", null, [ + f("tr", null, [ + (d(!0), c(I, null, L(t.headers, (n, o) => (d(), c("th", { + key: o, + style: ne({ width: t.resizable ? "auto" : "initial" }) + }, w(n), 5))), 128)) + ]) + ]), + f("tbody", null, [ + (d(!0), c(I, null, L(t.filteredData, (n, o) => (d(), c("tr", { key: o }, [ + (d(!0), c(I, null, L(n, (u, p) => (d(), c("td", { key: p }, w(u), 1))), 128)) + ]))), 128)) + ]) + ]), + t.paginationEnabled ? (d(), c("div", Ff, [ + f("button", { + onClick: e[1] || (e[1] = (...n) => t.prevPage && t.prevPage(...n)), + disabled: t.page === 1, + "aria-label": "Previous Page" + }, "Previous", 8, Uf), + f("span", null, "Page " + w(t.page) + " of " + w(t.totalPages), 1), + f("button", { + onClick: e[2] || (e[2] = (...n) => t.nextPage && t.nextPage(...n)), + disabled: t.page === t.totalPages, + "aria-label": "Next Page" + }, "Next", 8, jf) + ])) : P("", !0) + ]); +} +const _T = /* @__PURE__ */ A(Rf, [["render", Vf], ["__scopeId", "data-v-804b97db"]]), Hf = C({ + name: "DataImportDialog", + setup() { + const t = m(null), e = m(!1), s = m(!1), r = m(null); + return { + file: t, + importing: e, + success: s, + error: r, + onFileChange: (n) => { + const o = n.target; + o.files && (t.value = o.files[0]); + }, + importData: async () => { + if (!t.value) { + r.value = "No file selected"; + return; + } + e.value = !0, r.value = null; + try { + await new Promise((n) => setTimeout(n, 2e3)), s.value = !0; + } catch { + r.value = "Import failed"; + } finally { + e.value = !1; + } + } + }; + } +}), zf = { + class: "data-import-dialog", + role: "dialog", + "aria-labelledby": "dialog-title" +}, Gf = ["disabled"], Kf = { + key: 0, + class: "progress-indicator", + "aria-live": "polite" +}, Wf = { + key: 1, + class: "success-message", + "aria-live": "polite" +}, Zf = { + key: 2, + class: "error-message", + "aria-live": "assertive" +}; +function Xf(t, e, s, r, i, a) { + return d(), c("div", zf, [ + e[2] || (e[2] = f("h2", { id: "dialog-title" }, "Import Data", -1)), + f("input", { + type: "file", + onChange: e[0] || (e[0] = (...n) => t.onFileChange && t.onFileChange(...n)), + accept: ".csv, .xls, .xlsx", + "aria-label": "File upload" + }, null, 32), + f("button", { + onClick: e[1] || (e[1] = (...n) => t.importData && t.importData(...n)), + disabled: t.importing || !t.file + }, " Import ", 8, Gf), + t.importing ? (d(), c("div", Kf, " Importing... ")) : P("", !0), + t.success ? (d(), c("div", Wf, " Import successful! ")) : P("", !0), + t.error ? (d(), c("div", Zf, w(t.error), 1)) : P("", !0) + ]); +} +const NT = /* @__PURE__ */ A(Hf, [["render", Xf], ["__scopeId", "data-v-520df743"]]), Yf = C({ + name: "DataSummary", + props: { + data: { + type: Array, + default: () => [] + } + }, + setup(t) { + const e = m(null), s = m(null), r = m(null), i = m(null); + return fs(() => t.data, () => { + try { + if (!t.data.length) throw new Error("No data available"); + e.value = t.data.reduce((n, o) => n + o, 0), s.value = e.value / t.data.length, r.value = t.data.length, i.value = null; + } catch (n) { + i.value = n.message, e.value = null, s.value = null, r.value = null; + } + }, { immediate: !0 }), { + total: e, + average: s, + count: r, + error: i + }; + } +}), Qf = { class: "data-summary" }, Jf = { + key: 0, + class: "error-message", + role: "alert" +}, xf = { key: 1 }; +function ep(t, e, s, r, i, a) { + return d(), c("div", Qf, [ + t.error ? (d(), c("div", Jf, w(t.error), 1)) : (d(), c("div", xf, [ + f("div", null, "Total: " + w(t.total), 1), + f("div", null, "Average: " + w(t.average), 1), + f("div", null, "Count: " + w(t.count), 1) + ])) + ]); +} +const IT = /* @__PURE__ */ A(Yf, [["render", ep], ["__scopeId", "data-v-e55471fe"]]), tp = C({ + name: "DataTable", + props: { + columns: { + type: Array, + required: !0 + }, + rows: { + type: Array, + required: !0 + }, + loading: Boolean, + pagination: Boolean, + itemsPerPage: { + type: Number, + default: 10 + } + }, + setup(t) { + const e = m(1); + return { + paginatedRows: W(() => { + if (!t.pagination) return t.rows; + const i = (e.value - 1) * t.itemsPerPage, a = e.value * t.itemsPerPage; + return t.rows.slice(i, a); + }), + currentPage: e, + toggleSort: (i) => { + console.log(i); + } + }; + } +}), sp = ["aria-busy"], np = ["onClick"], rp = { key: 1 }, ip = { + key: 0, + class: "pagination-controls" +}, ap = ["disabled"], op = ["disabled"]; +function lp(t, e, s, r, i, a) { + return d(), c("div", { + class: "data-table", + "aria-busy": t.loading + }, [ + f("table", null, [ + f("thead", null, [ + f("tr", null, [ + (d(!0), c(I, null, L(t.columns, (n) => (d(), c("th", { + key: n.key, + style: ne({ width: n.width, textAlign: n.align }) + }, [ + n.sortable ? (d(), c("button", { + key: 0, + onClick: (o) => t.toggleSort(n.key) + }, w(n.label), 9, np)) : (d(), c("span", rp, w(n.label), 1)) + ], 4))), 128)) + ]) + ]), + f("tbody", null, [ + (d(!0), c(I, null, L(t.paginatedRows, (n) => (d(), c("tr", { + key: n.id + }, [ + (d(!0), c(I, null, L(t.columns, (o) => (d(), c("td", { + key: o.key + }, w(n[o.key]), 1))), 128)) + ]))), 128)) + ]) + ]), + t.pagination ? (d(), c("div", ip, [ + f("button", { + onClick: e[0] || (e[0] = (n) => t.currentPage--), + disabled: t.currentPage === 1 + }, "Previous", 8, ap), + f("button", { + onClick: e[1] || (e[1] = (n) => t.currentPage++), + disabled: t.currentPage * t.itemsPerPage >= t.rows.length + }, "Next", 8, op) + ])) : P("", !0) + ], 8, sp); +} +const LT = /* @__PURE__ */ A(tp, [["render", lp], ["__scopeId", "data-v-c061f510"]]), up = C({ + name: "DateAndTimePicker", + props: { + selectedDate: { + type: String, + default: "" + }, + selectedTime: { + type: String, + default: "" + }, + disabled: { + type: Boolean, + default: !1 + } + }, + methods: { + updateDate(t) { + const e = t.target; + this.$emit("update:selectedDate", e.value); + }, + updateTime(t) { + const e = t.target; + this.$emit("update:selectedTime", e.value); + } + } +}), dp = { class: "date-time-picker-container" }, cp = ["value", "disabled", "aria-disabled"], fp = ["value", "disabled", "aria-disabled"]; +function pp(t, e, s, r, i, a) { + return d(), c("div", dp, [ + f("input", { + type: "date", + value: t.selectedDate, + disabled: t.disabled, + onInput: e[0] || (e[0] = (...n) => t.updateDate && t.updateDate(...n)), + "aria-label": "Select Date", + "aria-disabled": t.disabled + }, null, 40, cp), + f("input", { + type: "time", + value: t.selectedTime, + disabled: t.disabled, + onInput: e[1] || (e[1] = (...n) => t.updateTime && t.updateTime(...n)), + "aria-label": "Select Time", + "aria-disabled": t.disabled + }, null, 40, fp) + ]); +} +const qT = /* @__PURE__ */ A(up, [["render", pp], ["__scopeId", "data-v-5121df4f"]]), hp = C({ + name: "DatePicker", + props: { + startDate: { + type: String, + default: "" + }, + endDate: { + type: String, + default: "" + }, + selectedTime: { + type: String, + default: "" + }, + isDateRange: { + type: Boolean, + default: !1 + }, + isTimePicker: { + type: Boolean, + default: !1 + }, + disabled: { + type: Boolean, + default: !1 + } + }, + methods: { + updateStartDate(t) { + const e = t.target; + this.$emit("update:startDate", e.value); + }, + updateEndDate(t) { + const e = t.target; + this.$emit("update:endDate", e.value); + }, + updateTime(t) { + const e = t.target; + this.$emit("update:selectedTime", e.value); + } + } +}), gp = { class: "date-picker-container" }, mp = ["value", "disabled", "aria-disabled"], vp = ["value", "disabled", "aria-disabled"], bp = ["value", "disabled", "aria-disabled"]; +function yp(t, e, s, r, i, a) { + return d(), c("div", gp, [ + f("input", { + type: "date", + value: t.startDate, + disabled: t.disabled, + onInput: e[0] || (e[0] = (...n) => t.updateStartDate && t.updateStartDate(...n)), + "aria-label": "Start Date", + "aria-disabled": t.disabled + }, null, 40, mp), + t.isDateRange ? (d(), c("input", { + key: 0, + type: "date", + value: t.endDate, + disabled: t.disabled, + onInput: e[1] || (e[1] = (...n) => t.updateEndDate && t.updateEndDate(...n)), + "aria-label": "End Date", + "aria-disabled": t.disabled + }, null, 40, vp)) : P("", !0), + t.isTimePicker ? (d(), c("input", { + key: 1, + type: "time", + value: t.selectedTime, + disabled: t.disabled, + onInput: e[2] || (e[2] = (...n) => t.updateTime && t.updateTime(...n)), + "aria-label": "Time Picker", + "aria-disabled": t.disabled + }, null, 40, bp)) : P("", !0) + ]); +} +const OT = /* @__PURE__ */ A(hp, [["render", yp], ["__scopeId", "data-v-e00e68cd"]]), $p = C({ + name: "DealerButton", + props: { + state: { + type: String, + default: "default", + validator: (t) => ["default", "moving", "hovered"].includes(t) + } + }, + computed: { + stateClass() { + return this.state; + } + } +}); +function kp(t, e, s, r, i, a) { + return d(), c("div", { + class: q(["dealer-button", t.stateClass]), + "aria-label": "Dealer Button" + }, " D ", 2); +} +const PT = /* @__PURE__ */ A($p, [["render", kp], ["__scopeId", "data-v-66f3c7ea"]]), wp = C({ + name: "DeckOfCards", + props: { + cards: { + type: Array, + required: !0 + }, + overlap: { + type: Number, + default: 10 + } + }, + setup(t) { + const e = m(null); + return { onDragStart: (i) => { + e.value = i; + }, onDrop: (i) => { + if (e.value !== null && e.value !== i) { + const [a] = t.cards.splice(e.value, 1); + t.cards.splice(i, 0, a); + } + e.value = null; + } }; + } +}), Cp = { + class: "deck", + role: "list", + "aria-label": "Deck of Cards" +}, Ap = ["onDragstart", "onDrop"]; +function Ep(t, e, s, r, i, a) { + return d(), c("div", Cp, [ + (d(!0), c(I, null, L(t.cards, (n, o) => (d(), c("div", { + key: n.id, + class: "card", + style: ne({ zIndex: o, transform: `translateY(${o * t.overlap}px)` }), + role: "listitem", + "aria-label": "Card", + draggable: "true", + onDragstart: (u) => t.onDragStart(o), + onDragover: e[0] || (e[0] = de(() => { + }, ["prevent"])), + onDrop: (u) => t.onDrop(o) + }, [ + ie(t.$slots, "default", { card: n }, void 0, !0) + ], 44, Ap))), 128)) + ]); +} +const DT = /* @__PURE__ */ A(wp, [["render", Ep], ["__scopeId", "data-v-4591d50a"]]), Sp = C({ + name: "DiscardPile", + props: { + cards: { + type: Array, + required: !0 + }, + maxCards: { + type: Number, + default: 10 + }, + overlapOffset: { + type: Number, + default: 5 + } + }, + setup(t) { + const e = m([...t.cards]), s = m(!1), r = W(() => e.value.length === 0), i = W(() => e.value.length >= t.maxCards); + return { + cards: e, + isEmpty: r, + isFull: i, + isHovered: s, + handleDragOver: () => { + s.value = !0; + }, + handleDragLeave: () => { + s.value = !1; + } + }; + } +}), Tp = { + key: 0, + class: "empty-notification" +}, _p = { + key: 1, + class: "full-notification" +}; +function Np(t, e, s, r, i, a) { + return d(), c("div", { + class: q(["discard-pile", { empty: t.isEmpty, full: t.isFull, hovered: t.isHovered }]), + onDragover: e[0] || (e[0] = de((...n) => t.handleDragOver && t.handleDragOver(...n), ["prevent"])), + onDragleave: e[1] || (e[1] = (...n) => t.handleDragLeave && t.handleDragLeave(...n)) + }, [ + (d(!0), c(I, null, L(t.cards, (n, o) => (d(), c("div", { + key: n.id, + class: "card", + style: ne({ top: `${o * t.overlapOffset}px`, left: `${o * t.overlapOffset}px` }) + }, [ + ie(t.$slots, "card", { card: n }, void 0, !0) + ], 4))), 128)), + t.isEmpty ? (d(), c("div", Tp, "Pile is empty")) : P("", !0), + t.isFull ? (d(), c("div", _p, "Pile is full")) : P("", !0) + ], 34); +} +const RT = /* @__PURE__ */ A(Sp, [["render", Np], ["__scopeId", "data-v-d1a5b2af"]]), Ip = C({ + name: "DragAndDropScheduler", + props: { + events: { + type: Array, + requried: !0, + default: [ + { id: 1, title: "Default Meeting", position: 0, duration: 60, isDragging: !1 } + ] + } + }, + setup() { + const t = Array.from({ length: 24 }, (i, a) => a), e = m([ + { id: 1, title: "Meeting", position: 0, duration: 60, isDragging: !1 } + // More events... + ]); + return { + timeSlots: t, + events: e, + onDragStart: (i) => { + i.isDragging = !0; + }, + onDragEnd: (i) => { + i.isDragging = !1; + } + }; + } +}), Lp = { + class: "scheduler", + role: "application", + "aria-label": "Drag and Drop Scheduler" +}, qp = ["onDragstart", "onDragend"]; +function Op(t, e, s, r, i, a) { + return d(), c("div", Lp, [ + (d(!0), c(I, null, L(t.timeSlots, (n) => (d(), c("div", { + class: "time-slot", + key: n, + "aria-label": "Time Slot" + }, [ + (d(!0), c(I, null, L(t.events, (o) => (d(), c("div", { + key: o.id, + class: q(["event", { dragging: o.isDragging }]), + style: ne({ top: o.position + "px", height: o.duration + "px" }), + draggable: "true", + onDragstart: (u) => t.onDragStart(o), + onDragend: (u) => t.onDragEnd(o) + }, w(o.title), 47, qp))), 128)) + ]))), 128)) + ]); +} +const BT = /* @__PURE__ */ A(Ip, [["render", Op], ["__scopeId", "data-v-25e365d3"]]), Pp = C({ + name: "DropdownMenu", + props: { + menuItems: { + type: Array, + required: !0 + } + }, + setup() { + const t = m(!1), e = m(null); + return { + isExpanded: t, + toggleDropdown: () => { + t.value = !t.value; + }, + activeIndex: e, + hoverItem: (a) => { + e.value = a; + }, + leaveItem: () => { + e.value = null; + } + }; + } +}), Dp = { class: "dropdown-menu" }, Rp = ["aria-expanded"], Bp = { + key: 0, + role: "menu", + class: "dropdown-list" +}, Mp = ["href", "onMouseover"]; +function Fp(t, e, s, r, i, a) { + return d(), c("div", Dp, [ + f("button", { + onClick: e[0] || (e[0] = (...n) => t.toggleDropdown && t.toggleDropdown(...n)), + class: "dropdown-toggle", + "aria-expanded": t.isExpanded + }, " Menu ", 8, Rp), + t.isExpanded ? (d(), c("ul", Bp, [ + (d(!0), c(I, null, L(t.menuItems, (n, o) => (d(), c("li", { + key: o, + role: "menuitem" + }, [ + f("a", { + href: n.link, + onMouseover: (u) => t.hoverItem(o), + onMouseleave: e[1] || (e[1] = (...u) => t.leaveItem && t.leaveItem(...u)), + class: q({ active: t.activeIndex === o }) + }, w(n.name), 43, Mp) + ]))), 128)) + ])) : P("", !0) + ]); +} +const MT = /* @__PURE__ */ A(Pp, [["render", Fp], ["__scopeId", "data-v-f56e06f8"]]), Up = C({ + name: "EditableDataTable", + props: { + columns: { + type: Array, + required: !0 + }, + rows: { + type: Array, + required: !0 + }, + pagination: Boolean, + itemsPerPage: { + type: Number, + default: 10 + } + }, + setup(t) { + const e = m(1), s = m(null), r = m([...t.rows]), i = W(() => { + if (!t.pagination) return r.value; + const u = (e.value - 1) * t.itemsPerPage, p = e.value * t.itemsPerPage; + return r.value.slice(u, p); + }), a = (u) => { + s.value = s.value === u ? null : u; + }; + return { + paginatedRows: i, + currentPage: e, + editingRow: s, + toggleEditRow: a, + saveRow: (u) => { + a(u); + }, + deleteRow: (u) => { + r.value.splice(u, 1); + }, + editedRows: r + }; + } +}), jp = { class: "editable-data-table" }, Vp = { key: 0 }, Hp = ["onUpdate:modelValue"], zp = ["onUpdate:modelValue"], Gp = { key: 1 }, Kp = ["onClick"], Wp = ["onClick"], Zp = ["onClick"], Xp = { + key: 0, + class: "pagination-controls" +}, Yp = ["disabled"], Qp = ["disabled"]; +function Jp(t, e, s, r, i, a) { + return d(), c("div", jp, [ + f("table", null, [ + f("thead", null, [ + f("tr", null, [ + (d(!0), c(I, null, L(t.columns, (n) => (d(), c("th", { + key: n.key, + style: ne({ width: n.width, textAlign: n.align }) + }, w(n.label), 5))), 128)), + e[2] || (e[2] = f("th", null, "Actions", -1)) + ]) + ]), + f("tbody", null, [ + (d(!0), c(I, null, L(t.paginatedRows, (n, o) => (d(), c("tr", { + key: n.id, + class: q({ editing: t.editingRow === o }) + }, [ + (d(!0), c(I, null, L(t.columns, (u) => (d(), c("td", { + key: u.key + }, [ + t.editingRow === o && u.editable ? (d(), c("div", Vp, [ + u.multiline ? R((d(), c("textarea", { + key: 1, + "onUpdate:modelValue": (p) => n[u.key] = p + }, null, 8, zp)), [ + [H, n[u.key]] + ]) : R((d(), c("input", { + key: 0, + type: "text", + "onUpdate:modelValue": (p) => n[u.key] = p + }, null, 8, Hp)), [ + [H, n[u.key]] + ]) + ])) : (d(), c("div", Gp, w(n[u.key]), 1)) + ]))), 128)), + f("td", null, [ + t.editingRow !== o ? (d(), c("button", { + key: 0, + onClick: (u) => t.toggleEditRow(o) + }, "Edit", 8, Kp)) : P("", !0), + t.editingRow === o ? (d(), c("button", { + key: 1, + onClick: (u) => t.saveRow(o) + }, "Save", 8, Wp)) : P("", !0), + f("button", { + onClick: (u) => t.deleteRow(o) + }, "Delete", 8, Zp) + ]) + ], 2))), 128)) + ]) + ]), + t.pagination ? (d(), c("div", Xp, [ + f("button", { + onClick: e[0] || (e[0] = (n) => t.currentPage--), + disabled: t.currentPage === 1 + }, "Previous", 8, Yp), + f("button", { + onClick: e[1] || (e[1] = (n) => t.currentPage++), + disabled: t.currentPage * t.itemsPerPage >= t.editedRows.length + }, "Next", 8, Qp) + ])) : P("", !0) + ]); +} +const FT = /* @__PURE__ */ A(Up, [["render", Jp], ["__scopeId", "data-v-f7a88ca4"]]), xp = C({ + name: "EmbeddedMediaIframe", + props: { + src: { + type: String, + required: !0 + } + }, + setup() { + const t = m(!1), e = m(!0); + return { + isFullscreen: t, + isBuffering: e, + toggleFullscreen: () => { + t.value = !t.value; + }, + handleLoad: () => { + e.value = !1; + } + }; + } +}), eh = ["src", "aria-busy"]; +function th(t, e, s, r, i, a) { + return d(), c("div", { + class: q(["embedded-media-iframe", { fullscreen: t.isFullscreen }]) + }, [ + f("iframe", { + ref: "iframe", + src: t.src, + frameborder: "0", + allowfullscreen: "", + "aria-busy": t.isBuffering, + onLoad: e[0] || (e[0] = (...n) => t.handleLoad && t.handleLoad(...n)) + }, null, 40, eh), + t.isFullscreen ? (d(), c("button", { + key: 1, + onClick: e[2] || (e[2] = (...n) => t.toggleFullscreen && t.toggleFullscreen(...n)), + "aria-label": "Exit fullscreen", + class: "fullscreen-btn" + }, "⤡")) : (d(), c("button", { + key: 0, + onClick: e[1] || (e[1] = (...n) => t.toggleFullscreen && t.toggleFullscreen(...n)), + "aria-label": "Enter fullscreen", + class: "fullscreen-btn" + }, "⤢")) + ], 2); +} +const UT = /* @__PURE__ */ A(xp, [["render", th], ["__scopeId", "data-v-df79aece"]]), sh = C({ + name: "EmojiReactionPoll", + props: { + question: { + type: String, + required: !0 + }, + emojis: { + type: Array, + default: () => ["😀", "😐", "😢"] + }, + initialSelection: { + type: Number, + default: null + }, + isDisabled: { + type: Boolean, + default: !1 + }, + showResults: { + type: Boolean, + default: !1 + } + }, + setup(t) { + const e = m(t.initialSelection); + return { + selectedEmoji: e, + selectEmoji: (r) => { + t.isDisabled || (e.value = r); + } + }; + } +}), nh = { + class: "emoji-reaction-poll", + role: "radiogroup", + "aria-labelledby": "poll-question" +}, rh = { id: "poll-question" }, ih = { class: "emojis" }, ah = ["aria-checked", "disabled", "onClick"]; +function oh(t, e, s, r, i, a) { + return d(), c("div", nh, [ + f("p", rh, w(t.question), 1), + f("div", ih, [ + (d(!0), c(I, null, L(t.emojis, (n, o) => (d(), c("button", { + key: o, + "aria-checked": o === t.selectedEmoji, + disabled: t.isDisabled, + onClick: (u) => t.selectEmoji(o), + class: q(["emoji", { selected: o === t.selectedEmoji }]), + role: "radio" + }, w(n), 11, ah))), 128)) + ]) + ]); +} +const jT = /* @__PURE__ */ A(sh, [["render", oh], ["__scopeId", "data-v-d06ec7ef"]]), lh = C({ + name: "EraserTool", + setup() { + const t = m(!1), e = m(10); + return { + isActive: t, + eraserSize: e, + toggleActive: () => { + t.value = !t.value; + } + }; + } +}), uh = ["aria-pressed"], dh = { + key: 0, + class: "eraser-settings" +}; +function ch(t, e, s, r, i, a) { + return d(), c("div", { + class: q(["eraser-tool", { active: t.isActive }]) + }, [ + f("button", { + onClick: e[0] || (e[0] = (...n) => t.toggleActive && t.toggleActive(...n)), + "aria-pressed": t.isActive, + class: "eraser-button" + }, "Eraser Tool", 8, uh), + t.isActive ? (d(), c("div", dh, [ + e[2] || (e[2] = f("label", { for: "eraserSize" }, "Eraser Size", -1)), + R(f("input", { + id: "eraserSize", + type: "range", + "onUpdate:modelValue": e[1] || (e[1] = (n) => t.eraserSize = n), + min: "1", + max: "50", + "aria-valuemin": "1", + "aria-valuemax": "50", + "aria-valuenow": "eraserSize" + }, null, 512), [ + [H, t.eraserSize] + ]) + ])) : P("", !0) + ], 2); +} +const VT = /* @__PURE__ */ A(lh, [["render", ch], ["__scopeId", "data-v-022c39bc"]]), fh = C({ + name: "EventDetailsDialog", + props: { + event: { + type: Object, + required: !0 + }, + isOpen: { + type: Boolean, + default: !1 + }, + isLoading: { + type: Boolean, + default: !1 + }, + error: { + type: String, + default: "" + } + }, + setup() { + return { + editEvent: () => { + }, + deleteEvent: () => { + }, + duplicateEvent: () => { + }, + closeDialog: () => { + } + }; + } +}), ph = { + key: 0, + class: "dialog", + role: "dialog", + "aria-labelledby": "dialog-title", + "aria-describedby": "dialog-description" +}, hh = { class: "dialog-content" }, gh = { id: "dialog-title" }, mh = { id: "dialog-description" }, vh = { class: "dialog-actions" }, bh = ["disabled"], yh = ["disabled"], $h = ["disabled"], kh = { + key: 0, + class: "loading" +}, wh = { + key: 1, + class: "error" +}; +function Ch(t, e, s, r, i, a) { + return d(), uo(co, { name: "fade" }, { + default: Ri(() => [ + t.isOpen ? (d(), c("div", ph, [ + f("div", hh, [ + f("h2", gh, w(t.event.title), 1), + f("p", mh, w(t.event.description), 1), + f("ul", null, [ + f("li", null, [ + e[4] || (e[4] = f("strong", null, "Participants:", -1)), + Fe(" " + w(t.event.participants.join(", ")), 1) + ]), + f("li", null, [ + e[5] || (e[5] = f("strong", null, "Location:", -1)), + Fe(" " + w(t.event.location), 1) + ]), + f("li", null, [ + e[6] || (e[6] = f("strong", null, "Time:", -1)), + Fe(" " + w(t.event.time), 1) + ]) + ]), + f("div", vh, [ + f("button", { + onClick: e[0] || (e[0] = (...n) => t.editEvent && t.editEvent(...n)), + disabled: t.isLoading + }, "Edit", 8, bh), + f("button", { + onClick: e[1] || (e[1] = (...n) => t.deleteEvent && t.deleteEvent(...n)), + disabled: t.isLoading + }, "Delete", 8, yh), + f("button", { + onClick: e[2] || (e[2] = (...n) => t.duplicateEvent && t.duplicateEvent(...n)), + disabled: t.isLoading + }, "Duplicate", 8, $h) + ]), + f("button", { + class: "close-button", + onClick: e[3] || (e[3] = (...n) => t.closeDialog && t.closeDialog(...n)), + "aria-label": "Close dialog" + }, "×") + ]), + t.isLoading ? (d(), c("div", kh, "Loading...")) : P("", !0), + t.error ? (d(), c("div", wh, w(t.error), 1)) : P("", !0) + ])) : P("", !0) + ]), + _: 1 + }); +} +const HT = /* @__PURE__ */ A(fh, [["render", Ch], ["__scopeId", "data-v-8ed5c1ba"]]), Ah = C({ + name: "EventFilterBar", + setup() { + const t = m(["Conference", "Meetup", "Workshop"]), e = m({ + category: "", + startDate: "", + endDate: "", + location: "", + participants: 1 + }), s = W(() => Object.entries(e.value).filter(([, a]) => a).map(([a, n]) => `${a}: ${n}`).join(", ")); + return { + categories: t, + filters: e, + activeFilters: s, + applyFilters: () => { + console.log("Filters applied:", e.value); + }, + clearFilters: () => { + e.value = { + category: "", + startDate: "", + endDate: "", + location: "", + participants: 1 + }; + } + }; + } +}), Eh = { + class: "event-filter-bar", + role: "toolbar", + "aria-label": "Event Filter Bar" +}, Sh = { class: "filter-group" }, Th = ["value"], _h = { class: "filter-group" }, Nh = { class: "filter-group" }, Ih = { class: "filter-group" }, Lh = { class: "filter-buttons" }, qh = { + key: 0, + class: "active-filters" +}; +function Oh(t, e, s, r, i, a) { + return d(), c("div", Eh, [ + f("form", { + onSubmit: e[6] || (e[6] = de((...n) => t.applyFilters && t.applyFilters(...n), ["prevent"])) + }, [ + f("div", Sh, [ + e[8] || (e[8] = f("label", { for: "category" }, "Category", -1)), + R(f("select", { + id: "category", + "onUpdate:modelValue": e[0] || (e[0] = (n) => t.filters.category = n) + }, [ + e[7] || (e[7] = f("option", { value: "" }, "All Categories", -1)), + (d(!0), c(I, null, L(t.categories, (n) => (d(), c("option", { + key: n, + value: n + }, w(n), 9, Th))), 128)) + ], 512), [ + [ye, t.filters.category] + ]) + ]), + f("div", _h, [ + e[9] || (e[9] = f("label", { for: "date-range" }, "Date Range", -1)), + R(f("input", { + type: "date", + id: "start-date", + "onUpdate:modelValue": e[1] || (e[1] = (n) => t.filters.startDate = n) + }, null, 512), [ + [H, t.filters.startDate] + ]), + R(f("input", { + type: "date", + id: "end-date", + "onUpdate:modelValue": e[2] || (e[2] = (n) => t.filters.endDate = n) + }, null, 512), [ + [H, t.filters.endDate] + ]) + ]), + f("div", Nh, [ + e[10] || (e[10] = f("label", { for: "location" }, "Location", -1)), + R(f("input", { + type: "text", + id: "location", + "onUpdate:modelValue": e[3] || (e[3] = (n) => t.filters.location = n), + placeholder: "Enter location" + }, null, 512), [ + [H, t.filters.location] + ]) + ]), + f("div", Ih, [ + e[11] || (e[11] = f("label", { for: "participants" }, "Participants", -1)), + R(f("input", { + type: "number", + id: "participants", + "onUpdate:modelValue": e[4] || (e[4] = (n) => t.filters.participants = n), + min: "1" + }, null, 512), [ + [H, t.filters.participants] + ]) + ]), + f("div", Lh, [ + e[12] || (e[12] = f("button", { type: "submit" }, "Apply Filters", -1)), + f("button", { + type: "button", + onClick: e[5] || (e[5] = (...n) => t.clearFilters && t.clearFilters(...n)) + }, "Clear Filters") + ]) + ], 32), + t.activeFilters ? (d(), c("div", qh, " Active Filters: " + w(t.activeFilters), 1)) : P("", !0) + ]); +} +const zT = /* @__PURE__ */ A(Ah, [["render", Oh], ["__scopeId", "data-v-88e66fe9"]]), Ph = C({ + name: "EventReminderSystem", + props: { + feedbackMessage: { + type: String, + default: "" + } + }, + setup(t) { + const e = m([ + { id: "1", title: "Team Meeting" }, + { id: "2", title: "Project Deadline" }, + { id: "3", title: "Client Call" } + ]), s = m({ + event: "", + time: "", + method: "" + }), r = m(t.feedbackMessage), i = () => { + r.value = `Reminder set for "${s.value.event}" via ${s.value.method}.`, n(); + }, a = () => { + r.value = `Reminder for "${s.value.event}" canceled.`, n(); + }, n = () => { + s.value = { + event: "", + time: "", + method: "" + }; + }; + return { + events: e, + form: s, + localFeedbackMessage: r, + setReminder: i, + cancelReminder: a + }; + } +}), Dh = { + class: "event-reminder-system", + role: "region", + "aria-label": "Event Reminder System" +}, Rh = { class: "form-group" }, Bh = ["value"], Mh = { class: "form-group" }, Fh = { class: "form-group" }, Uh = { class: "reminder-buttons" }, jh = { + key: 0, + class: "feedback" +}; +function Vh(t, e, s, r, i, a) { + return d(), c("div", Dh, [ + f("form", { + onSubmit: e[4] || (e[4] = de((...n) => t.setReminder && t.setReminder(...n), ["prevent"])) + }, [ + f("div", Rh, [ + e[5] || (e[5] = f("label", { for: "event" }, "Event", -1)), + R(f("select", { + id: "event", + "onUpdate:modelValue": e[0] || (e[0] = (n) => t.form.event = n), + required: "" + }, [ + (d(!0), c(I, null, L(t.events, (n) => (d(), c("option", { + key: n.id, + value: n.id + }, w(n.title), 9, Bh))), 128)) + ], 512), [ + [ye, t.form.event] + ]) + ]), + f("div", Mh, [ + e[7] || (e[7] = f("label", { for: "time" }, "Reminder Time", -1)), + R(f("select", { + id: "time", + "onUpdate:modelValue": e[1] || (e[1] = (n) => t.form.time = n), + required: "" + }, [...e[6] || (e[6] = [ + f("option", { value: "1 hour" }, "1 hour before", -1), + f("option", { value: "1 day" }, "1 day before", -1), + f("option", { value: "1 week" }, "1 week before", -1) + ])], 512), [ + [ye, t.form.time] + ]) + ]), + f("div", Fh, [ + e[9] || (e[9] = f("label", { for: "method" }, "Notification Method", -1)), + R(f("select", { + id: "method", + "onUpdate:modelValue": e[2] || (e[2] = (n) => t.form.method = n), + required: "" + }, [...e[8] || (e[8] = [ + f("option", { value: "email" }, "Email", -1), + f("option", { value: "sms" }, "SMS", -1), + f("option", { value: "push" }, "Push Notification", -1) + ])], 512), [ + [ye, t.form.method] + ]) + ]), + f("div", Uh, [ + e[10] || (e[10] = f("button", { type: "submit" }, "Set Reminder", -1)), + f("button", { + type: "button", + onClick: e[3] || (e[3] = (...n) => t.cancelReminder && t.cancelReminder(...n)) + }, "Cancel Reminder") + ]) + ], 32), + t.localFeedbackMessage ? (d(), c("div", jh, w(t.localFeedbackMessage), 1)) : P("", !0) + ]); +} +const GT = /* @__PURE__ */ A(Ph, [["render", Vh], ["__scopeId", "data-v-abb09c51"]]), Hh = C({ + name: "ExpandableList", + props: { + items: { + type: Array, + required: !0 + } + }, + setup() { + const t = m(null), e = m(null); + return { selectedItem: t, hoveredItem: e, toggleItem: (i) => { + t.value = t.value === i ? null : i; + }, hoverItem: (i) => { + e.value = i; + } }; + } +}), zh = { + class: "expandable-list", + role: "list", + "aria-label": "Expandable list" +}, Gh = ["aria-expanded", "onClick", "onMouseover"], Kh = { class: "item-header" }, Wh = { + key: 0, + class: "item-content" +}; +function Zh(t, e, s, r, i, a) { + return d(), c("div", zh, [ + (d(!0), c(I, null, L(t.items, (n, o) => (d(), c("div", { + key: o, + class: "list-item", + "aria-expanded": t.selectedItem === o, + onClick: (u) => t.toggleItem(o), + onMouseover: (u) => t.hoverItem(o), + onMouseleave: e[0] || (e[0] = (u) => t.hoverItem(null)) + }, [ + f("div", Kh, w(n.title), 1), + t.selectedItem === o ? (d(), c("div", Wh, w(n.content), 1)) : P("", !0) + ], 40, Gh))), 128)) + ]); +} +const KT = /* @__PURE__ */ A(Hh, [["render", Zh], ["__scopeId", "data-v-2bce1792"]]), Xh = C({ + name: "FavoritesList", + props: { + items: { + type: Array, + required: !0 + } + }, + setup(t) { + const e = m(null), s = m(null); + return { selectedItem: e, hoveredItem: s, selectItem: (n) => { + e.value = e.value === n ? null : n; + }, hoverItem: (n) => { + s.value = n; + }, toggleStar: (n) => { + t.items[n].starred = !t.items[n].starred; + } }; + } +}), Yh = { + class: "favorites-list", + role: "list", + "aria-label": "Favorites list" +}, Qh = ["onClick", "onMouseover"], Jh = { class: "item-header" }, xh = ["aria-pressed", "onClick"]; +function eg(t, e, s, r, i, a) { + return d(), c("div", Yh, [ + (d(!0), c(I, null, L(t.items, (n, o) => (d(), c("div", { + key: o, + class: q(["list-item", { selected: t.selectedItem === o, hover: t.hoveredItem === o }]), + onClick: (u) => t.selectItem(o), + onMouseover: (u) => t.hoverItem(o), + onMouseleave: e[0] || (e[0] = (u) => t.hoverItem(null)) + }, [ + f("div", Jh, [ + Fe(w(n.title) + " ", 1), + f("button", { + class: "star-button", + "aria-pressed": n.starred, + onClick: de((u) => t.toggleStar(o), ["stop"]) + }, w(n.starred ? "★" : "☆"), 9, xh) + ]) + ], 42, Qh))), 128)) + ]); +} +const WT = /* @__PURE__ */ A(Xh, [["render", eg], ["__scopeId", "data-v-62a6fd5a"]]), tg = C({ + name: "FieldEditableDataTable", + setup() { + const t = li([ + { id: "1", name: "John Doe", description: "A short note" }, + { id: "2", name: "Jane Smith", description: "Another note" } + ]), e = m(null), s = li({}), r = m(null); + return { + data: t, + editingField: e, + fieldValues: s, + error: r, + editField: (o, u) => { + var p; + e.value = { rowId: o, field: u }, s[u] = ((p = t.find((g) => g.id === o)) == null ? void 0 : p[u]) || ""; + }, + saveField: () => { + if (!e.value) return; + const { rowId: o, field: u } = e.value, p = t.find((g) => g.id === o); + if (!p) { + r.value = "Row not found"; + return; + } + p[u] = s[u], r.value = null, e.value = null; + }, + discardChanges: () => { + e.value = null, r.value = null; + } + }; + } +}), sg = { + class: "field-editable-data-table", + role: "table" +}, ng = ["onUpdate:modelValue"], rg = ["onUpdate:modelValue"], ig = ["onClick"], ag = { + key: 0, + class: "error-message", + "aria-live": "assertive" +}; +function og(t, e, s, r, i, a) { + return d(), c("div", sg, [ + (d(!0), c(I, null, L(t.data, (n) => (d(), c("div", { + role: "row", + key: n.id, + class: "table-row" + }, [ + (d(!0), c(I, null, L(n, (o, u) => (d(), c("div", { + role: "cell", + class: "table-cell", + key: u + }, [ + t.editingField && t.editingField.rowId === n.id && t.editingField.field === u ? (d(), c(I, { key: 0 }, [ + u === "description" ? R((d(), c("textarea", { + key: 0, + "onUpdate:modelValue": (p) => t.fieldValues[u] = p, + "aria-label": "Edit description" + }, null, 8, ng)), [ + [H, t.fieldValues[u]] + ]) : R((d(), c("input", { + key: 1, + type: "text", + "onUpdate:modelValue": (p) => t.fieldValues[u] = p, + "aria-label": "Edit field" + }, null, 8, rg)), [ + [H, t.fieldValues[u]] + ]), + f("button", { + onClick: e[0] || (e[0] = (...p) => t.saveField && t.saveField(...p)) + }, "Save"), + f("button", { + onClick: e[1] || (e[1] = (...p) => t.discardChanges && t.discardChanges(...p)) + }, "Cancel") + ], 64)) : (d(), c("span", { + key: 1, + onClick: (p) => t.editField(n.id, u.toString()), + class: "editable-field" + }, w(o), 9, ig)) + ]))), 128)) + ]))), 128)), + t.error ? (d(), c("div", ag, w(t.error), 1)) : P("", !0) + ]); +} +const ZT = /* @__PURE__ */ A(tg, [["render", og], ["__scopeId", "data-v-5d7b0258"]]), lg = C({ + name: "FileInputWithPreview", + props: { + disabled: { + type: Boolean, + default: !1 + } + }, + setup() { + const t = m(null), e = m(null); + return { preview: t, error: e, handleFileUpload: (r) => { + const i = r.target, a = i.files ? i.files[0] : null; + if (a) { + const n = new FileReader(); + n.onload = () => { + t.value = n.result, e.value = null; + }, n.onerror = () => { + e.value = "Error loading file.", t.value = null; + }, n.readAsDataURL(a); + } + } }; + } +}), ug = { class: "file-input-container" }, dg = ["disabled", "aria-disabled"], cg = { + key: 0, + class: "preview-container" +}, fg = ["src"], pg = { + key: 1, + class: "error-message", + role: "alert" +}; +function hg(t, e, s, r, i, a) { + return d(), c("div", ug, [ + f("input", { + type: "file", + disabled: t.disabled, + onChange: e[0] || (e[0] = (...n) => t.handleFileUpload && t.handleFileUpload(...n)), + "aria-label": "Upload File", + "aria-disabled": t.disabled + }, null, 40, dg), + t.preview ? (d(), c("div", cg, [ + f("img", { + src: t.preview, + alt: "File Preview", + class: "preview-image" + }, null, 8, fg) + ])) : P("", !0), + t.error ? (d(), c("div", pg, w(t.error), 1)) : P("", !0) + ]); +} +const XT = /* @__PURE__ */ A(lg, [["render", hg], ["__scopeId", "data-v-a42dea44"]]), gg = C({ + name: "FileUpload", + props: { + multiple: { + type: Boolean, + default: !1 + } + }, + setup() { + const t = m(!1), e = m(null); + return { isDragOver: t, progress: e, handleFileSelect: (n) => { + n.target.files; + }, handleDragOver: () => { + t.value = !0; + }, handleDragLeave: () => { + t.value = !1; + }, handleDrop: (n) => { + var o; + t.value = !1, (o = n.dataTransfer) == null || o.files; + } }; + } +}), mg = ["multiple"], vg = ["aria-valuenow"]; +function bg(t, e, s, r, i, a) { + return d(), c("div", { + class: q(["file-upload-container", { "drag-over": t.isDragOver }]), + onDragover: e[1] || (e[1] = de((...n) => t.handleDragOver && t.handleDragOver(...n), ["prevent"])), + onDragleave: e[2] || (e[2] = de((...n) => t.handleDragLeave && t.handleDragLeave(...n), ["prevent"])), + onDrop: e[3] || (e[3] = de((...n) => t.handleDrop && t.handleDrop(...n), ["prevent"])) + }, [ + f("input", { + type: "file", + multiple: t.multiple, + onChange: e[0] || (e[0] = (...n) => t.handleFileSelect && t.handleFileSelect(...n)), + "aria-label": "Upload File(s)" + }, null, 40, mg), + t.progress ? (d(), c("div", { + key: 0, + class: "progress-bar", + role: "progressbar", + "aria-valuenow": t.progress, + "aria-valuemin": "0", + "aria-valuemax": "100" + }, [ + f("div", { + class: "progress", + style: ne({ width: t.progress + "%" }) + }, null, 4) + ], 8, vg)) : P("", !0) + ], 34); +} +const YT = /* @__PURE__ */ A(gg, [["render", bg], ["__scopeId", "data-v-e710dbb6"]]), yg = C({ + name: "FillTool", + setup() { + const t = m(!1), e = m(!1), s = m(50); + return { + isActive: t, + isDisabled: e, + tolerance: s, + toggleActive: () => { + e.value || (t.value = !t.value); + } + }; + } +}), $g = { class: "fill-tool" }, kg = ["disabled"], wg = { + key: 0, + class: "fill-options" +}; +function Cg(t, e, s, r, i, a) { + return d(), c("div", $g, [ + f("button", { + disabled: t.isDisabled, + onClick: e[0] || (e[0] = (...n) => t.toggleActive && t.toggleActive(...n)), + class: q({ active: t.isActive }), + "aria-label": "Fill Tool" + }, " Fill ", 10, kg), + t.isActive ? (d(), c("div", wg, [ + e[2] || (e[2] = f("label", { for: "tolerance" }, "Tolerance:", -1)), + R(f("input", { + id: "tolerance", + type: "range", + min: "0", + max: "100", + "onUpdate:modelValue": e[1] || (e[1] = (n) => t.tolerance = n) + }, null, 512), [ + [H, t.tolerance] + ]) + ])) : P("", !0) + ]); +} +const QT = /* @__PURE__ */ A(yg, [["render", Cg], ["__scopeId", "data-v-c9034f98"]]), Ag = C({ + name: "FilterableList", + props: { + items: { + type: Array, + required: !0 + } + }, + setup(t) { + const e = m(""), s = W( + () => t.items.filter( + (i) => i.toLowerCase().includes(e.value.toLowerCase()) + ) + ); + return { filterText: e, filteredItems: s, clearFilter: () => { + e.value = ""; + } }; + } +}), Eg = { class: "filterable-list" }, Sg = { + role: "list", + "aria-label": "Filtered list" +}, Tg = { + key: 0, + class: "no-results" +}; +function _g(t, e, s, r, i, a) { + return d(), c("div", Eg, [ + R(f("input", { + type: "text", + "onUpdate:modelValue": e[0] || (e[0] = (n) => t.filterText = n), + placeholder: "Filter items...", + "aria-label": "Filter items", + class: "filter-input" + }, null, 512), [ + [H, t.filterText] + ]), + f("button", { + onClick: e[1] || (e[1] = (...n) => t.clearFilter && t.clearFilter(...n)), + class: "clear-button" + }, "Clear Filter"), + f("ul", Sg, [ + (d(!0), c(I, null, L(t.filteredItems, (n, o) => (d(), c("li", { + key: o, + class: "list-item" + }, w(n), 1))), 128)) + ]), + t.filteredItems.length === 0 ? (d(), c("p", Tg, "No results found")) : P("", !0) + ]); +} +const JT = /* @__PURE__ */ A(Ag, [["render", _g], ["__scopeId", "data-v-d92ce0dc"]]), Ng = C({ + name: "FlipCard", + props: { + disabled: { + type: Boolean, + default: !1 + } + }, + setup(t) { + const e = m(!1); + return { + flipped: e, + toggleFlip: () => { + t.disabled || (e.value = !e.value); + } + }; + } +}), Ig = ["aria-disabled"], Lg = { + class: "flip-card-front", + role: "region", + "aria-label": "Front Content" +}, qg = { + class: "flip-card-back", + role: "region", + "aria-label": "Back Content" +}; +function Og(t, e, s, r, i, a) { + return d(), c("div", { + class: q(["flip-card", { flipped: t.flipped, disabled: t.disabled }]), + onClick: e[0] || (e[0] = (...n) => t.toggleFlip && t.toggleFlip(...n)), + "aria-disabled": t.disabled + }, [ + f("div", { + class: "flip-card-inner", + style: ne({ transform: t.flipped ? "rotateY(180deg)" : "none" }) + }, [ + f("div", Lg, [ + ie(t.$slots, "front", {}, void 0, !0) + ]), + f("div", qg, [ + ie(t.$slots, "back", {}, void 0, !0) + ]) + ], 4) + ], 10, Ig); +} +const xT = /* @__PURE__ */ A(Ng, [["render", Og], ["__scopeId", "data-v-fd008512"]]), Pg = C({ + name: "FloatingActionButton", + props: { + isExpanded: { + type: Boolean, + default: !1 + } + }, + setup(t) { + const e = m(t.isExpanded); + return { localIsExpanded: e, toggleExpand: () => { + e.value = !e.value; + } }; + } +}), Dg = ["aria-expanded"], Rg = { + key: 0, + "aria-hidden": "true" +}, Bg = { + key: 1, + "aria-hidden": "true" +}, Mg = { class: "sr-only" }; +function Fg(t, e, s, r, i, a) { + return d(), c("button", { + class: "floating-action-button", + onClick: e[0] || (e[0] = (...n) => t.toggleExpand && t.toggleExpand(...n)), + "aria-expanded": t.localIsExpanded + }, [ + t.localIsExpanded ? (d(), c("span", Rg, "✖")) : (d(), c("span", Bg, "+")), + f("span", Mg, w(t.localIsExpanded ? "Close menu" : "Open menu"), 1) + ], 8, Dg); +} +const e4 = /* @__PURE__ */ A(Pg, [["render", Fg], ["__scopeId", "data-v-ec8edb50"]]), Ug = C({ + name: "FoldButton", + props: { + disabled: { + type: Boolean, + default: !1 + } + } +}), jg = ["disabled"]; +function Vg(t, e, s, r, i, a) { + return d(), c("button", { + class: "fold-button", + disabled: t.disabled, + "aria-label": "Fold Hand" + }, " Fold ", 8, jg); +} +const t4 = /* @__PURE__ */ A(Ug, [["render", Vg], ["__scopeId", "data-v-3abbd310"]]), Hg = C({ + name: "GroupedList", + props: { + groups: { + type: Array, + required: !0 + } + }, + setup() { + const t = m([]), e = m(null), s = m(null); + return { expandedGroups: t, selectedItem: e, hoveredItem: s, toggleGroup: (a) => { + t.value[a] = !t.value[a]; + }, selectItem: (a) => { + e.value = a; + } }; + } +}), zg = { class: "grouped-list" }, Gg = ["onClick"], Kg = { + class: "group-items", + role: "list", + "aria-label": "Group items" +}, Wg = ["onClick", "onMouseover", "aria-selected"]; +function Zg(t, e, s, r, i, a) { + return d(), c("div", zg, [ + (d(!0), c(I, null, L(t.groups, (n, o) => (d(), c("div", { + key: o, + class: "group" + }, [ + f("div", { + class: "group-header", + onClick: (u) => t.toggleGroup(o) + }, w(n.name), 9, Gg), + R(f("ul", Kg, [ + (d(!0), c(I, null, L(n.items, (u, p) => (d(), c("li", { + key: p, + class: q(["list-item", { selected: t.selectedItem === u }]), + onClick: (g) => t.selectItem(u), + onMouseover: (g) => t.hoveredItem = u, + onMouseleave: e[0] || (e[0] = (g) => t.hoveredItem = null), + "aria-selected": t.selectedItem === u, + role: "option" + }, w(u), 43, Wg))), 128)) + ], 512), [ + [lo, t.expandedGroups[o]] + ]) + ]))), 128)) + ]); +} +const s4 = /* @__PURE__ */ A(Hg, [["render", Zg], ["__scopeId", "data-v-024aa25b"]]), Xg = C({ + name: "HandOfCards", + props: { + cards: { + type: Array, + required: !0 + }, + maxCards: { + type: Number, + default: 5 + } + }, + setup(t) { + const e = m([...t.cards]), s = m([]), r = (n) => { + s.value.includes(n) ? s.value = s.value.filter((o) => o !== n) : s.value.length < t.maxCards && s.value.push(n); + }, i = W(() => e.value.length >= t.maxCards), a = W(() => s.value.length >= t.maxCards); + return { + hand: e, + selectedCards: s, + isFull: i, + maxLimitReached: a, + toggleSelect: r + }; + } +}), Yg = ["onClick"], Qg = { + key: 0, + class: "limit-notification" +}; +function Jg(t, e, s, r, i, a) { + return d(), c("div", { + class: q(["hand", { full: t.isFull, maxLimit: t.maxLimitReached }]) + }, [ + (d(!0), c(I, null, L(t.hand, (n) => (d(), c("div", { + key: n.id, + class: q(["card", { selected: t.selectedCards.includes(n.id) }]), + onClick: (o) => t.toggleSelect(n.id) + }, [ + ie(t.$slots, "card", { card: n }, void 0, !0) + ], 10, Yg))), 128)), + t.maxLimitReached ? (d(), c("div", Qg, "Max card limit reached")) : P("", !0) + ], 2); +} +const n4 = /* @__PURE__ */ A(Xg, [["render", Jg], ["__scopeId", "data-v-68df639c"]]), xg = C({ + name: "IconButton", + props: { + disabled: { + type: Boolean, + default: !1 + } + }, + setup(t) { + const e = m(!1), s = m(!1), r = W(() => t.disabled ? "disabled" : s.value ? "active" : e.value ? "hover" : ""); + return { + isHover: e, + isActive: s, + buttonState: r + }; + } +}), em = ["aria-disabled", "disabled"]; +function tm(t, e, s, r, i, a) { + return d(), c("button", { + class: q(["icon-button", t.buttonState, { disabled: t.disabled }]), + "aria-disabled": t.disabled, + disabled: t.disabled, + onMouseover: e[0] || (e[0] = (n) => t.isHover = !0), + onMouseleave: e[1] || (e[1] = (n) => t.isHover = !1), + onMousedown: e[2] || (e[2] = (n) => t.isActive = !0), + onMouseup: e[3] || (e[3] = (n) => t.isActive = !1) + }, [ + ie(t.$slots, "default", {}, void 0, !0) + ], 42, em); +} +const r4 = /* @__PURE__ */ A(xg, [["render", tm], ["__scopeId", "data-v-1fd9d51b"]]), sm = C({ + name: "ImageChoicePoll", + props: { + question: { + type: String, + required: !0 + }, + options: { + type: Array, + required: !0 + }, + initialValue: { + type: Number, + default: null + }, + resultsVisible: { + type: Boolean, + default: !1 + }, + disabled: { + type: Boolean, + default: !1 + } + }, + setup(t) { + const e = m(t.initialValue); + return { + selectedOption: e, + selectOption: (r) => { + t.disabled || (e.value = r); + } + }; + } +}), nm = { + class: "image-choice-poll", + role: "group", + "aria-labelledby": "image-choice-poll-label" +}, rm = { + id: "image-choice-poll-label", + class: "poll-label" +}, im = { class: "options" }, am = ["onClick", "disabled", "aria-pressed"], om = ["src", "alt"], lm = { + key: 0, + class: "result-display" +}; +function um(t, e, s, r, i, a) { + return d(), c("div", nm, [ + f("div", rm, w(t.question), 1), + f("div", im, [ + (d(!0), c(I, null, L(t.options, (n, o) => (d(), c("button", { + key: o, + class: q(["option", { selected: t.selectedOption === o, disabled: t.disabled }]), + onClick: (u) => t.selectOption(o), + disabled: t.disabled, + "aria-pressed": t.selectedOption === o, + "aria-label": "Option" + }, [ + f("img", { + src: n.image, + alt: n.alt, + class: "option-image" + }, null, 8, om) + ], 10, am))), 128)) + ]), + t.resultsVisible && t.selectedOption !== null ? (d(), c("div", lm, " Selected: " + w(t.options[t.selectedOption].alt), 1)) : P("", !0) + ]); +} +const i4 = /* @__PURE__ */ A(sm, [["render", um], ["__scopeId", "data-v-ec76f45f"]]), dm = C({ + name: "ImageSlider", + props: { + images: { + type: Array, + required: !0 + } + }, + setup(t) { + const e = m(0), s = m(!1); + return { + activeIndex: e, + isHovering: s, + setHover: (n) => { + s.value = n; + }, + prevImage: () => { + e.value = (e.value - 1 + t.images.length) % t.images.length; + }, + nextImage: () => { + e.value = (e.value + 1) % t.images.length; + } + }; + } +}), cm = { class: "slider-images" }; +function fm(t, e, s, r, i, a) { + return d(), c("div", { + class: "image-slider", + onMouseenter: e[2] || (e[2] = (n) => t.setHover(!0)), + onMouseleave: e[3] || (e[3] = (n) => t.setHover(!1)) + }, [ + f("div", cm, [ + (d(!0), c(I, null, L(t.images, (n, o) => (d(), c("div", { + key: o, + class: q(["slider-image", { active: o === t.activeIndex, hover: t.isHovering }]), + style: ne({ backgroundImage: "url(" + n + ")" }) + }, null, 6))), 128)) + ]), + f("button", { + class: "prev-btn", + onClick: e[0] || (e[0] = (...n) => t.prevImage && t.prevImage(...n)), + "aria-label": "Previous image" + }, "‹"), + f("button", { + class: "next-btn", + onClick: e[1] || (e[1] = (...n) => t.nextImage && t.nextImage(...n)), + "aria-label": "Next image" + }, "›") + ], 32); +} +const a4 = /* @__PURE__ */ A(dm, [["render", fm], ["__scopeId", "data-v-a1b0374f"]]), pm = C({ + name: "InteractiveMediaMap", + props: { + mapSrc: { + type: String, + required: !0 + }, + markers: { + type: Array, + default: () => [] + } + }, + setup() { + const t = m(null), e = m(!0); + return { + selectedMarker: t, + loading: e, + selectMarker: (n) => { + t.value = n; + }, + deselectMarker: () => { + t.value = null; + }, + zoomIn: () => { + }, + zoomOut: () => { + } + }; + } +}), hm = { + class: "interactive-media-map", + role: "application", + "aria-label": "Interactive Media Map" +}, gm = { class: "map-container" }, mm = ["src"], vm = ["onClick", "aria-label"], bm = { + key: 0, + class: "marker-info" +}, ym = { + key: 1, + class: "loading" +}; +function $m(t, e, s, r, i, a) { + return d(), c("div", hm, [ + f("div", gm, [ + f("img", { + src: t.mapSrc, + alt: "Map", + onLoad: e[0] || (e[0] = (n) => t.loading = !1) + }, null, 40, mm), + (d(!0), c(I, null, L(t.markers, (n, o) => (d(), c("div", { + key: o, + class: "map-marker", + style: ne({ top: n.y + "%", left: n.x + "%" }), + onClick: (u) => t.selectMarker(o), + "aria-label": "Marker " + (o + 1), + role: "button" + }, null, 12, vm))), 128)) + ]), + f("button", { + onClick: e[1] || (e[1] = (...n) => t.zoomIn && t.zoomIn(...n)), + "aria-label": "Zoom in", + class: "zoom-btn" + }, "+"), + f("button", { + onClick: e[2] || (e[2] = (...n) => t.zoomOut && t.zoomOut(...n)), + "aria-label": "Zoom out", + class: "zoom-btn" + }, "-"), + t.selectedMarker !== null ? (d(), c("div", bm, [ + f("p", null, w(t.markers[t.selectedMarker].info), 1), + f("button", { + onClick: e[3] || (e[3] = (...n) => t.deselectMarker && t.deselectMarker(...n)), + "aria-label": "Close marker info" + }, "Close") + ])) : P("", !0), + t.loading ? (d(), c("div", ym, "Loading...")) : P("", !0) + ]); +} +const o4 = /* @__PURE__ */ A(pm, [["render", $m], ["__scopeId", "data-v-45db09ed"]]), km = C({ + name: "InteractivePollResults", + props: { + title: { + type: String, + required: !0 + }, + options: { + type: Array, + required: !0 + }, + state: { + type: String, + required: !0 + } + } +}), wm = { + class: "poll-results", + role: "region", + "aria-live": "polite" +}, Cm = ["aria-label"], Am = { class: "option-text" }, Em = { class: "percentage" }, Sm = { + key: 0, + class: "poll-status" +}, Tm = { + key: 1, + class: "poll-status" +}; +function _m(t, e, s, r, i, a) { + return d(), c("div", wm, [ + f("h2", null, w(t.title), 1), + f("ul", null, [ + (d(!0), c(I, null, L(t.options, (n) => (d(), c("li", { + key: n.id, + "aria-label": `${n.text}: ${n.percentage}%` + }, [ + f("span", Am, w(n.text), 1), + f("div", { + class: "progress-bar", + style: ne({ "--progress": `${n.percentage}%` }) + }, [ + f("span", Em, w(n.percentage) + "%", 1) + ], 4) + ], 8, Cm))), 128)) + ]), + t.state === "completed" ? (d(), c("div", Sm, "Poll Completed")) : P("", !0), + t.state === "closed" ? (d(), c("div", Tm, "Poll Closed")) : P("", !0) + ]); +} +const l4 = /* @__PURE__ */ A(km, [["render", _m], ["__scopeId", "data-v-4fad34d2"]]), Nm = C({ + name: "LayerPanel", + setup() { + const t = m([ + { id: 1, name: "Layer 1", opacity: 1, visible: !0 } + ]), e = m(0); + return { + layers: t, + activeLayerIndex: e, + addLayer: () => { + t.value.push({ + id: Date.now(), + name: `Layer ${t.value.length + 1}`, + opacity: 1, + visible: !0 + }); + }, + removeLayer: (n) => { + t.value.length > 1 && (t.value.splice(n, 1), e.value >= t.value.length && (e.value = t.value.length - 1)); + }, + renameLayer: (n, o) => { + t.value[n].name = o; + }, + toggleVisibility: (n) => { + t.value[n].visible = !t.value[n].visible; + } + }; + } +}), Im = { class: "layer-panel" }, Lm = ["onUpdate:modelValue", "onBlur"], qm = ["onUpdate:modelValue"], Om = ["onClick"], Pm = ["onClick"]; +function Dm(t, e, s, r, i, a) { + return d(), c("div", Im, [ + e[1] || (e[1] = f("h2", null, "Layers", -1)), + f("ul", null, [ + (d(!0), c(I, null, L(t.layers, (n, o) => (d(), c("li", { + key: n.id, + class: q({ active: o === t.activeLayerIndex }) + }, [ + R(f("input", { + type: "text", + "onUpdate:modelValue": (u) => n.name = u, + onBlur: (u) => t.renameLayer(o, n.name), + "aria-label": "Layer Name" + }, null, 40, Lm), [ + [H, n.name] + ]), + R(f("input", { + type: "range", + "onUpdate:modelValue": (u) => n.opacity = u, + min: "0", + max: "1", + step: "0.01", + "aria-label": "Layer Opacity" + }, null, 8, qm), [ + [H, n.opacity] + ]), + f("button", { + onClick: (u) => t.toggleVisibility(o), + "aria-label": "Toggle Visibility" + }, w(n.visible ? "Hide" : "Show"), 9, Om), + f("button", { + onClick: (u) => t.removeLayer(o), + "aria-label": "Remove Layer" + }, "Remove", 8, Pm) + ], 2))), 128)) + ]), + f("button", { + onClick: e[0] || (e[0] = (...n) => t.addLayer && t.addLayer(...n)), + "aria-label": "Add Layer" + }, "Add Layer") + ]); +} +const u4 = /* @__PURE__ */ A(Nm, [["render", Dm], ["__scopeId", "data-v-54d3b63a"]]), Rm = C({ + name: "LiveResultsPoll", + props: { + question: { + type: String, + required: !0 + }, + options: { + type: Array, + required: !0 + }, + initialValue: { + type: Number, + default: null + }, + liveResultsVisible: { + type: Boolean, + default: !1 + }, + closed: { + type: Boolean, + default: !1 + } + }, + setup(t) { + const e = m(t.initialValue), s = W( + () => t.options.reduce((a, n) => a + n.votes, 0) + ); + return { + selectedOption: e, + calculatePercentage: (a) => s.value > 0 ? Math.round(a / s.value * 100) : 0, + selectOption: (a) => { + t.closed || (e.value = a, t.options[a].votes += 1); + } + }; + } +}), Bm = { + class: "live-results-poll", + role: "group", + "aria-labelledby": "live-results-poll-label" +}, Mm = { + id: "live-results-poll-label", + class: "poll-label" +}, Fm = { class: "options" }, Um = ["onClick", "disabled", "aria-pressed"], jm = { + key: 0, + class: "result-percentage" +}; +function Vm(t, e, s, r, i, a) { + return d(), c("div", Bm, [ + f("div", Mm, w(t.question), 1), + f("div", Fm, [ + (d(!0), c(I, null, L(t.options, (n, o) => (d(), c("button", { + key: o, + class: q(["option", { selected: t.selectedOption === o, disabled: t.closed }]), + onClick: (u) => t.selectOption(o), + disabled: t.closed, + "aria-pressed": t.selectedOption === o, + "aria-label": "Option" + }, [ + Fe(w(n.text) + " ", 1), + t.liveResultsVisible ? (d(), c("span", jm, " (" + w(t.calculatePercentage(n.votes)) + "%) ", 1)) : P("", !0) + ], 10, Um))), 128)) + ]) + ]); +} +const d4 = /* @__PURE__ */ A(Rm, [["render", Vm], ["__scopeId", "data-v-f650da5f"]]), Hm = C({ + name: "LiveStreamPlayer", + props: { + streamSrc: { + type: String, + required: !0 + } + }, + setup() { + const t = m(null), e = m(!1), s = m(!1), r = () => { + t.value && (t.value.muted = !t.value.muted, e.value = t.value.muted); + }, i = () => { + s.value = !1; + }, a = () => { + }, n = () => { + s.value = !0; + }, o = () => { + t.value && (e.value = t.value.muted); + }; + return fs(e, (u) => { + t.value && (t.value.muted = u); + }), { + videoElement: t, + muted: e, + buffering: s, + toggleMute: r, + onPlay: i, + onPause: a, + onBuffering: n, + onVolumeChange: o + }; + } +}), zm = { + class: "live-stream-player", + role: "region", + "aria-label": "Live Stream Player" +}, Gm = ["src"], Km = { + key: 0, + class: "buffering-overlay" +}; +function Wm(t, e, s, r, i, a) { + return d(), c("div", zm, [ + f("video", { + ref: "videoElement", + src: t.streamSrc, + onPlay: e[0] || (e[0] = (...n) => t.onPlay && t.onPlay(...n)), + onPause: e[1] || (e[1] = (...n) => t.onPause && t.onPause(...n)), + onWaiting: e[2] || (e[2] = (...n) => t.onBuffering && t.onBuffering(...n)), + onVolumechange: e[3] || (e[3] = (...n) => t.onVolumeChange && t.onVolumeChange(...n)), + controls: "" + }, null, 40, Gm), + t.buffering ? (d(), c("div", Km, "Buffering...")) : P("", !0), + f("button", { + onClick: e[4] || (e[4] = (...n) => t.toggleMute && t.toggleMute(...n)), + "aria-label": "Toggle Mute", + class: "mute-btn" + }, w(t.muted ? "Unmute" : "Mute"), 1) + ]); +} +const c4 = /* @__PURE__ */ A(Hm, [["render", Wm], ["__scopeId", "data-v-86ab4e35"]]), Zm = C({ + name: "LoadMoreButtonInList", + props: { + items: { + type: Array, + required: !0 + }, + batchSize: { + type: Number, + default: 5 + } + }, + setup(t) { + const e = m([]), s = m(!1), r = m(!1), i = () => { + s.value || r.value || (s.value = !0, setTimeout(() => { + const a = t.items.slice(e.value.length, e.value.length + t.batchSize); + a.length > 0 && e.value.push(...a), e.value.length >= t.items.length && (r.value = !0), s.value = !1; + }, 1e3)); + }; + return i(), { displayedItems: e, isLoading: s, endOfList: r, loadMore: i }; + } +}), Xm = { class: "load-more-button-in-list" }, Ym = { + class: "item-list", + role: "list", + "aria-label": "Items" +}, Qm = ["disabled", "aria-busy"], Jm = { key: 0 }, xm = { key: 1 }, e1 = { + key: 1, + class: "end-of-list-message" +}; +function t1(t, e, s, r, i, a) { + return d(), c("div", Xm, [ + f("ul", Ym, [ + (d(!0), c(I, null, L(t.displayedItems, (n, o) => (d(), c("li", { + key: o, + class: "list-item" + }, w(n), 1))), 128)) + ]), + t.endOfList ? (d(), c("div", e1, "End of List")) : (d(), c("button", { + key: 0, + onClick: e[0] || (e[0] = (...n) => t.loadMore && t.loadMore(...n)), + disabled: t.isLoading, + class: "load-more-button", + "aria-busy": t.isLoading + }, [ + t.isLoading ? (d(), c("span", Jm, "Loading...")) : (d(), c("span", xm, "Load More")) + ], 8, Qm)) + ]); +} +const f4 = /* @__PURE__ */ A(Zm, [["render", t1], ["__scopeId", "data-v-c106ae76"]]), s1 = C({ + name: "LoadingBarsWithSteps", + props: { + steps: { + type: Array, + required: !0 + }, + currentStep: { + type: Number, + required: !0 + } + }, + methods: { + getStepClass(t) { + return t < this.currentStep ? "completed" : t === this.currentStep ? "active" : "inactive"; + }, + getProgress(t) { + return t < this.currentStep ? 100 : t === this.currentStep ? 50 : 0; + } + } +}), n1 = ["aria-valuenow", "aria-valuemax"], r1 = { class: "step-label" }; +function i1(t, e, s, r, i, a) { + return d(), c("div", { + class: "loading-bars", + role: "progressbar", + "aria-valuemin": "0", + "aria-valuenow": t.currentStep, + "aria-valuemax": t.steps.length + }, [ + f("ul", null, [ + (d(!0), c(I, null, L(t.steps, (n, o) => (d(), c("li", { + key: n.id, + class: q(t.getStepClass(o)) + }, [ + f("div", { + class: "step-bar", + style: ne({ "--progress": `${t.getProgress(o)}%` }) + }, null, 4), + f("span", r1, w(n.label), 1) + ], 2))), 128)) + ]) + ], 8, n1); +} +const p4 = /* @__PURE__ */ A(s1, [["render", i1], ["__scopeId", "data-v-5c336f48"]]), a1 = C({ + name: "LoadingSpinner", + props: { + active: { + type: Boolean, + required: !0 + } + } +}), o1 = { + key: 0, + class: "spinner", + role: "status", + "aria-live": "polite", + "aria-label": "Loading" +}; +function l1(t, e, s, r, i, a) { + return t.active ? (d(), c("div", o1, [...e[0] || (e[0] = [ + f("span", { class: "visually-hidden" }, "Loading...", -1) + ])])) : P("", !0); +} +const h4 = /* @__PURE__ */ A(a1, [["render", l1], ["__scopeId", "data-v-8e2b0a89"]]), u1 = C({ + name: "MediaGallery", + props: { + images: { + type: Array, + required: !0 + } + }, + setup(t) { + const e = m("thumbnail"), s = m(0), r = m(!1); + let i; + const a = (b) => { + e.value = b; + }, n = (b) => { + s.value = b, a("expanded"); + }, o = () => { + s.value = (s.value + 1) % t.images.length; + }, u = () => { + s.value = (s.value - 1 + t.images.length) % t.images.length; + }, p = () => { + r.value ? k() : g(); + }, g = () => { + r.value = !0, i = window.setInterval(o, 3e3); + }, k = () => { + r.value = !1, clearInterval(i); + }, y = () => { + }; + return Ce(() => { + }), Tl(() => { + k(); + }), { + viewMode: e, + currentIndex: s, + isSlideshow: r, + setViewMode: a, + expandImage: n, + nextImage: o, + previousImage: u, + toggleSlideshow: p, + toggleZoom: y + }; + } +}), d1 = { + class: "media-gallery", + role: "region", + "aria-label": "Media Gallery" +}, c1 = { + key: 0, + class: "thumbnail-view" +}, f1 = ["src", "onClick"], p1 = { + key: 1, + class: "expanded-view" +}, h1 = ["src"], g1 = { class: "controls" }; +function m1(t, e, s, r, i, a) { + return d(), c("div", d1, [ + t.viewMode === "thumbnail" ? (d(), c("div", c1, [ + (d(!0), c(I, null, L(t.images, (n, o) => (d(), c("img", { + key: o, + src: n, + alt: "Thumbnail", + onClick: (u) => t.expandImage(o), + class: "thumbnail" + }, null, 8, f1))), 128)) + ])) : P("", !0), + t.viewMode === "expanded" ? (d(), c("div", p1, [ + f("button", { + onClick: e[0] || (e[0] = (...n) => t.previousImage && t.previousImage(...n)), + "aria-label": "Previous Image", + class: "nav-btn" + }, "◀"), + f("img", { + src: t.images[t.currentIndex], + alt: "Expanded View", + class: "expanded-image", + onClick: e[1] || (e[1] = (...n) => t.toggleZoom && t.toggleZoom(...n)) + }, null, 8, h1), + f("button", { + onClick: e[2] || (e[2] = (...n) => t.nextImage && t.nextImage(...n)), + "aria-label": "Next Image", + class: "nav-btn" + }, "▶") + ])) : P("", !0), + f("div", g1, [ + f("button", { + onClick: e[3] || (e[3] = (n) => t.setViewMode("thumbnail")), + "aria-label": "Thumbnail View", + class: "control-btn" + }, "Thumbnail"), + f("button", { + onClick: e[4] || (e[4] = (n) => t.setViewMode("expanded")), + "aria-label": "Expanded View", + class: "control-btn" + }, "Expanded"), + f("button", { + onClick: e[5] || (e[5] = (...n) => t.toggleSlideshow && t.toggleSlideshow(...n)), + "aria-label": "Toggle Slideshow", + class: "control-btn" + }, w(t.isSlideshow ? "Stop Slideshow" : "Start Slideshow"), 1) + ]) + ]); +} +const g4 = /* @__PURE__ */ A(u1, [["render", m1], ["__scopeId", "data-v-4974d288"]]), v1 = C({ + name: "MultipleChoicePoll", + props: { + question: { + type: String, + required: !0 + }, + options: { + type: Array, + required: !0 + }, + isDisabled: { + type: Boolean, + default: !1 + }, + showResults: { + type: Boolean, + default: !1 + } + }, + emits: ["update:selectedOptions"], + setup(t, { emit: e }) { + const s = m([]); + return { + selectedOptions: s, + handleChange: () => { + e("update:selectedOptions", s.value); + } + }; + } +}), b1 = { + role: "group", + "aria-labelledby": "poll-question", + class: "poll" +}, y1 = { id: "poll-question" }, $1 = ["id", "value", "disabled", "aria-checked"], k1 = ["for"]; +function w1(t, e, s, r, i, a) { + return d(), c("div", b1, [ + f("p", y1, w(t.question), 1), + (d(!0), c(I, null, L(t.options, (n) => (d(), c("div", { + key: n, + class: "option" + }, [ + R(f("input", { + type: "checkbox", + id: n, + value: n, + "onUpdate:modelValue": e[0] || (e[0] = (o) => t.selectedOptions = o), + disabled: t.isDisabled, + onChange: e[1] || (e[1] = (...o) => t.handleChange && t.handleChange(...o)), + "aria-checked": t.selectedOptions.includes(n) + }, null, 40, $1), [ + [_l, t.selectedOptions] + ]), + f("label", { for: n }, w(n), 9, k1) + ]))), 128)) + ]); +} +const m4 = /* @__PURE__ */ A(v1, [["render", w1], ["__scopeId", "data-v-e84ac6d0"]]), C1 = C({ + name: "MultiselectList", + props: { + items: { + type: Array, + required: !0 + } + }, + setup() { + const t = m([]), e = m(null); + return { selectedItems: t, hoveredItem: e, toggleItemSelection: (r) => { + if (r.disabled) return; + const i = t.value.indexOf(r.value); + i === -1 ? t.value.push(r.value) : t.value.splice(i, 1); + } }; + } +}), A1 = { + class: "multiselect-list", + role: "listbox", + "aria-multiselectable": "true" +}, E1 = { + class: "item-list", + role: "list", + "aria-label": "Selectable Items" +}, S1 = ["onClick", "onMouseover", "aria-selected", "aria-disabled"]; +function T1(t, e, s, r, i, a) { + return d(), c("div", A1, [ + f("ul", E1, [ + (d(!0), c(I, null, L(t.items, (n) => (d(), c("li", { + key: n.value, + class: q(["list-item", { selected: t.selectedItems.includes(n.value), disabled: n.disabled }]), + onClick: (o) => t.toggleItemSelection(n), + onMouseover: (o) => t.hoveredItem = n.value, + onMouseleave: e[0] || (e[0] = (o) => t.hoveredItem = null), + "aria-selected": t.selectedItems.includes(n.value), + "aria-disabled": n.disabled + }, w(n.label), 43, S1))), 128)) + ]) + ]); +} +const v4 = /* @__PURE__ */ A(C1, [["render", T1], ["__scopeId", "data-v-d45a7e8f"]]), _1 = C({ + name: "Notification", + props: { + notificationType: { + type: String, + default: "success" + }, + message: { + type: String, + default: "This is a notification message." + }, + isDismissed: { + type: Boolean, + default: !1 + } + }, + setup(t) { + const e = m(t.isDismissed); + return { localIsDismissed: e, dismiss: () => { + e.value = !0; + } }; + } +}); +function N1(t, e, s, r, i, a) { + return t.localIsDismissed ? P("", !0) : (d(), c("div", { + key: 0, + class: q(["notification", t.notificationType]), + role: "alert" + }, [ + f("span", null, w(t.message), 1), + f("button", { + class: "close-btn", + onClick: e[0] || (e[0] = (...n) => t.dismiss && t.dismiss(...n)), + "aria-label": "Dismiss notification" + }, "✖") + ], 2)); +} +const b4 = /* @__PURE__ */ A(_1, [["render", N1], ["__scopeId", "data-v-cce4ab9d"]]), I1 = C({ + name: "NotificationBellIcon", + props: { + hasNotifications: { + type: Boolean, + required: !0 + }, + dismissed: { + type: Boolean, + required: !1, + default: !1 + } + } +}), L1 = { + class: "notification-bell", + role: "button", + "aria-label": "Notification Bell" +}, q1 = { + key: 0, + class: "notification-dot", + "aria-label": "New Notifications" +}; +function O1(t, e, s, r, i, a) { + return d(), c("div", L1, [ + (d(), c("svg", { + class: q(["bell-icon", { "has-notifications": t.hasNotifications, dismissed: t.dismissed }]), + xmlns: "http://www.w3.org/2000/svg", + viewBox: "0 0 24 24", + fill: "currentColor", + "aria-hidden": "true" + }, [...e[0] || (e[0] = [ + f("path", { d: "M12 2C10.34 2 9 3.34 9 5v1.07C6.83 6.57 5 8.64 5 11v5h14v-5c0-2.36-1.83-4.43-4-4.93V5c0-1.66-1.34-3-3-3zm-1 19c0 .55.45 1 1 1s1-.45 1-1h-2z" }, null, -1) + ])], 2)), + t.hasNotifications ? (d(), c("span", q1)) : P("", !0) + ]); +} +const y4 = /* @__PURE__ */ A(I1, [["render", O1], ["__scopeId", "data-v-5f09368b"]]), P1 = C({ + name: "NumberInputWithIncrement", + props: { + modelValue: { + type: Number, + default: 0 + }, + step: { + type: Number, + default: 1 + }, + disabled: { + type: Boolean, + default: !1 + } + }, + emits: ["update:modelValue"], + setup(t, { emit: e }) { + const s = m(t.modelValue); + return { currentValue: s, increment: () => { + s.value += t.step, e("update:modelValue", s.value); + }, decrement: () => { + s.value -= t.step, e("update:modelValue", s.value); + } }; + } +}), D1 = { class: "number-input-container" }, R1 = ["disabled"], B1 = ["disabled"], M1 = ["disabled"]; +function F1(t, e, s, r, i, a) { + return d(), c("div", D1, [ + f("button", { + class: "decrement-button", + onClick: e[0] || (e[0] = (...n) => t.decrement && t.decrement(...n)), + disabled: t.disabled, + "aria-label": "Decrement" + }, " - ", 8, R1), + R(f("input", { + type: "number", + "onUpdate:modelValue": e[1] || (e[1] = (n) => t.currentValue = n), + disabled: t.disabled, + "aria-label": "Number Input" + }, null, 8, B1), [ + [H, t.currentValue] + ]), + f("button", { + class: "increment-button", + onClick: e[2] || (e[2] = (...n) => t.increment && t.increment(...n)), + disabled: t.disabled, + "aria-label": "Increment" + }, " + ", 8, M1) + ]); +} +const $4 = /* @__PURE__ */ A(P1, [["render", F1], ["__scopeId", "data-v-0261d126"]]), U1 = C({ + name: "NumberedList", + props: { + items: { + type: Array, + required: !0 + } + }, + setup() { + const t = m(null), e = m(null); + return { selectedItem: t, hoveredItem: e, selectItem: (r) => { + r.disabled || (t.value = r.value); + } }; + } +}), j1 = { + class: "numbered-list", + role: "list", + "aria-label": "Numbered Items" +}, V1 = ["onClick", "onMouseover", "aria-disabled"]; +function H1(t, e, s, r, i, a) { + return d(), c("ol", j1, [ + (d(!0), c(I, null, L(t.items, (n) => (d(), c("li", { + key: n.value, + class: q(["list-item", { selected: t.selectedItem === n.value, disabled: n.disabled }]), + onClick: (o) => t.selectItem(n), + onMouseover: (o) => t.hoveredItem = n.value, + onMouseleave: e[0] || (e[0] = (o) => t.hoveredItem = null), + "aria-disabled": n.disabled + }, w(n.label), 43, V1))), 128)) + ]); +} +const k4 = /* @__PURE__ */ A(U1, [["render", H1], ["__scopeId", "data-v-3038608d"]]), z1 = C({ + name: "OpenEndedPoll", + props: { + question: { + type: String, + required: !0 + }, + initialResponses: { + type: Array, + default: () => [] + }, + resultsVisible: { + type: Boolean, + default: !1 + }, + disabled: { + type: Boolean, + default: !1 + } + }, + setup(t) { + const e = m(""), s = m([...t.initialResponses]); + return { + response: e, + responses: s, + submitResponse: () => { + e.value.trim() && (s.value.push(e.value.trim()), e.value = ""); + } + }; + } +}), G1 = { + class: "open-ended-poll", + role: "form", + "aria-labelledby": "open-ended-poll-label" +}, K1 = { + id: "open-ended-poll-label", + class: "poll-label" +}, W1 = ["disabled", "aria-disabled"], Z1 = ["disabled"], X1 = { + key: 0, + class: "results" +}; +function Y1(t, e, s, r, i, a) { + return d(), c("div", G1, [ + f("label", K1, w(t.question), 1), + R(f("textarea", { + "onUpdate:modelValue": e[0] || (e[0] = (n) => t.response = n), + disabled: t.disabled, + "aria-disabled": t.disabled, + "aria-required": "true", + placeholder: "Type your response here...", + class: "response-input" + }, null, 8, W1), [ + [H, t.response] + ]), + f("button", { + onClick: e[1] || (e[1] = (...n) => t.submitResponse && t.submitResponse(...n)), + disabled: t.disabled || !t.response.trim(), + class: "submit-button" + }, " Submit ", 8, Z1), + t.resultsVisible ? (d(), c("div", X1, [ + e[2] || (e[2] = f("h3", null, "Responses:", -1)), + f("ul", null, [ + (d(!0), c(I, null, L(t.responses, (n, o) => (d(), c("li", { key: o }, w(n), 1))), 128)) + ]) + ])) : P("", !0) + ]); +} +const w4 = /* @__PURE__ */ A(z1, [["render", Y1], ["__scopeId", "data-v-5c6668a4"]]), Q1 = C({ + name: "Pagination", + props: { + totalPages: { + type: Number, + required: !0 + }, + currentPage: { + type: Number, + required: !0 + } + }, + setup(t, { emit: e }) { + const s = m(null); + return { pages: W(() => Array.from({ length: t.totalPages }, (a, n) => n + 1)), hoveredPage: s, changePage: (a) => { + a !== t.currentPage && e("update:currentPage", a); + } }; + } +}), J1 = { + class: "pagination", + "aria-label": "Pagination Navigation" +}, x1 = { class: "pagination-list" }, ev = ["onClick", "onMouseover", "aria-current"]; +function tv(t, e, s, r, i, a) { + return d(), c("nav", J1, [ + f("ul", x1, [ + (d(!0), c(I, null, L(t.pages, (n) => (d(), c("li", { + key: n, + class: q(["pagination-item", { active: n === t.currentPage }]), + onClick: (o) => t.changePage(n), + onMouseover: (o) => t.hoveredPage = n, + onMouseleave: e[0] || (e[0] = (o) => t.hoveredPage = null), + "aria-current": n === t.currentPage ? "page" : void 0 + }, w(n), 43, ev))), 128)) + ]) + ]); +} +const C4 = /* @__PURE__ */ A(Q1, [["render", tv], ["__scopeId", "data-v-62783a13"]]), sv = C({ + name: "PaginationControl", + props: { + totalPages: { + type: Number, + required: !0 + }, + currentPage: { + type: Number, + default: 1 + }, + rowsPerPageOptions: { + type: Array, + default: () => [10, 20, 50, 100] + } + }, + emits: ["update:currentPage", "update:rowsPerPage"], + setup(t, { emit: e }) { + const s = m(t.rowsPerPageOptions[0]), r = W(() => t.currentPage === 1), i = W(() => t.currentPage === t.totalPages); + return { + selectedRowsPerPage: s, + isFirstPage: r, + isLastPage: i, + setPage: (o) => { + o >= 1 && o <= t.totalPages && e("update:currentPage", o); + }, + setRowsPerPage: (o) => { + s.value = o, e("update:rowsPerPage", o); + } + }; + } +}), nv = { class: "pagination-control" }, rv = ["disabled"], iv = ["disabled"], av = ["disabled"], ov = ["disabled"], lv = ["value"]; +function uv(t, e, s, r, i, a) { + return d(), c("div", nv, [ + f("button", { + onClick: e[0] || (e[0] = (n) => t.setPage(1)), + disabled: t.isFirstPage + }, "First", 8, rv), + f("button", { + onClick: e[1] || (e[1] = (n) => t.setPage(t.currentPage - 1)), + disabled: t.isFirstPage + }, "Previous", 8, iv), + f("span", null, "Page " + w(t.currentPage) + " of " + w(t.totalPages), 1), + f("button", { + onClick: e[2] || (e[2] = (n) => t.setPage(t.currentPage + 1)), + disabled: t.isLastPage + }, "Next", 8, av), + f("button", { + onClick: e[3] || (e[3] = (n) => t.setPage(t.totalPages)), + disabled: t.isLastPage + }, "Last", 8, ov), + R(f("select", { + "onUpdate:modelValue": e[4] || (e[4] = (n) => t.selectedRowsPerPage = n), + onChange: e[5] || (e[5] = (n) => t.setRowsPerPage(t.selectedRowsPerPage)) + }, [ + (d(!0), c(I, null, L(t.rowsPerPageOptions, (n) => (d(), c("option", { + key: n, + value: n + }, w(n) + " rows per page ", 9, lv))), 128)) + ], 544), [ + [ye, t.selectedRowsPerPage] + ]) + ]); +} +const A4 = /* @__PURE__ */ A(sv, [["render", uv], ["__scopeId", "data-v-e9633912"]]), dv = C({ + name: "PasswordConfirmationField", + props: { + disabled: { + type: Boolean, + default: !1 + } + }, + setup() { + const t = m(""), e = m(""), s = W(() => t.value === e.value); + return { password: t, confirmPassword: e, isMatching: s }; + } +}), cv = { class: "password-confirmation-container" }, fv = ["disabled"], pv = ["disabled"]; +function hv(t, e, s, r, i, a) { + return d(), c("div", cv, [ + R(f("input", { + type: "password", + "onUpdate:modelValue": e[0] || (e[0] = (n) => t.password = n), + disabled: t.disabled, + placeholder: "Enter password", + "aria-label": "Enter password" + }, null, 8, fv), [ + [H, t.password] + ]), + R(f("input", { + type: "password", + "onUpdate:modelValue": e[1] || (e[1] = (n) => t.confirmPassword = n), + disabled: t.disabled, + placeholder: "Confirm password", + "aria-label": "Confirm password" + }, null, 8, pv), [ + [H, t.confirmPassword] + ]), + f("p", { + class: q({ match: t.isMatching, "not-match": !t.isMatching }) + }, w(t.isMatching ? "Passwords match" : "Passwords do not match"), 3) + ]); +} +const E4 = /* @__PURE__ */ A(dv, [["render", hv], ["__scopeId", "data-v-7bc855fa"]]), gv = C({ + name: "PinnedList", + props: { + items: { + type: Array, + required: !0 + }, + selectedItem: { + type: Number, + required: !0 + } + }, + setup(t, { emit: e }) { + return { hoveredItem: m(null), selectItem: (i) => { + e("update:selectedItem", i); + } }; + } +}), mv = { class: "pinned-list" }, vv = ["onClick", "onMouseover", "aria-selected"]; +function bv(t, e, s, r, i, a) { + return d(), c("ul", mv, [ + (d(!0), c(I, null, L(t.items, (n) => (d(), c("li", { + key: n.id, + class: q([ + "pinned-list-item", + { pinned: n.pinned, selected: n.id === t.selectedItem, hover: n.id === t.hoveredItem } + ]), + onClick: (o) => t.selectItem(n.id), + onMouseover: (o) => t.hoveredItem = n.id, + onMouseleave: e[0] || (e[0] = (o) => t.hoveredItem = null), + "aria-selected": n.id === t.selectedItem ? "true" : "false" + }, w(n.label), 43, vv))), 128)) + ]); +} +const S4 = /* @__PURE__ */ A(gv, [["render", bv], ["__scopeId", "data-v-ecd634ef"]]), yv = C({ + name: "PlayingCard", + props: { + flipped: { + type: Boolean, + default: !1 + }, + disabled: { + type: Boolean, + default: !1 + } + }, + emits: ["update:flipped"], + setup(t, { emit: e }) { + return { handleClick: () => { + t.disabled || e("update:flipped", !t.flipped); + } }; + } +}), $v = ["aria-pressed", "aria-disabled"], kv = { + key: 0, + class: "card-face card-front" +}, wv = { + key: 1, + class: "card-face card-back" +}; +function Cv(t, e, s, r, i, a) { + return d(), c("div", { + class: q(["playing-card", { "is-flipped": t.flipped, "is-disabled": t.disabled }]), + onClick: e[0] || (e[0] = (...n) => t.handleClick && t.handleClick(...n)), + role: "button", + "aria-pressed": t.flipped, + "aria-disabled": t.disabled, + tabindex: "0" + }, [ + t.flipped ? (d(), c("div", wv, [ + ie(t.$slots, "front", {}, () => [ + e[2] || (e[2] = Fe("Ace of Spades", -1)) + ], !0) + ])) : (d(), c("div", kv, [ + ie(t.$slots, "back", {}, () => [ + e[1] || (e[1] = Fe("Back Design", -1)) + ], !0) + ])) + ], 10, $v); +} +const T4 = /* @__PURE__ */ A(yv, [["render", Cv], ["__scopeId", "data-v-4b36289e"]]), Av = C({ + name: "PodcastPlayer", + props: { + episodes: { + type: Array, + required: !0 + } + }, + setup() { + const t = m(!1), e = m(null), s = m(!1), r = m(!0); + return { + isPlaying: t, + currentEpisodeIndex: e, + isDownloading: s, + showEpisodeList: r, + togglePlayPause: () => { + e.value !== null && (t.value = !t.value); + }, + playEpisode: (o) => { + e.value = o, t.value = !0; + }, + downloadEpisode: () => { + e.value !== null && (s.value = !0, setTimeout(() => { + s.value = !1; + }, 3e3)); + } + }; + } +}), Ev = { + class: "podcast-player", + role: "region", + "aria-label": "Podcast Player" +}, Sv = { class: "player-controls" }, Tv = { + key: 0, + class: "episode-list" +}, _v = ["onClick"]; +function Nv(t, e, s, r, i, a) { + return d(), c("div", Ev, [ + f("div", Sv, [ + f("button", { + onClick: e[0] || (e[0] = (...n) => t.togglePlayPause && t.togglePlayPause(...n)), + "aria-label": "Play/Pause", + class: "control-btn" + }, w(t.isPlaying ? "Pause" : "Play"), 1), + f("button", { + onClick: e[1] || (e[1] = (...n) => t.downloadEpisode && t.downloadEpisode(...n)), + "aria-label": "Download Episode", + class: "control-btn" + }, w(t.isDownloading ? "Downloading..." : "Download"), 1) + ]), + t.showEpisodeList ? (d(), c("div", Tv, [ + f("ul", null, [ + (d(!0), c(I, null, L(t.episodes, (n, o) => (d(), c("li", { + key: o, + onClick: (u) => t.playEpisode(o) + }, w(n.title), 9, _v))), 128)) + ]) + ])) : P("", !0) + ]); +} +const _4 = /* @__PURE__ */ A(Av, [["render", Nv], ["__scopeId", "data-v-6c576cf9"]]), Iv = C({ + name: "PokerChips", + props: { + chips: { + type: Array, + default: () => [] + }, + state: { + type: String, + default: "stacked", + validator: (t) => ["stacked", "moving", "betPlaced", "allIn"].includes(t) + } + }, + computed: { + stateClass() { + return this.state; + } + } +}), Lv = ["aria-label"]; +function qv(t, e, s, r, i, a) { + return d(), c("div", { + class: q(["poker-chips", t.stateClass]) + }, [ + (d(!0), c(I, null, L(t.chips, (n) => (d(), c("div", { + key: n.id, + class: "chip", + style: ne({ backgroundColor: n.color }), + "aria-label": `Chip denomination: ${n.denomination}` + }, w(n.denomination), 13, Lv))), 128)) + ], 2); +} +const N4 = /* @__PURE__ */ A(Iv, [["render", qv], ["__scopeId", "data-v-4c1ec75e"]]), Ov = C({ + name: "PokerHand", + props: { + cards: { + type: Array, + required: !0 + }, + revealed: { + type: Boolean, + default: !1 + }, + folded: { + type: Boolean, + default: !1 + } + } +}), Pv = ["data-folded"], Dv = ["data-revealed"], Rv = { + key: 0, + class: "card-value" +}; +function Bv(t, e, s, r, i, a) { + return d(), c("div", { + class: "poker-hand", + "aria-label": "Poker Hand", + "data-folded": t.folded + }, [ + (d(!0), c(I, null, L(t.cards, (n) => (d(), c("div", { + key: n.id, + class: "card", + "data-revealed": t.revealed + }, [ + t.revealed ? (d(), c("span", Rv, w(n.value), 1)) : P("", !0) + ], 8, Dv))), 128)) + ], 8, Pv); +} +const I4 = /* @__PURE__ */ A(Ov, [["render", Bv], ["__scopeId", "data-v-a5885677"]]), Mv = C({ + name: "PokerTable", + props: { + seats: { + type: Array, + default: () => [] + }, + communityCards: { + type: Array, + default: () => [] + }, + tableColor: { + type: String, + default: "var(--table-green)" + } + } +}), Fv = { class: "community-cards" }; +function Uv(t, e, s, r, i, a) { + return d(), c("div", { + class: "poker-table", + style: ne({ backgroundColor: t.tableColor }) + }, [ + (d(!0), c(I, null, L(t.seats, (n) => (d(), c("div", { + class: "seats", + key: n.id + }, [ + f("div", { + class: q(["player", { "player-active": n.active }]) + }, w(n.name), 3) + ]))), 128)), + f("div", Fv, [ + (d(!0), c(I, null, L(t.communityCards, (n) => (d(), c("div", { + class: "card", + key: n.id + }, w(n.suit) + w(n.rank), 1))), 128)) + ]), + e[0] || (e[0] = f("div", { + class: "dealer-button", + role: "button", + "aria-label": "Dealer button" + }, null, -1)) + ], 4); +} +const L4 = /* @__PURE__ */ A(Mv, [["render", Uv], ["__scopeId", "data-v-e5c0bbc5"]]), jv = C({ + name: "PokerTimer", + props: { + initialTime: { + type: Number, + default: 30 + } + }, + setup(t) { + const e = m(t.initialTime), s = m(!1), r = W(() => { + const u = Math.floor(e.value / 60), p = e.value % 60; + return `${u}:${p < 10 ? "0" : ""}${p}`; + }), i = W(() => e.value <= 10), a = () => { + !s.value && e.value > 0 && (e.value -= 1); + }, n = () => { + s.value = !s.value; + }; + let o; + return Ce(() => { + o = setInterval(a, 1e3); + }), Di(() => { + clearInterval(o); + }), { + timeLeft: e, + formattedTime: r, + timeRunningOut: i, + isPaused: s, + togglePause: n + }; + } +}), Vv = { + class: "poker-timer", + "aria-label": "Poker Timer" +}; +function Hv(t, e, s, r, i, a) { + return d(), c("div", Vv, [ + f("div", { + class: q(["timer-display", { "time-running-out": t.timeRunningOut }]) + }, w(t.formattedTime), 3), + f("button", { + onClick: e[0] || (e[0] = (...n) => t.togglePause && t.togglePause(...n)), + class: "timer-button" + }, w(t.isPaused ? "Resume" : "Pause"), 1) + ]); +} +const q4 = /* @__PURE__ */ A(jv, [["render", Hv], ["__scopeId", "data-v-439e81ff"]]), zv = C({ + name: "Pot", + props: { + chips: { + type: Array, + default: () => [] + }, + totalChips: { + type: Number, + default: 0 + }, + isWon: { + type: Boolean, + default: !1 + } + } +}), Gv = { class: "total" }; +function Kv(t, e, s, r, i, a) { + return d(), c("div", { + class: q(["pot", { won: t.isWon }]), + "aria-label": "Poker Pot" + }, [ + f("div", { + class: q(["chips", { empty: t.totalChips === 0 }]) + }, [ + fo(Nl, { + name: "chip-move", + tag: "div", + class: "chip-stack" + }, { + default: Ri(() => [ + (d(!0), c(I, null, L(t.chips, (n, o) => (d(), c("div", { + key: o, + class: "chip" + }, w(n), 1))), 128)) + ]), + _: 1 + }) + ], 2), + f("div", Gv, "Total: " + w(t.totalChips), 1) + ], 2); +} +const O4 = /* @__PURE__ */ A(zv, [["render", Kv], ["__scopeId", "data-v-177d41d0"]]), Wv = C({ + name: "ProgressBar", + props: { + progress: { + type: Number, + required: !0, + validator: (t) => t >= 0 && t <= 100 + }, + disabled: { + type: Boolean, + required: !1, + default: !1 + } + } +}), Zv = ["aria-valuenow", "aria-label"]; +function Xv(t, e, s, r, i, a) { + return d(), c("div", { + class: q(["progress-bar-container", { disabled: t.disabled }]), + role: "progressbar", + "aria-valuenow": t.progress, + "aria-valuemin": "0", + "aria-valuemax": "100", + "aria-label": `Progress: ${t.progress}%` + }, [ + f("div", { + class: "progress-bar", + style: ne({ width: t.progress + "%" }) + }, null, 4) + ], 10, Zv); +} +const P4 = /* @__PURE__ */ A(Wv, [["render", Xv], ["__scopeId", "data-v-0da1f739"]]), Yv = C({ + name: "ProgressCircle", + props: { + progress: { + type: Number, + required: !0, + validator: (t) => t >= 0 && t <= 100 + }, + status: { + type: String, + required: !1, + default: "active", + validator: (t) => ["complete", "incomplete", "paused", "active"].includes(t) + } + } +}), Qv = ["aria-valuenow", "aria-label"], Jv = { + viewBox: "0 0 36 36", + class: "circular-chart" +}, xv = ["stroke-dasharray"]; +function eb(t, e, s, r, i, a) { + return d(), c("div", { + class: "progress-circle", + role: "progressbar", + "aria-valuenow": t.progress, + "aria-valuemin": "0", + "aria-valuemax": "100", + "aria-label": `Progress: ${t.progress}%`, + status: t.status + }, [ + (d(), c("svg", Jv, [ + e[0] || (e[0] = f("path", { + class: "circle-bg", + d: "M18 2.0845 a 15.9155 15.9155 0 0 1 0 31.831 a 15.9155 15.9155 0 0 1 0 -31.831" + }, null, -1)), + f("path", { + class: "circle", + "stroke-dasharray": `${t.progress}, 100`, + d: "M18 2.0845 a 15.9155 15.9155 0 0 1 0 31.831 a 15.9155 15.9155 0 0 1 0 -31.831" + }, null, 8, xv) + ])) + ], 8, Qv); +} +const D4 = /* @__PURE__ */ A(Yv, [["render", eb], ["__scopeId", "data-v-d33da7d5"]]), tb = C({ + name: "PublicViewCalendar", + props: { + selectedCategoryProp: { + type: String, + default: "" + }, + selectedEventProp: { + type: Object, + default: null + } + }, + setup(t) { + const e = m([ + { id: 1, title: "Project Meeting", description: "Discussing project scope.", category: "Work", location: "Room 101", date: "2023-11-01" }, + { id: 2, title: "Yoga Class", description: "Morning yoga session.", category: "Health", location: "Gym", date: "2023-11-02" } + ]), s = m([...new Set(e.value.map((g) => g.category))]), r = m([...new Set(e.value.map((g) => g.location))]), i = m(""), a = m(""), n = m(t.selectedEventProp), o = W(() => e.value.filter((g) => (!i.value || g.category === i.value) && (!a.value || g.location === a.value))); + return { + categories: s, + locations: r, + selectedCategory: i, + selectedLocation: a, + filteredEvents: o, + selectedEvent: n, + showEventDetails: (g) => { + n.value = g; + }, + closeEventDetails: () => { + n.value = null; + } + }; + } +}), sb = { + class: "calendar", + role: "region", + "aria-label": "Public Calendar" +}, nb = { class: "filter-options" }, rb = ["value"], ib = ["value"], ab = { class: "events" }, ob = ["onClick"], lb = { + key: 0, + class: "event-details", + role: "dialog", + "aria-labelledby": "event-title" +}, ub = { id: "event-title" }; +function db(t, e, s, r, i, a) { + return d(), c("div", sb, [ + f("div", nb, [ + e[5] || (e[5] = f("label", { for: "category-filter" }, "Category:", -1)), + R(f("select", { + id: "category-filter", + "onUpdate:modelValue": e[0] || (e[0] = (n) => t.selectedCategory = n), + "aria-label": "Filter by category" + }, [ + e[3] || (e[3] = f("option", { value: "" }, "All", -1)), + (d(!0), c(I, null, L(t.categories, (n) => (d(), c("option", { + key: n, + value: n + }, w(n), 9, rb))), 128)) + ], 512), [ + [ye, t.selectedCategory] + ]), + e[6] || (e[6] = f("label", { for: "location-filter" }, "Location:", -1)), + R(f("select", { + id: "location-filter", + "onUpdate:modelValue": e[1] || (e[1] = (n) => t.selectedLocation = n), + "aria-label": "Filter by location" + }, [ + e[4] || (e[4] = f("option", { value: "" }, "All", -1)), + (d(!0), c(I, null, L(t.locations, (n) => (d(), c("option", { + key: n, + value: n + }, w(n), 9, ib))), 128)) + ], 512), [ + [ye, t.selectedLocation] + ]) + ]), + f("div", ab, [ + (d(!0), c(I, null, L(t.filteredEvents, (n) => (d(), c("div", { + key: n.id, + class: "event", + onClick: (o) => t.showEventDetails(n) + }, [ + f("h3", null, w(n.title), 1), + f("p", null, w(n.date), 1) + ], 8, ob))), 128)) + ]), + fo(co, { name: "fade" }, { + default: Ri(() => [ + t.selectedEvent ? (d(), c("div", lb, [ + f("h2", ub, w(t.selectedEvent.title), 1), + f("p", null, w(t.selectedEvent.description), 1), + f("button", { + class: "close-details", + onClick: e[2] || (e[2] = (...n) => t.closeEventDetails && t.closeEventDetails(...n)), + "aria-label": "Close event details" + }, "Close") + ])) : P("", !0) + ]), + _: 1 + }) + ]); +} +const R4 = /* @__PURE__ */ A(tb, [["render", db], ["__scopeId", "data-v-4ed1ea3e"]]), cb = C({ + name: "RadioButton", + props: { + checked: { + type: Boolean, + default: !1 + }, + disabled: { + type: Boolean, + default: !1 + }, + value: { + type: String, + required: !0 + }, + name: { + type: String, + required: !0 + } + }, + emits: ["update:checked"], + methods: { + onChange(t) { + this.disabled || this.$emit("update:checked", t.target.checked); + } + } +}), fb = { class: "radio-button-container" }, pb = ["checked", "disabled", "aria-checked", "aria-disabled"], hb = { class: "radio-label" }; +function gb(t, e, s, r, i, a) { + return d(), c("div", fb, [ + f("label", { + class: q({ disabled: t.disabled }) + }, [ + f("input", { + type: "radio", + checked: t.checked, + disabled: t.disabled, + onChange: e[0] || (e[0] = (...n) => t.onChange && t.onChange(...n)), + "aria-checked": t.checked, + "aria-disabled": t.disabled + }, null, 40, pb), + f("span", hb, [ + ie(t.$slots, "default", {}, void 0, !0) + ]) + ], 2) + ]); +} +const B4 = /* @__PURE__ */ A(cb, [["render", gb], ["__scopeId", "data-v-31d50aca"]]), mb = C({ + name: "RaiseButton", + props: { + disabled: { + type: Boolean, + default: !1 + } + } +}), vb = ["disabled"]; +function bb(t, e, s, r, i, a) { + return d(), c("button", { + class: "raise-button", + disabled: t.disabled, + "aria-label": "Raise Bet" + }, " Raise ", 8, vb); +} +const M4 = /* @__PURE__ */ A(mb, [["render", bb], ["__scopeId", "data-v-cd38f00c"]]), yb = C({ + name: "RangeSlider", + props: { + min: { + type: Number, + default: 0 + }, + max: { + type: Number, + default: 100 + }, + value: { + type: Number, + default: 50 + }, + step: { + type: Number, + default: 1 + }, + disabled: { + type: Boolean, + default: !1 + }, + label: { + type: String, + default: "" + }, + labelPosition: { + type: String, + default: "right" + } + }, + emits: ["update:value"], + methods: { + onInput(t) { + this.$emit("update:value", Number(t.target.value)); + }, + onFocus() { + this.$el.classList.add("focused"); + }, + onBlur() { + this.$el.classList.remove("focused"); + } + } +}), $b = { class: "range-slider-container" }, kb = ["aria-label", "aria-disabled"], wb = { key: 0 }, Cb = ["min", "max", "step", "value", "disabled"], Ab = { key: 1 }; +function Eb(t, e, s, r, i, a) { + return d(), c("div", $b, [ + f("label", { + class: q(["range-label", t.labelPosition]), + "aria-label": `Range slider at ${t.value}`, + "aria-disabled": t.disabled + }, [ + t.labelPosition === "left" || t.labelPosition === "center" ? (d(), c("span", wb, w(t.label), 1)) : P("", !0), + f("input", { + type: "range", + min: t.min, + max: t.max, + step: t.step, + value: t.value, + disabled: t.disabled, + onInput: e[0] || (e[0] = (...n) => t.onInput && t.onInput(...n)), + onFocus: e[1] || (e[1] = (...n) => t.onFocus && t.onFocus(...n)), + onBlur: e[2] || (e[2] = (...n) => t.onBlur && t.onBlur(...n)) + }, null, 40, Cb), + t.labelPosition === "right" ? (d(), c("span", Ab, w(t.label), 1)) : P("", !0) + ], 10, kb) + ]); +} +const F4 = /* @__PURE__ */ A(yb, [["render", Eb], ["__scopeId", "data-v-99cac0a4"]]), Sb = C({ + name: "RankingPoll", + props: { + question: { + type: String, + required: !0 + }, + options: { + type: Array, + required: !0 + }, + isDisabled: { + type: Boolean, + default: !1 + }, + showResults: { + type: Boolean, + default: !1 + } + }, + setup(t) { + const e = m([...t.options]); + return { + rankedOptions: e, + onDragStart: (i) => (a) => { + var n; + (n = a.dataTransfer) == null || n.setData("text/plain", i.toString()); + }, + onDrop: (i) => (a) => { + var u; + const n = Number((u = a.dataTransfer) == null ? void 0 : u.getData("text")), o = e.value[n]; + e.value.splice(n, 1), e.value.splice(i, 0, o); + } + }; + } +}), Tb = { + role: "list", + "aria-labelledby": "poll-question", + class: "ranking-poll" +}, _b = { id: "poll-question" }, Nb = ["draggable", "onDragstart", "onDrop"]; +function Ib(t, e, s, r, i, a) { + return d(), c("div", Tb, [ + f("p", _b, w(t.question), 1), + f("ul", null, [ + (d(!0), c(I, null, L(t.rankedOptions, (n, o) => (d(), c("li", { + key: n, + draggable: !t.isDisabled, + onDragstart: (u) => t.onDragStart(o), + onDragover: e[0] || (e[0] = de(() => { + }, ["prevent"])), + onDrop: (u) => t.onDrop(o), + class: "rank-option" + }, [ + f("span", null, w(o + 1) + ".", 1), + Fe(" " + w(n), 1) + ], 40, Nb))), 128)) + ]) + ]); +} +const U4 = /* @__PURE__ */ A(Sb, [["render", Ib], ["__scopeId", "data-v-f7cd36d5"]]), Lb = C({ + name: "RatingStars", + props: { + maxStars: { + type: Number, + default: 5 + }, + initialRating: { + type: Number, + default: 0 + }, + inactive: { + type: Boolean, + default: !1 + } + }, + setup(t) { + const e = m(t.initialRating), s = m(0); + return { + currentRating: e, + hoverRating: s, + selectRating: (a) => { + t.inactive || (e.value = a); + }, + setHoverRating: (a) => { + t.inactive || (s.value = a); + }, + stars: Array.from({ length: t.maxStars }, (a, n) => n + 1) + }; + } +}), qb = { + class: "rating-stars", + role: "radiogroup", + "aria-label": "Rating" +}, Ob = ["aria-checked", "aria-label", "onClick", "onMouseenter"]; +function Pb(t, e, s, r, i, a) { + return d(), c("div", qb, [ + (d(!0), c(I, null, L(t.stars, (n) => (d(), c("button", { + key: n, + type: "button", + class: q(["star", { active: n <= t.currentRating, hover: n <= t.hoverRating }]), + "aria-checked": n === t.currentRating, + "aria-label": `${n} Star${n > 1 ? "s" : ""}`, + onClick: (o) => t.selectRating(n), + onMouseenter: (o) => t.setHoverRating(n), + onMouseleave: e[0] || (e[0] = (o) => t.setHoverRating(0)) + }, " ★ ", 42, Ob))), 128)) + ]); +} +const j4 = /* @__PURE__ */ A(Lb, [["render", Pb], ["__scopeId", "data-v-97f264b6"]]), Db = C({ + name: "RecurringEventScheduler", + props: { + feedbackMessageProp: { + type: String, + default: "" + } + }, + setup(t) { + const e = m("daily"), s = m(""), r = m(""), i = m(t.feedbackMessageProp); + return { + recurrencePattern: e, + startDate: s, + endDate: r, + feedbackMessage: i, + setRecurrence: () => { + s.value && r.value ? i.value = `Recurrence set: ${e.value} from ${s.value} to ${r.value}` : i.value = "Please select start and end dates"; + } + }; + } +}), Rb = { + class: "recurring-event-scheduler", + role: "region", + "aria-label": "Recurring Event Scheduler" +}, Bb = { class: "recurrence-settings" }, Mb = { + key: 0, + class: "feedback" +}; +function Fb(t, e, s, r, i, a) { + return d(), c("div", Rb, [ + f("div", Bb, [ + e[5] || (e[5] = f("label", { for: "recurrence-pattern" }, "Recurrence Pattern:", -1)), + R(f("select", { + id: "recurrence-pattern", + "onUpdate:modelValue": e[0] || (e[0] = (n) => t.recurrencePattern = n), + "aria-label": "Select recurrence pattern" + }, [...e[4] || (e[4] = [ + f("option", { value: "daily" }, "Daily", -1), + f("option", { value: "weekly" }, "Weekly", -1), + f("option", { value: "monthly" }, "Monthly", -1) + ])], 512), [ + [ye, t.recurrencePattern] + ]), + e[6] || (e[6] = f("label", { for: "start-date" }, "Start Date:", -1)), + R(f("input", { + id: "start-date", + type: "date", + "onUpdate:modelValue": e[1] || (e[1] = (n) => t.startDate = n), + "aria-label": "Select start date" + }, null, 512), [ + [H, t.startDate] + ]), + e[7] || (e[7] = f("label", { for: "end-date" }, "End Date:", -1)), + R(f("input", { + id: "end-date", + type: "date", + "onUpdate:modelValue": e[2] || (e[2] = (n) => t.endDate = n), + "aria-label": "Select end date" + }, null, 512), [ + [H, t.endDate] + ]) + ]), + f("button", { + onClick: e[3] || (e[3] = (...n) => t.setRecurrence && t.setRecurrence(...n)), + "aria-label": "Set recurrence" + }, "Set Recurrence"), + t.feedbackMessage ? (d(), c("div", Mb, w(t.feedbackMessage), 1)) : P("", !0) + ]); +} +const V4 = /* @__PURE__ */ A(Db, [["render", Fb], ["__scopeId", "data-v-29126a52"]]); +var po = typeof global == "object" && global && global.Object === Object && global, Ub = typeof self == "object" && self && self.Object === Object && self, et = po || Ub || Function("return this")(), $t = et.Symbol, ho = Object.prototype, jb = ho.hasOwnProperty, Vb = ho.toString, Os = $t ? $t.toStringTag : void 0; +function Hb(t) { + var e = jb.call(t, Os), s = t[Os]; + try { + t[Os] = void 0; + var r = !0; + } catch { + } + var i = Vb.call(t); + return r && (e ? t[Os] = s : delete t[Os]), i; +} +var zb = Object.prototype, Gb = zb.toString; +function Kb(t) { + return Gb.call(t); +} +var Wb = "[object Null]", Zb = "[object Undefined]", oa = $t ? $t.toStringTag : void 0; +function ps(t) { + return t == null ? t === void 0 ? Zb : Wb : oa && oa in Object(t) ? Hb(t) : Kb(t); +} +function ot(t) { + return t != null && typeof t == "object"; +} +var qt = Array.isArray; +function kt(t) { + var e = typeof t; + return t != null && (e == "object" || e == "function"); +} +function go(t) { + return t; +} +var Xb = "[object AsyncFunction]", Yb = "[object Function]", Qb = "[object GeneratorFunction]", Jb = "[object Proxy]"; +function Bi(t) { + if (!kt(t)) + return !1; + var e = ps(t); + return e == Yb || e == Qb || e == Xb || e == Jb; +} +var Xr = et["__core-js_shared__"], la = function() { + var t = /[^.]+$/.exec(Xr && Xr.keys && Xr.keys.IE_PROTO || ""); + return t ? "Symbol(src)_1." + t : ""; +}(); +function xb(t) { + return !!la && la in t; +} +var e0 = Function.prototype, t0 = e0.toString; +function Dt(t) { + if (t != null) { + try { + return t0.call(t); + } catch { + } + try { + return t + ""; + } catch { + } + } + return ""; +} +var s0 = /[\\^$.*+?()[\]{}|]/g, n0 = /^\[object .+?Constructor\]$/, r0 = Function.prototype, i0 = Object.prototype, a0 = r0.toString, o0 = i0.hasOwnProperty, l0 = RegExp( + "^" + a0.call(o0).replace(s0, "\\$&").replace(/hasOwnProperty|(function).*?(?=\\\()| for .+?(?=\\\])/g, "$1.*?") + "$" +); +function u0(t) { + if (!kt(t) || xb(t)) + return !1; + var e = Bi(t) ? l0 : n0; + return e.test(Dt(t)); +} +function d0(t, e) { + return t == null ? void 0 : t[e]; +} +function Rt(t, e) { + var s = d0(t, e); + return u0(s) ? s : void 0; +} +var ui = Rt(et, "WeakMap"), ua = Object.create, c0 = /* @__PURE__ */ function() { + function t() { + } + return function(e) { + if (!kt(e)) + return {}; + if (ua) + return ua(e); + t.prototype = e; + var s = new t(); + return t.prototype = void 0, s; + }; +}(); +function f0(t, e, s) { + switch (s.length) { + case 0: + return t.call(e); + case 1: + return t.call(e, s[0]); + case 2: + return t.call(e, s[0], s[1]); + case 3: + return t.call(e, s[0], s[1], s[2]); + } + return t.apply(e, s); +} +function p0(t, e) { + var s = -1, r = t.length; + for (e || (e = Array(r)); ++s < r; ) + e[s] = t[s]; + return e; +} +var h0 = 800, g0 = 16, m0 = Date.now; +function v0(t) { + var e = 0, s = 0; + return function() { + var r = m0(), i = g0 - (r - s); + if (s = r, i > 0) { + if (++e >= h0) + return arguments[0]; + } else + e = 0; + return t.apply(void 0, arguments); + }; +} +function b0(t) { + return function() { + return t; + }; +} +var Mn = function() { + try { + var t = Rt(Object, "defineProperty"); + return t({}, "", {}), t; + } catch { + } +}(), y0 = Mn ? function(t, e) { + return Mn(t, "toString", { + configurable: !0, + enumerable: !1, + value: b0(e), + writable: !0 + }); +} : go, $0 = v0(y0); +function k0(t, e) { + for (var s = -1, r = t == null ? 0 : t.length; ++s < r && e(t[s], s, t) !== !1; ) + ; + return t; +} +var w0 = 9007199254740991, C0 = /^(?:0|[1-9]\d*)$/; +function mo(t, e) { + var s = typeof t; + return e = e ?? w0, !!e && (s == "number" || s != "symbol" && C0.test(t)) && t > -1 && t % 1 == 0 && t < e; +} +function Mi(t, e, s) { + e == "__proto__" && Mn ? Mn(t, e, { + configurable: !0, + enumerable: !0, + value: s, + writable: !0 + }) : t[e] = s; +} +function Ys(t, e) { + return t === e || t !== t && e !== e; +} +var A0 = Object.prototype, E0 = A0.hasOwnProperty; +function vo(t, e, s) { + var r = t[e]; + (!(E0.call(t, e) && Ys(r, s)) || s === void 0 && !(e in t)) && Mi(t, e, s); +} +function S0(t, e, s, r) { + var i = !s; + s || (s = {}); + for (var a = -1, n = e.length; ++a < n; ) { + var o = e[a], u = void 0; + u === void 0 && (u = t[o]), i ? Mi(s, o, u) : vo(s, o, u); + } + return s; +} +var da = Math.max; +function T0(t, e, s) { + return e = da(e === void 0 ? t.length - 1 : e, 0), function() { + for (var r = arguments, i = -1, a = da(r.length - e, 0), n = Array(a); ++i < a; ) + n[i] = r[e + i]; + i = -1; + for (var o = Array(e + 1); ++i < e; ) + o[i] = r[i]; + return o[e] = s(n), f0(t, this, o); + }; +} +function _0(t, e) { + return $0(T0(t, e, go), t + ""); +} +var N0 = 9007199254740991; +function bo(t) { + return typeof t == "number" && t > -1 && t % 1 == 0 && t <= N0; +} +function zn(t) { + return t != null && bo(t.length) && !Bi(t); +} +function I0(t, e, s) { + if (!kt(s)) + return !1; + var r = typeof e; + return (r == "number" ? zn(s) && mo(e, s.length) : r == "string" && e in s) ? Ys(s[e], t) : !1; +} +function L0(t) { + return _0(function(e, s) { + var r = -1, i = s.length, a = i > 1 ? s[i - 1] : void 0, n = i > 2 ? s[2] : void 0; + for (a = t.length > 3 && typeof a == "function" ? (i--, a) : void 0, n && I0(s[0], s[1], n) && (a = i < 3 ? void 0 : a, i = 1), e = Object(e); ++r < i; ) { + var o = s[r]; + o && t(e, o, r, a); + } + return e; + }); +} +var q0 = Object.prototype; +function Fi(t) { + var e = t && t.constructor, s = typeof e == "function" && e.prototype || q0; + return t === s; +} +function O0(t, e) { + for (var s = -1, r = Array(t); ++s < t; ) + r[s] = e(s); + return r; +} +var P0 = "[object Arguments]"; +function ca(t) { + return ot(t) && ps(t) == P0; +} +var yo = Object.prototype, D0 = yo.hasOwnProperty, R0 = yo.propertyIsEnumerable, di = ca(/* @__PURE__ */ function() { + return arguments; +}()) ? ca : function(t) { + return ot(t) && D0.call(t, "callee") && !R0.call(t, "callee"); +}; +function B0() { + return !1; +} +var $o = typeof exports == "object" && exports && !exports.nodeType && exports, fa = $o && typeof module == "object" && module && !module.nodeType && module, M0 = fa && fa.exports === $o, pa = M0 ? et.Buffer : void 0, F0 = pa ? pa.isBuffer : void 0, Vs = F0 || B0, U0 = "[object Arguments]", j0 = "[object Array]", V0 = "[object Boolean]", H0 = "[object Date]", z0 = "[object Error]", G0 = "[object Function]", K0 = "[object Map]", W0 = "[object Number]", Z0 = "[object Object]", X0 = "[object RegExp]", Y0 = "[object Set]", Q0 = "[object String]", J0 = "[object WeakMap]", x0 = "[object ArrayBuffer]", ey = "[object DataView]", ty = "[object Float32Array]", sy = "[object Float64Array]", ny = "[object Int8Array]", ry = "[object Int16Array]", iy = "[object Int32Array]", ay = "[object Uint8Array]", oy = "[object Uint8ClampedArray]", ly = "[object Uint16Array]", uy = "[object Uint32Array]", se = {}; +se[ty] = se[sy] = se[ny] = se[ry] = se[iy] = se[ay] = se[oy] = se[ly] = se[uy] = !0; +se[U0] = se[j0] = se[x0] = se[V0] = se[ey] = se[H0] = se[z0] = se[G0] = se[K0] = se[W0] = se[Z0] = se[X0] = se[Y0] = se[Q0] = se[J0] = !1; +function dy(t) { + return ot(t) && bo(t.length) && !!se[ps(t)]; +} +function Ui(t) { + return function(e) { + return t(e); + }; +} +var ko = typeof exports == "object" && exports && !exports.nodeType && exports, Ms = ko && typeof module == "object" && module && !module.nodeType && module, cy = Ms && Ms.exports === ko, Yr = cy && po.process, os = function() { + try { + var t = Ms && Ms.require && Ms.require("util").types; + return t || Yr && Yr.binding && Yr.binding("util"); + } catch { + } +}(), ha = os && os.isTypedArray, ji = ha ? Ui(ha) : dy, fy = Object.prototype, py = fy.hasOwnProperty; +function wo(t, e) { + var s = qt(t), r = !s && di(t), i = !s && !r && Vs(t), a = !s && !r && !i && ji(t), n = s || r || i || a, o = n ? O0(t.length, String) : [], u = o.length; + for (var p in t) + (e || py.call(t, p)) && !(n && // Safari 9 has enumerable `arguments.length` in strict mode. + (p == "length" || // Node.js 0.10 has enumerable non-index properties on buffers. + i && (p == "offset" || p == "parent") || // PhantomJS 2 has enumerable non-index properties on typed arrays. + a && (p == "buffer" || p == "byteLength" || p == "byteOffset") || // Skip index properties. + mo(p, u))) && o.push(p); + return o; +} +function Co(t, e) { + return function(s) { + return t(e(s)); + }; +} +var hy = Co(Object.keys, Object), gy = Object.prototype, my = gy.hasOwnProperty; +function vy(t) { + if (!Fi(t)) + return hy(t); + var e = []; + for (var s in Object(t)) + my.call(t, s) && s != "constructor" && e.push(s); + return e; +} +function by(t) { + return zn(t) ? wo(t) : vy(t); +} +function yy(t) { + var e = []; + if (t != null) + for (var s in Object(t)) + e.push(s); + return e; +} +var $y = Object.prototype, ky = $y.hasOwnProperty; +function wy(t) { + if (!kt(t)) + return yy(t); + var e = Fi(t), s = []; + for (var r in t) + r == "constructor" && (e || !ky.call(t, r)) || s.push(r); + return s; +} +function Ao(t) { + return zn(t) ? wo(t, !0) : wy(t); +} +var Hs = Rt(Object, "create"); +function Cy() { + this.__data__ = Hs ? Hs(null) : {}, this.size = 0; +} +function Ay(t) { + var e = this.has(t) && delete this.__data__[t]; + return this.size -= e ? 1 : 0, e; +} +var Ey = "__lodash_hash_undefined__", Sy = Object.prototype, Ty = Sy.hasOwnProperty; +function _y(t) { + var e = this.__data__; + if (Hs) { + var s = e[t]; + return s === Ey ? void 0 : s; + } + return Ty.call(e, t) ? e[t] : void 0; +} +var Ny = Object.prototype, Iy = Ny.hasOwnProperty; +function Ly(t) { + var e = this.__data__; + return Hs ? e[t] !== void 0 : Iy.call(e, t); +} +var qy = "__lodash_hash_undefined__"; +function Oy(t, e) { + var s = this.__data__; + return this.size += this.has(t) ? 0 : 1, s[t] = Hs && e === void 0 ? qy : e, this; +} +function Ot(t) { + var e = -1, s = t == null ? 0 : t.length; + for (this.clear(); ++e < s; ) { + var r = t[e]; + this.set(r[0], r[1]); + } +} +Ot.prototype.clear = Cy; +Ot.prototype.delete = Ay; +Ot.prototype.get = _y; +Ot.prototype.has = Ly; +Ot.prototype.set = Oy; +function Py() { + this.__data__ = [], this.size = 0; +} +function Gn(t, e) { + for (var s = t.length; s--; ) + if (Ys(t[s][0], e)) + return s; + return -1; +} +var Dy = Array.prototype, Ry = Dy.splice; +function By(t) { + var e = this.__data__, s = Gn(e, t); + if (s < 0) + return !1; + var r = e.length - 1; + return s == r ? e.pop() : Ry.call(e, s, 1), --this.size, !0; +} +function My(t) { + var e = this.__data__, s = Gn(e, t); + return s < 0 ? void 0 : e[s][1]; +} +function Fy(t) { + return Gn(this.__data__, t) > -1; +} +function Uy(t, e) { + var s = this.__data__, r = Gn(s, t); + return r < 0 ? (++this.size, s.push([t, e])) : s[r][1] = e, this; +} +function dt(t) { + var e = -1, s = t == null ? 0 : t.length; + for (this.clear(); ++e < s; ) { + var r = t[e]; + this.set(r[0], r[1]); + } +} +dt.prototype.clear = Py; +dt.prototype.delete = By; +dt.prototype.get = My; +dt.prototype.has = Fy; +dt.prototype.set = Uy; +var zs = Rt(et, "Map"); +function jy() { + this.size = 0, this.__data__ = { + hash: new Ot(), + map: new (zs || dt)(), + string: new Ot() + }; +} +function Vy(t) { + var e = typeof t; + return e == "string" || e == "number" || e == "symbol" || e == "boolean" ? t !== "__proto__" : t === null; +} +function Kn(t, e) { + var s = t.__data__; + return Vy(e) ? s[typeof e == "string" ? "string" : "hash"] : s.map; +} +function Hy(t) { + var e = Kn(this, t).delete(t); + return this.size -= e ? 1 : 0, e; +} +function zy(t) { + return Kn(this, t).get(t); +} +function Gy(t) { + return Kn(this, t).has(t); +} +function Ky(t, e) { + var s = Kn(this, t), r = s.size; + return s.set(t, e), this.size += s.size == r ? 0 : 1, this; +} +function Bt(t) { + var e = -1, s = t == null ? 0 : t.length; + for (this.clear(); ++e < s; ) { + var r = t[e]; + this.set(r[0], r[1]); + } +} +Bt.prototype.clear = jy; +Bt.prototype.delete = Hy; +Bt.prototype.get = zy; +Bt.prototype.has = Gy; +Bt.prototype.set = Ky; +function Wy(t, e) { + for (var s = -1, r = e.length, i = t.length; ++s < r; ) + t[i + s] = e[s]; + return t; +} +var Eo = Co(Object.getPrototypeOf, Object), Zy = "[object Object]", Xy = Function.prototype, Yy = Object.prototype, So = Xy.toString, Qy = Yy.hasOwnProperty, Jy = So.call(Object); +function xy(t) { + if (!ot(t) || ps(t) != Zy) + return !1; + var e = Eo(t); + if (e === null) + return !0; + var s = Qy.call(e, "constructor") && e.constructor; + return typeof s == "function" && s instanceof s && So.call(s) == Jy; +} +function e$() { + this.__data__ = new dt(), this.size = 0; +} +function t$(t) { + var e = this.__data__, s = e.delete(t); + return this.size = e.size, s; +} +function s$(t) { + return this.__data__.get(t); +} +function n$(t) { + return this.__data__.has(t); +} +var r$ = 200; +function i$(t, e) { + var s = this.__data__; + if (s instanceof dt) { + var r = s.__data__; + if (!zs || r.length < r$ - 1) + return r.push([t, e]), this.size = ++s.size, this; + s = this.__data__ = new Bt(r); + } + return s.set(t, e), this.size = s.size, this; +} +function Ye(t) { + var e = this.__data__ = new dt(t); + this.size = e.size; +} +Ye.prototype.clear = e$; +Ye.prototype.delete = t$; +Ye.prototype.get = s$; +Ye.prototype.has = n$; +Ye.prototype.set = i$; +var To = typeof exports == "object" && exports && !exports.nodeType && exports, ga = To && typeof module == "object" && module && !module.nodeType && module, a$ = ga && ga.exports === To, ma = a$ ? et.Buffer : void 0, va = ma ? ma.allocUnsafe : void 0; +function _o(t, e) { + if (e) + return t.slice(); + var s = t.length, r = va ? va(s) : new t.constructor(s); + return t.copy(r), r; +} +function o$(t, e) { + for (var s = -1, r = t == null ? 0 : t.length, i = 0, a = []; ++s < r; ) { + var n = t[s]; + e(n, s, t) && (a[i++] = n); + } + return a; +} +function l$() { + return []; +} +var u$ = Object.prototype, d$ = u$.propertyIsEnumerable, ba = Object.getOwnPropertySymbols, c$ = ba ? function(t) { + return t == null ? [] : (t = Object(t), o$(ba(t), function(e) { + return d$.call(t, e); + })); +} : l$; +function f$(t, e, s) { + var r = e(t); + return qt(t) ? r : Wy(r, s(t)); +} +function ci(t) { + return f$(t, by, c$); +} +var fi = Rt(et, "DataView"), pi = Rt(et, "Promise"), hi = Rt(et, "Set"), ya = "[object Map]", p$ = "[object Object]", $a = "[object Promise]", ka = "[object Set]", wa = "[object WeakMap]", Ca = "[object DataView]", h$ = Dt(fi), g$ = Dt(zs), m$ = Dt(pi), v$ = Dt(hi), b$ = Dt(ui), Be = ps; +(fi && Be(new fi(new ArrayBuffer(1))) != Ca || zs && Be(new zs()) != ya || pi && Be(pi.resolve()) != $a || hi && Be(new hi()) != ka || ui && Be(new ui()) != wa) && (Be = function(t) { + var e = ps(t), s = e == p$ ? t.constructor : void 0, r = s ? Dt(s) : ""; + if (r) + switch (r) { + case h$: + return Ca; + case g$: + return ya; + case m$: + return $a; + case v$: + return ka; + case b$: + return wa; + } + return e; +}); +var y$ = Object.prototype, $$ = y$.hasOwnProperty; +function k$(t) { + var e = t.length, s = new t.constructor(e); + return e && typeof t[0] == "string" && $$.call(t, "index") && (s.index = t.index, s.input = t.input), s; +} +var Fn = et.Uint8Array; +function Vi(t) { + var e = new t.constructor(t.byteLength); + return new Fn(e).set(new Fn(t)), e; +} +function w$(t, e) { + var s = Vi(t.buffer); + return new t.constructor(s, t.byteOffset, t.byteLength); +} +var C$ = /\w*$/; +function A$(t) { + var e = new t.constructor(t.source, C$.exec(t)); + return e.lastIndex = t.lastIndex, e; +} +var Aa = $t ? $t.prototype : void 0, Ea = Aa ? Aa.valueOf : void 0; +function E$(t) { + return Ea ? Object(Ea.call(t)) : {}; +} +function No(t, e) { + var s = e ? Vi(t.buffer) : t.buffer; + return new t.constructor(s, t.byteOffset, t.length); +} +var S$ = "[object Boolean]", T$ = "[object Date]", _$ = "[object Map]", N$ = "[object Number]", I$ = "[object RegExp]", L$ = "[object Set]", q$ = "[object String]", O$ = "[object Symbol]", P$ = "[object ArrayBuffer]", D$ = "[object DataView]", R$ = "[object Float32Array]", B$ = "[object Float64Array]", M$ = "[object Int8Array]", F$ = "[object Int16Array]", U$ = "[object Int32Array]", j$ = "[object Uint8Array]", V$ = "[object Uint8ClampedArray]", H$ = "[object Uint16Array]", z$ = "[object Uint32Array]"; +function G$(t, e, s) { + var r = t.constructor; + switch (e) { + case P$: + return Vi(t); + case S$: + case T$: + return new r(+t); + case D$: + return w$(t); + case R$: + case B$: + case M$: + case F$: + case U$: + case j$: + case V$: + case H$: + case z$: + return No(t, s); + case _$: + return new r(); + case N$: + case q$: + return new r(t); + case I$: + return A$(t); + case L$: + return new r(); + case O$: + return E$(t); + } +} +function Io(t) { + return typeof t.constructor == "function" && !Fi(t) ? c0(Eo(t)) : {}; +} +var K$ = "[object Map]"; +function W$(t) { + return ot(t) && Be(t) == K$; +} +var Sa = os && os.isMap, Z$ = Sa ? Ui(Sa) : W$, X$ = "[object Set]"; +function Y$(t) { + return ot(t) && Be(t) == X$; +} +var Ta = os && os.isSet, Q$ = Ta ? Ui(Ta) : Y$, J$ = 1, Lo = "[object Arguments]", x$ = "[object Array]", e2 = "[object Boolean]", t2 = "[object Date]", s2 = "[object Error]", qo = "[object Function]", n2 = "[object GeneratorFunction]", r2 = "[object Map]", i2 = "[object Number]", Oo = "[object Object]", a2 = "[object RegExp]", o2 = "[object Set]", l2 = "[object String]", u2 = "[object Symbol]", d2 = "[object WeakMap]", c2 = "[object ArrayBuffer]", f2 = "[object DataView]", p2 = "[object Float32Array]", h2 = "[object Float64Array]", g2 = "[object Int8Array]", m2 = "[object Int16Array]", v2 = "[object Int32Array]", b2 = "[object Uint8Array]", y2 = "[object Uint8ClampedArray]", $2 = "[object Uint16Array]", k2 = "[object Uint32Array]", ee = {}; +ee[Lo] = ee[x$] = ee[c2] = ee[f2] = ee[e2] = ee[t2] = ee[p2] = ee[h2] = ee[g2] = ee[m2] = ee[v2] = ee[r2] = ee[i2] = ee[Oo] = ee[a2] = ee[o2] = ee[l2] = ee[u2] = ee[b2] = ee[y2] = ee[$2] = ee[k2] = !0; +ee[s2] = ee[qo] = ee[d2] = !1; +function Rn(t, e, s, r, i, a) { + var n, o = e & J$; + if (n !== void 0) + return n; + if (!kt(t)) + return t; + var u = qt(t); + if (u) + n = k$(t); + else { + var p = Be(t), g = p == qo || p == n2; + if (Vs(t)) + return _o(t, o); + if (p == Oo || p == Lo || g && !i) + n = g ? {} : Io(t); + else { + if (!ee[p]) + return i ? t : {}; + n = G$(t, p, o); + } + } + a || (a = new Ye()); + var k = a.get(t); + if (k) + return k; + a.set(t, n), Q$(t) ? t.forEach(function(v) { + n.add(Rn(v, e, s, v, t, a)); + }) : Z$(t) && t.forEach(function(v, E) { + n.set(E, Rn(v, e, s, E, t, a)); + }); + var y = ci, b = u ? void 0 : y(t); + return k0(b || t, function(v, E) { + b && (E = v, v = t[E]), vo(n, E, Rn(v, e, s, E, t, a)); + }), n; +} +var w2 = 1, C2 = 4; +function rs(t) { + return Rn(t, w2 | C2); +} +var A2 = "__lodash_hash_undefined__"; +function E2(t) { + return this.__data__.set(t, A2), this; +} +function S2(t) { + return this.__data__.has(t); +} +function Un(t) { + var e = -1, s = t == null ? 0 : t.length; + for (this.__data__ = new Bt(); ++e < s; ) + this.add(t[e]); +} +Un.prototype.add = Un.prototype.push = E2; +Un.prototype.has = S2; +function T2(t, e) { + for (var s = -1, r = t == null ? 0 : t.length; ++s < r; ) + if (e(t[s], s, t)) + return !0; + return !1; +} +function _2(t, e) { + return t.has(e); +} +var N2 = 1, I2 = 2; +function Po(t, e, s, r, i, a) { + var n = s & N2, o = t.length, u = e.length; + if (o != u && !(n && u > o)) + return !1; + var p = a.get(t), g = a.get(e); + if (p && g) + return p == e && g == t; + var k = -1, y = !0, b = s & I2 ? new Un() : void 0; + for (a.set(t, e), a.set(e, t); ++k < o; ) { + var v = t[k], E = e[k]; + if (r) + var S = n ? r(E, v, k, e, t, a) : r(v, E, k, t, e, a); + if (S !== void 0) { + if (S) + continue; + y = !1; + break; + } + if (b) { + if (!T2(e, function(_, O) { + if (!_2(b, O) && (v === _ || i(v, _, s, r, a))) + return b.push(O); + })) { + y = !1; + break; + } + } else if (!(v === E || i(v, E, s, r, a))) { + y = !1; + break; + } + } + return a.delete(t), a.delete(e), y; +} +function L2(t) { + var e = -1, s = Array(t.size); + return t.forEach(function(r, i) { + s[++e] = [i, r]; + }), s; +} +function q2(t) { + var e = -1, s = Array(t.size); + return t.forEach(function(r) { + s[++e] = r; + }), s; +} +var O2 = 1, P2 = 2, D2 = "[object Boolean]", R2 = "[object Date]", B2 = "[object Error]", M2 = "[object Map]", F2 = "[object Number]", U2 = "[object RegExp]", j2 = "[object Set]", V2 = "[object String]", H2 = "[object Symbol]", z2 = "[object ArrayBuffer]", G2 = "[object DataView]", _a = $t ? $t.prototype : void 0, Qr = _a ? _a.valueOf : void 0; +function K2(t, e, s, r, i, a, n) { + switch (s) { + case G2: + if (t.byteLength != e.byteLength || t.byteOffset != e.byteOffset) + return !1; + t = t.buffer, e = e.buffer; + case z2: + return !(t.byteLength != e.byteLength || !a(new Fn(t), new Fn(e))); + case D2: + case R2: + case F2: + return Ys(+t, +e); + case B2: + return t.name == e.name && t.message == e.message; + case U2: + case V2: + return t == e + ""; + case M2: + var o = L2; + case j2: + var u = r & O2; + if (o || (o = q2), t.size != e.size && !u) + return !1; + var p = n.get(t); + if (p) + return p == e; + r |= P2, n.set(t, e); + var g = Po(o(t), o(e), r, i, a, n); + return n.delete(t), g; + case H2: + if (Qr) + return Qr.call(t) == Qr.call(e); + } + return !1; +} +var W2 = 1, Z2 = Object.prototype, X2 = Z2.hasOwnProperty; +function Y2(t, e, s, r, i, a) { + var n = s & W2, o = ci(t), u = o.length, p = ci(e), g = p.length; + if (u != g && !n) + return !1; + for (var k = u; k--; ) { + var y = o[k]; + if (!(n ? y in e : X2.call(e, y))) + return !1; + } + var b = a.get(t), v = a.get(e); + if (b && v) + return b == e && v == t; + var E = !0; + a.set(t, e), a.set(e, t); + for (var S = n; ++k < u; ) { + y = o[k]; + var _ = t[y], O = e[y]; + if (r) + var D = n ? r(O, _, y, e, t, a) : r(_, O, y, t, e, a); + if (!(D === void 0 ? _ === O || i(_, O, s, r, a) : D)) { + E = !1; + break; + } + S || (S = y == "constructor"); + } + if (E && !S) { + var U = t.constructor, M = e.constructor; + U != M && "constructor" in t && "constructor" in e && !(typeof U == "function" && U instanceof U && typeof M == "function" && M instanceof M) && (E = !1); + } + return a.delete(t), a.delete(e), E; +} +var Q2 = 1, Na = "[object Arguments]", Ia = "[object Array]", In = "[object Object]", J2 = Object.prototype, La = J2.hasOwnProperty; +function x2(t, e, s, r, i, a) { + var n = qt(t), o = qt(e), u = n ? Ia : Be(t), p = o ? Ia : Be(e); + u = u == Na ? In : u, p = p == Na ? In : p; + var g = u == In, k = p == In, y = u == p; + if (y && Vs(t)) { + if (!Vs(e)) + return !1; + n = !0, g = !1; + } + if (y && !g) + return a || (a = new Ye()), n || ji(t) ? Po(t, e, s, r, i, a) : K2(t, e, u, s, r, i, a); + if (!(s & Q2)) { + var b = g && La.call(t, "__wrapped__"), v = k && La.call(e, "__wrapped__"); + if (b || v) { + var E = b ? t.value() : t, S = v ? e.value() : e; + return a || (a = new Ye()), i(E, S, s, r, a); + } + } + return y ? (a || (a = new Ye()), Y2(t, e, s, r, i, a)) : !1; +} +function Do(t, e, s, r, i) { + return t === e ? !0 : t == null || e == null || !ot(t) && !ot(e) ? t !== t && e !== e : x2(t, e, s, r, Do, i); +} +function ek(t) { + return function(e, s, r) { + for (var i = -1, a = Object(e), n = r(e), o = n.length; o--; ) { + var u = n[++i]; + if (s(a[u], u, a) === !1) + break; + } + return e; + }; +} +var tk = ek(); +function gi(t, e, s) { + (s !== void 0 && !Ys(t[e], s) || s === void 0 && !(e in t)) && Mi(t, e, s); +} +function sk(t) { + return ot(t) && zn(t); +} +function mi(t, e) { + if (!(e === "constructor" && typeof t[e] == "function") && e != "__proto__") + return t[e]; +} +function nk(t) { + return S0(t, Ao(t)); +} +function rk(t, e, s, r, i, a, n) { + var o = mi(t, s), u = mi(e, s), p = n.get(u); + if (p) { + gi(t, s, p); + return; + } + var g = a ? a(o, u, s + "", t, e, n) : void 0, k = g === void 0; + if (k) { + var y = qt(u), b = !y && Vs(u), v = !y && !b && ji(u); + g = u, y || b || v ? qt(o) ? g = o : sk(o) ? g = p0(o) : b ? (k = !1, g = _o(u, !0)) : v ? (k = !1, g = No(u, !0)) : g = [] : xy(u) || di(u) ? (g = o, di(o) ? g = nk(o) : (!kt(o) || Bi(o)) && (g = Io(u))) : k = !1; + } + k && (n.set(u, g), i(g, u, r, a, n), n.delete(u)), gi(t, s, g); +} +function Ro(t, e, s, r, i) { + t !== e && tk(e, function(a, n) { + if (i || (i = new Ye()), kt(a)) + rk(t, e, n, s, Ro, r, i); + else { + var o = r ? r(mi(t, n), a, n + "", t, e, i) : void 0; + o === void 0 && (o = a), gi(t, n, o); + } + }, Ao); +} +function Hi(t, e) { + return Do(t, e); +} +var yt = L0(function(t, e, s) { + Ro(t, e, s); +}), V = /* @__PURE__ */ ((t) => (t[t.TYPE = 3] = "TYPE", t[t.LEVEL = 12] = "LEVEL", t[t.ATTRIBUTE = 13] = "ATTRIBUTE", t[t.BLOT = 14] = "BLOT", t[t.INLINE = 7] = "INLINE", t[t.BLOCK = 11] = "BLOCK", t[t.BLOCK_BLOT = 10] = "BLOCK_BLOT", t[t.INLINE_BLOT = 6] = "INLINE_BLOT", t[t.BLOCK_ATTRIBUTE = 9] = "BLOCK_ATTRIBUTE", t[t.INLINE_ATTRIBUTE = 5] = "INLINE_ATTRIBUTE", t[t.ANY = 15] = "ANY", t))(V || {}); +class Je { + constructor(e, s, r = {}) { + this.attrName = e, this.keyName = s; + const i = V.TYPE & V.ATTRIBUTE; + this.scope = r.scope != null ? ( + // Ignore type bits, force attribute bit + r.scope & V.LEVEL | i + ) : V.ATTRIBUTE, r.whitelist != null && (this.whitelist = r.whitelist); + } + static keys(e) { + return Array.from(e.attributes).map((s) => s.name); + } + add(e, s) { + return this.canAdd(e, s) ? (e.setAttribute(this.keyName, s), !0) : !1; + } + canAdd(e, s) { + return this.whitelist == null ? !0 : typeof s == "string" ? this.whitelist.indexOf(s.replace(/["']/g, "")) > -1 : this.whitelist.indexOf(s) > -1; + } + remove(e) { + e.removeAttribute(this.keyName); + } + value(e) { + const s = e.getAttribute(this.keyName); + return this.canAdd(e, s) && s ? s : ""; + } +} +class is extends Error { + constructor(e) { + e = "[Parchment] " + e, super(e), this.message = e, this.name = this.constructor.name; + } +} +const Bo = class vi { + constructor() { + this.attributes = {}, this.classes = {}, this.tags = {}, this.types = {}; + } + static find(e, s = !1) { + if (e == null) + return null; + if (this.blots.has(e)) + return this.blots.get(e) || null; + if (s) { + let r = null; + try { + r = e.parentNode; + } catch { + return null; + } + return this.find(r, s); + } + return null; + } + create(e, s, r) { + const i = this.query(s); + if (i == null) + throw new is(`Unable to create ${s} blot`); + const a = i, n = ( + // @ts-expect-error Fix me later + s instanceof Node || s.nodeType === Node.TEXT_NODE ? s : a.create(r) + ), o = new a(e, n, r); + return vi.blots.set(o.domNode, o), o; + } + find(e, s = !1) { + return vi.find(e, s); + } + query(e, s = V.ANY) { + let r; + return typeof e == "string" ? r = this.types[e] || this.attributes[e] : e instanceof Text || e.nodeType === Node.TEXT_NODE ? r = this.types.text : typeof e == "number" ? e & V.LEVEL & V.BLOCK ? r = this.types.block : e & V.LEVEL & V.INLINE && (r = this.types.inline) : e instanceof Element && ((e.getAttribute("class") || "").split(/\s+/).some((i) => (r = this.classes[i], !!r)), r = r || this.tags[e.tagName]), r == null ? null : "scope" in r && s & V.LEVEL & r.scope && s & V.TYPE & r.scope ? r : null; + } + register(...e) { + return e.map((s) => { + const r = "blotName" in s, i = "attrName" in s; + if (!r && !i) + throw new is("Invalid definition"); + if (r && s.blotName === "abstract") + throw new is("Cannot register abstract class"); + const a = r ? s.blotName : i ? s.attrName : void 0; + return this.types[a] = s, i ? typeof s.keyName == "string" && (this.attributes[s.keyName] = s) : r && (s.className && (this.classes[s.className] = s), s.tagName && (Array.isArray(s.tagName) ? s.tagName = s.tagName.map((n) => n.toUpperCase()) : s.tagName = s.tagName.toUpperCase(), (Array.isArray(s.tagName) ? s.tagName : [s.tagName]).forEach((n) => { + (this.tags[n] == null || s.className == null) && (this.tags[n] = s); + }))), s; + }); + } +}; +Bo.blots = /* @__PURE__ */ new WeakMap(); +let ls = Bo; +function qa(t, e) { + return (t.getAttribute("class") || "").split(/\s+/).filter((s) => s.indexOf(`${e}-`) === 0); +} +class ik extends Je { + static keys(e) { + return (e.getAttribute("class") || "").split(/\s+/).map((s) => s.split("-").slice(0, -1).join("-")); + } + add(e, s) { + return this.canAdd(e, s) ? (this.remove(e), e.classList.add(`${this.keyName}-${s}`), !0) : !1; + } + remove(e) { + qa(e, this.keyName).forEach((s) => { + e.classList.remove(s); + }), e.classList.length === 0 && e.removeAttribute("class"); + } + value(e) { + const s = (qa(e, this.keyName)[0] || "").slice(this.keyName.length + 1); + return this.canAdd(e, s) ? s : ""; + } +} +const Ve = ik; +function Jr(t) { + const e = t.split("-"), s = e.slice(1).map((r) => r[0].toUpperCase() + r.slice(1)).join(""); + return e[0] + s; +} +class ak extends Je { + static keys(e) { + return (e.getAttribute("style") || "").split(";").map((s) => s.split(":")[0].trim()); + } + add(e, s) { + return this.canAdd(e, s) ? (e.style[Jr(this.keyName)] = s, !0) : !1; + } + remove(e) { + e.style[Jr(this.keyName)] = "", e.getAttribute("style") || e.removeAttribute("style"); + } + value(e) { + const s = e.style[Jr(this.keyName)]; + return this.canAdd(e, s) ? s : ""; + } +} +const wt = ak; +class ok { + constructor(e) { + this.attributes = {}, this.domNode = e, this.build(); + } + attribute(e, s) { + s ? e.add(this.domNode, s) && (e.value(this.domNode) != null ? this.attributes[e.attrName] = e : delete this.attributes[e.attrName]) : (e.remove(this.domNode), delete this.attributes[e.attrName]); + } + build() { + this.attributes = {}; + const e = ls.find(this.domNode); + if (e == null) + return; + const s = Je.keys(this.domNode), r = Ve.keys(this.domNode), i = wt.keys(this.domNode); + s.concat(r).concat(i).forEach((a) => { + const n = e.scroll.query(a, V.ATTRIBUTE); + n instanceof Je && (this.attributes[n.attrName] = n); + }); + } + copy(e) { + Object.keys(this.attributes).forEach((s) => { + const r = this.attributes[s].value(this.domNode); + e.format(s, r); + }); + } + move(e) { + this.copy(e), Object.keys(this.attributes).forEach((s) => { + this.attributes[s].remove(this.domNode); + }), this.attributes = {}; + } + values() { + return Object.keys(this.attributes).reduce( + (e, s) => (e[s] = this.attributes[s].value(this.domNode), e), + {} + ); + } +} +const Wn = ok, Mo = class { + constructor(e, s) { + this.scroll = e, this.domNode = s, ls.blots.set(s, this), this.prev = null, this.next = null; + } + static create(e) { + if (this.tagName == null) + throw new is("Blot definition missing tagName"); + let s, r; + return Array.isArray(this.tagName) ? (typeof e == "string" ? (r = e.toUpperCase(), parseInt(r, 10).toString() === r && (r = parseInt(r, 10))) : typeof e == "number" && (r = e), typeof r == "number" ? s = document.createElement(this.tagName[r - 1]) : r && this.tagName.indexOf(r) > -1 ? s = document.createElement(r) : s = document.createElement(this.tagName[0])) : s = document.createElement(this.tagName), this.className && s.classList.add(this.className), s; + } + // Hack for accessing inherited static methods + get statics() { + return this.constructor; + } + attach() { + } + clone() { + const e = this.domNode.cloneNode(!1); + return this.scroll.create(e); + } + detach() { + this.parent != null && this.parent.removeChild(this), ls.blots.delete(this.domNode); + } + deleteAt(e, s) { + this.isolate(e, s).remove(); + } + formatAt(e, s, r, i) { + const a = this.isolate(e, s); + if (this.scroll.query(r, V.BLOT) != null && i) + a.wrap(r, i); + else if (this.scroll.query(r, V.ATTRIBUTE) != null) { + const n = this.scroll.create(this.statics.scope); + a.wrap(n), n.format(r, i); + } + } + insertAt(e, s, r) { + const i = r == null ? this.scroll.create("text", s) : this.scroll.create(s, r), a = this.split(e); + this.parent.insertBefore(i, a || void 0); + } + isolate(e, s) { + const r = this.split(e); + if (r == null) + throw new Error("Attempt to isolate at end"); + return r.split(s), r; + } + length() { + return 1; + } + offset(e = this.parent) { + return this.parent == null || this === e ? 0 : this.parent.children.offset(this) + this.parent.offset(e); + } + optimize(e) { + this.statics.requiredContainer && !(this.parent instanceof this.statics.requiredContainer) && this.wrap(this.statics.requiredContainer.blotName); + } + remove() { + this.domNode.parentNode != null && this.domNode.parentNode.removeChild(this.domNode), this.detach(); + } + replaceWith(e, s) { + const r = typeof e == "string" ? this.scroll.create(e, s) : e; + return this.parent != null && (this.parent.insertBefore(r, this.next || void 0), this.remove()), r; + } + split(e, s) { + return e === 0 ? this : this.next; + } + update(e, s) { + } + wrap(e, s) { + const r = typeof e == "string" ? this.scroll.create(e, s) : e; + if (this.parent != null && this.parent.insertBefore(r, this.next || void 0), typeof r.appendChild != "function") + throw new is(`Cannot wrap ${e}`); + return r.appendChild(this), r; + } +}; +Mo.blotName = "abstract"; +let Fo = Mo; +const Uo = class extends Fo { + /** + * Returns the value represented by domNode if it is this Blot's type + * No checking that domNode can represent this Blot type is required so + * applications needing it should check externally before calling. + */ + static value(e) { + return !0; + } + /** + * Given location represented by node and offset from DOM Selection Range, + * return index to that location. + */ + index(e, s) { + return this.domNode === e || this.domNode.compareDocumentPosition(e) & Node.DOCUMENT_POSITION_CONTAINED_BY ? Math.min(s, 1) : -1; + } + /** + * Given index to location within blot, return node and offset representing + * that location, consumable by DOM Selection Range + */ + position(e, s) { + let r = Array.from(this.parent.domNode.childNodes).indexOf(this.domNode); + return e > 0 && (r += 1), [this.parent.domNode, r]; + } + /** + * Return value represented by this blot + * Should not change without interaction from API or + * user change detectable by update() + */ + value() { + return { + [this.statics.blotName]: this.statics.value(this.domNode) || !0 + }; + } +}; +Uo.scope = V.INLINE_BLOT; +let lk = Uo; +const ge = lk; +class uk { + constructor() { + this.head = null, this.tail = null, this.length = 0; + } + append(...e) { + if (this.insertBefore(e[0], null), e.length > 1) { + const s = e.slice(1); + this.append(...s); + } + } + at(e) { + const s = this.iterator(); + let r = s(); + for (; r && e > 0; ) + e -= 1, r = s(); + return r; + } + contains(e) { + const s = this.iterator(); + let r = s(); + for (; r; ) { + if (r === e) + return !0; + r = s(); + } + return !1; + } + indexOf(e) { + const s = this.iterator(); + let r = s(), i = 0; + for (; r; ) { + if (r === e) + return i; + i += 1, r = s(); + } + return -1; + } + insertBefore(e, s) { + e != null && (this.remove(e), e.next = s, s != null ? (e.prev = s.prev, s.prev != null && (s.prev.next = e), s.prev = e, s === this.head && (this.head = e)) : this.tail != null ? (this.tail.next = e, e.prev = this.tail, this.tail = e) : (e.prev = null, this.head = this.tail = e), this.length += 1); + } + offset(e) { + let s = 0, r = this.head; + for (; r != null; ) { + if (r === e) + return s; + s += r.length(), r = r.next; + } + return -1; + } + remove(e) { + this.contains(e) && (e.prev != null && (e.prev.next = e.next), e.next != null && (e.next.prev = e.prev), e === this.head && (this.head = e.next), e === this.tail && (this.tail = e.prev), this.length -= 1); + } + iterator(e = this.head) { + return () => { + const s = e; + return e != null && (e = e.next), s; + }; + } + find(e, s = !1) { + const r = this.iterator(); + let i = r(); + for (; i; ) { + const a = i.length(); + if (e < a || s && e === a && (i.next == null || i.next.length() !== 0)) + return [i, e]; + e -= a, i = r(); + } + return [null, 0]; + } + forEach(e) { + const s = this.iterator(); + let r = s(); + for (; r; ) + e(r), r = s(); + } + forEachAt(e, s, r) { + if (s <= 0) + return; + const [i, a] = this.find(e); + let n = e - a; + const o = this.iterator(i); + let u = o(); + for (; u && n < e + s; ) { + const p = u.length(); + e > n ? r( + u, + e - n, + Math.min(s, n + p - e) + ) : r(u, 0, Math.min(p, e + s - n)), n += p, u = o(); + } + } + map(e) { + return this.reduce((s, r) => (s.push(e(r)), s), []); + } + reduce(e, s) { + const r = this.iterator(); + let i = r(); + for (; i; ) + s = e(s, i), i = r(); + return s; + } +} +function Oa(t, e) { + const s = e.find(t); + if (s) + return s; + try { + return e.create(t); + } catch { + const r = e.create(V.INLINE); + return Array.from(t.childNodes).forEach((i) => { + r.domNode.appendChild(i); + }), t.parentNode && t.parentNode.replaceChild(r.domNode, t), r.attach(), r; + } +} +const jo = class gt extends Fo { + constructor(e, s) { + super(e, s), this.uiNode = null, this.build(); + } + appendChild(e) { + this.insertBefore(e); + } + attach() { + super.attach(), this.children.forEach((e) => { + e.attach(); + }); + } + attachUI(e) { + this.uiNode != null && this.uiNode.remove(), this.uiNode = e, gt.uiClass && this.uiNode.classList.add(gt.uiClass), this.uiNode.setAttribute("contenteditable", "false"), this.domNode.insertBefore(this.uiNode, this.domNode.firstChild); + } + /** + * Called during construction, should fill its own children LinkedList. + */ + build() { + this.children = new uk(), Array.from(this.domNode.childNodes).filter((e) => e !== this.uiNode).reverse().forEach((e) => { + try { + const s = Oa(e, this.scroll); + this.insertBefore(s, this.children.head || void 0); + } catch (s) { + if (s instanceof is) + return; + throw s; + } + }); + } + deleteAt(e, s) { + if (e === 0 && s === this.length()) + return this.remove(); + this.children.forEachAt(e, s, (r, i, a) => { + r.deleteAt(i, a); + }); + } + descendant(e, s = 0) { + const [r, i] = this.children.find(s); + return e.blotName == null && e(r) || e.blotName != null && r instanceof e ? [r, i] : r instanceof gt ? r.descendant(e, i) : [null, -1]; + } + descendants(e, s = 0, r = Number.MAX_VALUE) { + let i = [], a = r; + return this.children.forEachAt( + s, + r, + (n, o, u) => { + (e.blotName == null && e(n) || e.blotName != null && n instanceof e) && i.push(n), n instanceof gt && (i = i.concat( + n.descendants(e, o, a) + )), a -= u; + } + ), i; + } + detach() { + this.children.forEach((e) => { + e.detach(); + }), super.detach(); + } + enforceAllowedChildren() { + let e = !1; + this.children.forEach((s) => { + e || this.statics.allowedChildren.some( + (r) => s instanceof r + ) || (s.statics.scope === V.BLOCK_BLOT ? (s.next != null && this.splitAfter(s), s.prev != null && this.splitAfter(s.prev), s.parent.unwrap(), e = !0) : s instanceof gt ? s.unwrap() : s.remove()); + }); + } + formatAt(e, s, r, i) { + this.children.forEachAt(e, s, (a, n, o) => { + a.formatAt(n, o, r, i); + }); + } + insertAt(e, s, r) { + const [i, a] = this.children.find(e); + if (i) + i.insertAt(a, s, r); + else { + const n = r == null ? this.scroll.create("text", s) : this.scroll.create(s, r); + this.appendChild(n); + } + } + insertBefore(e, s) { + e.parent != null && e.parent.children.remove(e); + let r = null; + this.children.insertBefore(e, s || null), e.parent = this, s != null && (r = s.domNode), (this.domNode.parentNode !== e.domNode || this.domNode.nextSibling !== r) && this.domNode.insertBefore(e.domNode, r), e.attach(); + } + length() { + return this.children.reduce((e, s) => e + s.length(), 0); + } + moveChildren(e, s) { + this.children.forEach((r) => { + e.insertBefore(r, s); + }); + } + optimize(e) { + if (super.optimize(e), this.enforceAllowedChildren(), this.uiNode != null && this.uiNode !== this.domNode.firstChild && this.domNode.insertBefore(this.uiNode, this.domNode.firstChild), this.children.length === 0) + if (this.statics.defaultChild != null) { + const s = this.scroll.create(this.statics.defaultChild.blotName); + this.appendChild(s); + } else + this.remove(); + } + path(e, s = !1) { + const [r, i] = this.children.find(e, s), a = [[this, e]]; + return r instanceof gt ? a.concat(r.path(i, s)) : (r != null && a.push([r, i]), a); + } + removeChild(e) { + this.children.remove(e); + } + replaceWith(e, s) { + const r = typeof e == "string" ? this.scroll.create(e, s) : e; + return r instanceof gt && this.moveChildren(r), super.replaceWith(r); + } + split(e, s = !1) { + if (!s) { + if (e === 0) + return this; + if (e === this.length()) + return this.next; + } + const r = this.clone(); + return this.parent && this.parent.insertBefore(r, this.next || void 0), this.children.forEachAt(e, this.length(), (i, a, n) => { + const o = i.split(a, s); + o != null && r.appendChild(o); + }), r; + } + splitAfter(e) { + const s = this.clone(); + for (; e.next != null; ) + s.appendChild(e.next); + return this.parent && this.parent.insertBefore(s, this.next || void 0), s; + } + unwrap() { + this.parent && this.moveChildren(this.parent, this.next || void 0), this.remove(); + } + update(e, s) { + const r = [], i = []; + e.forEach((a) => { + a.target === this.domNode && a.type === "childList" && (r.push(...a.addedNodes), i.push(...a.removedNodes)); + }), i.forEach((a) => { + if (a.parentNode != null && // @ts-expect-error Fix me later + a.tagName !== "IFRAME" && document.body.compareDocumentPosition(a) & Node.DOCUMENT_POSITION_CONTAINED_BY) + return; + const n = this.scroll.find(a); + n != null && (n.domNode.parentNode == null || n.domNode.parentNode === this.domNode) && n.detach(); + }), r.filter((a) => a.parentNode === this.domNode && a !== this.uiNode).sort((a, n) => a === n ? 0 : a.compareDocumentPosition(n) & Node.DOCUMENT_POSITION_FOLLOWING ? 1 : -1).forEach((a) => { + let n = null; + a.nextSibling != null && (n = this.scroll.find(a.nextSibling)); + const o = Oa(a, this.scroll); + (o.next !== n || o.next == null) && (o.parent != null && o.parent.removeChild(this), this.insertBefore(o, n || void 0)); + }), this.enforceAllowedChildren(); + } +}; +jo.uiClass = ""; +let dk = jo; +const Ue = dk; +function ck(t, e) { + if (Object.keys(t).length !== Object.keys(e).length) + return !1; + for (const s in t) + if (t[s] !== e[s]) + return !1; + return !0; +} +const Jt = class xt extends Ue { + static create(e) { + return super.create(e); + } + static formats(e, s) { + const r = s.query(xt.blotName); + if (!(r != null && e.tagName === r.tagName)) { + if (typeof this.tagName == "string") + return !0; + if (Array.isArray(this.tagName)) + return e.tagName.toLowerCase(); + } + } + constructor(e, s) { + super(e, s), this.attributes = new Wn(this.domNode); + } + format(e, s) { + if (e === this.statics.blotName && !s) + this.children.forEach((r) => { + r instanceof xt || (r = r.wrap(xt.blotName, !0)), this.attributes.copy(r); + }), this.unwrap(); + else { + const r = this.scroll.query(e, V.INLINE); + if (r == null) + return; + r instanceof Je ? this.attributes.attribute(r, s) : s && (e !== this.statics.blotName || this.formats()[e] !== s) && this.replaceWith(e, s); + } + } + formats() { + const e = this.attributes.values(), s = this.statics.formats(this.domNode, this.scroll); + return s != null && (e[this.statics.blotName] = s), e; + } + formatAt(e, s, r, i) { + this.formats()[r] != null || this.scroll.query(r, V.ATTRIBUTE) ? this.isolate(e, s).format(r, i) : super.formatAt(e, s, r, i); + } + optimize(e) { + super.optimize(e); + const s = this.formats(); + if (Object.keys(s).length === 0) + return this.unwrap(); + const r = this.next; + r instanceof xt && r.prev === this && ck(s, r.formats()) && (r.moveChildren(this), r.remove()); + } + replaceWith(e, s) { + const r = super.replaceWith(e, s); + return this.attributes.copy(r), r; + } + update(e, s) { + super.update(e, s), e.some( + (r) => r.target === this.domNode && r.type === "attributes" + ) && this.attributes.build(); + } + wrap(e, s) { + const r = super.wrap(e, s); + return r instanceof xt && this.attributes.move(r), r; + } +}; +Jt.allowedChildren = [Jt, ge], Jt.blotName = "inline", Jt.scope = V.INLINE_BLOT, Jt.tagName = "SPAN"; +let fk = Jt; +const zi = fk, es = class bi extends Ue { + static create(e) { + return super.create(e); + } + static formats(e, s) { + const r = s.query(bi.blotName); + if (!(r != null && e.tagName === r.tagName)) { + if (typeof this.tagName == "string") + return !0; + if (Array.isArray(this.tagName)) + return e.tagName.toLowerCase(); + } + } + constructor(e, s) { + super(e, s), this.attributes = new Wn(this.domNode); + } + format(e, s) { + const r = this.scroll.query(e, V.BLOCK); + r != null && (r instanceof Je ? this.attributes.attribute(r, s) : e === this.statics.blotName && !s ? this.replaceWith(bi.blotName) : s && (e !== this.statics.blotName || this.formats()[e] !== s) && this.replaceWith(e, s)); + } + formats() { + const e = this.attributes.values(), s = this.statics.formats(this.domNode, this.scroll); + return s != null && (e[this.statics.blotName] = s), e; + } + formatAt(e, s, r, i) { + this.scroll.query(r, V.BLOCK) != null ? this.format(r, i) : super.formatAt(e, s, r, i); + } + insertAt(e, s, r) { + if (r == null || this.scroll.query(s, V.INLINE) != null) + super.insertAt(e, s, r); + else { + const i = this.split(e); + if (i != null) { + const a = this.scroll.create(s, r); + i.parent.insertBefore(a, i); + } else + throw new Error("Attempt to insertAt after block boundaries"); + } + } + replaceWith(e, s) { + const r = super.replaceWith(e, s); + return this.attributes.copy(r), r; + } + update(e, s) { + super.update(e, s), e.some( + (r) => r.target === this.domNode && r.type === "attributes" + ) && this.attributes.build(); + } +}; +es.blotName = "block", es.scope = V.BLOCK_BLOT, es.tagName = "P", es.allowedChildren = [ + zi, + es, + ge +]; +let pk = es; +const Gs = pk, yi = class extends Ue { + checkMerge() { + return this.next !== null && this.next.statics.blotName === this.statics.blotName; + } + deleteAt(e, s) { + super.deleteAt(e, s), this.enforceAllowedChildren(); + } + formatAt(e, s, r, i) { + super.formatAt(e, s, r, i), this.enforceAllowedChildren(); + } + insertAt(e, s, r) { + super.insertAt(e, s, r), this.enforceAllowedChildren(); + } + optimize(e) { + super.optimize(e), this.children.length > 0 && this.next != null && this.checkMerge() && (this.next.moveChildren(this), this.next.remove()); + } +}; +yi.blotName = "container", yi.scope = V.BLOCK_BLOT; +let hk = yi; +const Zn = hk; +class gk extends ge { + static formats(e, s) { + } + format(e, s) { + super.formatAt(0, this.length(), e, s); + } + formatAt(e, s, r, i) { + e === 0 && s === this.length() ? this.format(r, i) : super.formatAt(e, s, r, i); + } + formats() { + return this.statics.formats(this.domNode, this.scroll); + } +} +const Ae = gk, mk = { + attributes: !0, + characterData: !0, + characterDataOldValue: !0, + childList: !0, + subtree: !0 +}, vk = 100, ts = class extends Ue { + constructor(e, s) { + super(null, s), this.registry = e, this.scroll = this, this.build(), this.observer = new MutationObserver((r) => { + this.update(r); + }), this.observer.observe(this.domNode, mk), this.attach(); + } + create(e, s) { + return this.registry.create(this, e, s); + } + find(e, s = !1) { + const r = this.registry.find(e, s); + return r ? r.scroll === this ? r : s ? this.find(r.scroll.domNode.parentNode, !0) : null : null; + } + query(e, s = V.ANY) { + return this.registry.query(e, s); + } + register(...e) { + return this.registry.register(...e); + } + build() { + this.scroll != null && super.build(); + } + detach() { + super.detach(), this.observer.disconnect(); + } + deleteAt(e, s) { + this.update(), e === 0 && s === this.length() ? this.children.forEach((r) => { + r.remove(); + }) : super.deleteAt(e, s); + } + formatAt(e, s, r, i) { + this.update(), super.formatAt(e, s, r, i); + } + insertAt(e, s, r) { + this.update(), super.insertAt(e, s, r); + } + optimize(e = [], s = {}) { + super.optimize(s); + const r = s.mutationsMap || /* @__PURE__ */ new WeakMap(); + let i = Array.from(this.observer.takeRecords()); + for (; i.length > 0; ) + e.push(i.pop()); + const a = (u, p = !0) => { + u == null || u === this || u.domNode.parentNode != null && (r.has(u.domNode) || r.set(u.domNode, []), p && a(u.parent)); + }, n = (u) => { + r.has(u.domNode) && (u instanceof Ue && u.children.forEach(n), r.delete(u.domNode), u.optimize(s)); + }; + let o = e; + for (let u = 0; o.length > 0; u += 1) { + if (u >= vk) + throw new Error("[Parchment] Maximum optimize iterations reached"); + for (o.forEach((p) => { + const g = this.find(p.target, !0); + g != null && (g.domNode === p.target && (p.type === "childList" ? (a(this.find(p.previousSibling, !1)), Array.from(p.addedNodes).forEach((k) => { + const y = this.find(k, !1); + a(y, !1), y instanceof Ue && y.children.forEach((b) => { + a(b, !1); + }); + })) : p.type === "attributes" && a(g.prev)), a(g)); + }), this.children.forEach(n), o = Array.from(this.observer.takeRecords()), i = o.slice(); i.length > 0; ) + e.push(i.pop()); + } + } + update(e, s = {}) { + e = e || this.observer.takeRecords(); + const r = /* @__PURE__ */ new WeakMap(); + e.map((i) => { + const a = this.find(i.target, !0); + return a == null ? null : r.has(a.domNode) ? (r.get(a.domNode).push(i), null) : (r.set(a.domNode, [i]), a); + }).forEach((i) => { + i != null && i !== this && r.has(i.domNode) && i.update(r.get(i.domNode) || [], s); + }), s.mutationsMap = r, r.has(this.domNode) && super.update(r.get(this.domNode), s), this.optimize(e, s); + } +}; +ts.blotName = "scroll", ts.defaultChild = Gs, ts.allowedChildren = [Gs, Zn], ts.scope = V.BLOCK_BLOT, ts.tagName = "DIV"; +let bk = ts; +const Gi = bk, $i = class Vo extends ge { + static create(e) { + return document.createTextNode(e); + } + static value(e) { + return e.data; + } + constructor(e, s) { + super(e, s), this.text = this.statics.value(this.domNode); + } + deleteAt(e, s) { + this.domNode.data = this.text = this.text.slice(0, e) + this.text.slice(e + s); + } + index(e, s) { + return this.domNode === e ? s : -1; + } + insertAt(e, s, r) { + r == null ? (this.text = this.text.slice(0, e) + s + this.text.slice(e), this.domNode.data = this.text) : super.insertAt(e, s, r); + } + length() { + return this.text.length; + } + optimize(e) { + super.optimize(e), this.text = this.statics.value(this.domNode), this.text.length === 0 ? this.remove() : this.next instanceof Vo && this.next.prev === this && (this.insertAt(this.length(), this.next.value()), this.next.remove()); + } + position(e, s = !1) { + return [this.domNode, e]; + } + split(e, s = !1) { + if (!s) { + if (e === 0) + return this; + if (e === this.length()) + return this.next; + } + const r = this.scroll.create(this.domNode.splitText(e)); + return this.parent.insertBefore(r, this.next || void 0), this.text = this.statics.value(this.domNode), r; + } + update(e, s) { + e.some((r) => r.type === "characterData" && r.target === this.domNode) && (this.text = this.statics.value(this.domNode)); + } + value() { + return this.text; + } +}; +$i.blotName = "text", $i.scope = V.INLINE_BLOT; +let yk = $i; +const jn = yk, $k = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ + __proto__: null, + Attributor: Je, + AttributorStore: Wn, + BlockBlot: Gs, + ClassAttributor: Ve, + ContainerBlot: Zn, + EmbedBlot: Ae, + InlineBlot: zi, + LeafBlot: ge, + ParentBlot: Ue, + Registry: ls, + Scope: V, + ScrollBlot: Gi, + StyleAttributor: wt, + TextBlot: jn +}, Symbol.toStringTag, { value: "Module" })); +var mt = typeof globalThis < "u" ? globalThis : typeof window < "u" ? window : typeof global < "u" ? global : typeof self < "u" ? self : {}; +function Ho(t) { + return t && t.__esModule && Object.prototype.hasOwnProperty.call(t, "default") ? t.default : t; +} +var ki = { exports: {} }, we = -1, be = 1, oe = 0; +function Ks(t, e, s, r, i) { + if (t === e) + return t ? [[oe, t]] : []; + if (s != null) { + var a = Nk(t, e, s); + if (a) + return a; + } + var n = Ki(t, e), o = t.substring(0, n); + t = t.substring(n), e = e.substring(n), n = Xn(t, e); + var u = t.substring(t.length - n); + t = t.substring(0, t.length - n), e = e.substring(0, e.length - n); + var p = kk(t, e); + return o && p.unshift([oe, o]), u && p.push([oe, u]), Wi(p, i), r && Ak(p), p; +} +function kk(t, e) { + var s; + if (!t) + return [[be, e]]; + if (!e) + return [[we, t]]; + var r = t.length > e.length ? t : e, i = t.length > e.length ? e : t, a = r.indexOf(i); + if (a !== -1) + return s = [ + [be, r.substring(0, a)], + [oe, i], + [be, r.substring(a + i.length)] + ], t.length > e.length && (s[0][0] = s[2][0] = we), s; + if (i.length === 1) + return [ + [we, t], + [be, e] + ]; + var n = Ck(t, e); + if (n) { + var o = n[0], u = n[1], p = n[2], g = n[3], k = n[4], y = Ks(o, p), b = Ks(u, g); + return y.concat([[oe, k]], b); + } + return wk(t, e); +} +function wk(t, e) { + for (var s = t.length, r = e.length, i = Math.ceil((s + r) / 2), a = i, n = 2 * i, o = new Array(n), u = new Array(n), p = 0; p < n; p++) + o[p] = -1, u[p] = -1; + o[a + 1] = 0, u[a + 1] = 0; + for (var g = s - r, k = g % 2 !== 0, y = 0, b = 0, v = 0, E = 0, S = 0; S < i; S++) { + for (var _ = -S + y; _ <= S - b; _ += 2) { + var O = a + _, D; + _ === -S || _ !== S && o[O - 1] < o[O + 1] ? D = o[O + 1] : D = o[O - 1] + 1; + for (var U = D - _; D < s && U < r && t.charAt(D) === e.charAt(U); ) + D++, U++; + if (o[O] = D, D > s) + b += 2; + else if (U > r) + y += 2; + else if (k) { + var M = a + g - _; + if (M >= 0 && M < n && u[M] !== -1) { + var G = s - u[M]; + if (D >= G) + return Pa(t, e, D, U); + } + } + } + for (var Z = -S + v; Z <= S - E; Z += 2) { + var M = a + Z, G; + Z === -S || Z !== S && u[M - 1] < u[M + 1] ? G = u[M + 1] : G = u[M - 1] + 1; + for (var te = G - Z; G < s && te < r && t.charAt(s - G - 1) === e.charAt(r - te - 1); ) + G++, te++; + if (u[M] = G, G > s) + E += 2; + else if (te > r) + v += 2; + else if (!k) { + var O = a + g - Z; + if (O >= 0 && O < n && o[O] !== -1) { + var D = o[O], U = a + D - O; + if (G = s - G, D >= G) + return Pa(t, e, D, U); + } + } + } + } + return [ + [we, t], + [be, e] + ]; +} +function Pa(t, e, s, r) { + var i = t.substring(0, s), a = e.substring(0, r), n = t.substring(s), o = e.substring(r), u = Ks(i, a), p = Ks(n, o); + return u.concat(p); +} +function Ki(t, e) { + if (!t || !e || t.charAt(0) !== e.charAt(0)) + return 0; + for (var s = 0, r = Math.min(t.length, e.length), i = r, a = 0; s < i; ) + t.substring(a, i) == e.substring(a, i) ? (s = i, a = s) : r = i, i = Math.floor((r - s) / 2 + s); + return zo(t.charCodeAt(i - 1)) && i--, i; +} +function Da(t, e) { + var s = t.length, r = e.length; + if (s == 0 || r == 0) + return 0; + s > r ? t = t.substring(s - r) : s < r && (e = e.substring(0, s)); + var i = Math.min(s, r); + if (t == e) + return i; + for (var a = 0, n = 1; ; ) { + var o = t.substring(i - n), u = e.indexOf(o); + if (u == -1) + return a; + n += u, (u == 0 || t.substring(i - n) == e.substring(0, n)) && (a = n, n++); + } +} +function Xn(t, e) { + if (!t || !e || t.slice(-1) !== e.slice(-1)) + return 0; + for (var s = 0, r = Math.min(t.length, e.length), i = r, a = 0; s < i; ) + t.substring(t.length - i, t.length - a) == e.substring(e.length - i, e.length - a) ? (s = i, a = s) : r = i, i = Math.floor((r - s) / 2 + s); + return Go(t.charCodeAt(t.length - i)) && i--, i; +} +function Ck(t, e) { + var s = t.length > e.length ? t : e, r = t.length > e.length ? e : t; + if (s.length < 4 || r.length * 2 < s.length) + return null; + function i(b, v, E) { + for (var S = b.substring(E, E + Math.floor(b.length / 4)), _ = -1, O = "", D, U, M, G; (_ = v.indexOf(S, _ + 1)) !== -1; ) { + var Z = Ki( + b.substring(E), + v.substring(_) + ), te = Xn( + b.substring(0, E), + v.substring(0, _) + ); + O.length < te + Z && (O = v.substring(_ - te, _) + v.substring(_, _ + Z), D = b.substring(0, E - te), U = b.substring(E + Z), M = v.substring(0, _ - te), G = v.substring(_ + Z)); + } + return O.length * 2 >= b.length ? [ + D, + U, + M, + G, + O + ] : null; + } + var a = i( + s, + r, + Math.ceil(s.length / 4) + ), n = i( + s, + r, + Math.ceil(s.length / 2) + ), o; + if (!a && !n) + return null; + n ? a ? o = a[4].length > n[4].length ? a : n : o = n : o = a; + var u, p, g, k; + t.length > e.length ? (u = o[0], p = o[1], g = o[2], k = o[3]) : (g = o[0], k = o[1], u = o[2], p = o[3]); + var y = o[4]; + return [u, p, g, k, y]; +} +function Ak(t) { + for (var e = !1, s = [], r = 0, i = null, a = 0, n = 0, o = 0, u = 0, p = 0; a < t.length; ) + t[a][0] == oe ? (s[r++] = a, n = u, o = p, u = 0, p = 0, i = t[a][1]) : (t[a][0] == be ? u += t[a][1].length : p += t[a][1].length, i && i.length <= Math.max(n, o) && i.length <= Math.max(u, p) && (t.splice(s[r - 1], 0, [ + we, + i + ]), t[s[r - 1] + 1][0] = be, r--, r--, a = r > 0 ? s[r - 1] : -1, n = 0, o = 0, u = 0, p = 0, i = null, e = !0)), a++; + for (e && Wi(t), Tk(t), a = 1; a < t.length; ) { + if (t[a - 1][0] == we && t[a][0] == be) { + var g = t[a - 1][1], k = t[a][1], y = Da(g, k), b = Da(k, g); + y >= b ? (y >= g.length / 2 || y >= k.length / 2) && (t.splice(a, 0, [ + oe, + k.substring(0, y) + ]), t[a - 1][1] = g.substring( + 0, + g.length - y + ), t[a + 1][1] = k.substring(y), a++) : (b >= g.length / 2 || b >= k.length / 2) && (t.splice(a, 0, [ + oe, + g.substring(0, b) + ]), t[a - 1][0] = be, t[a - 1][1] = k.substring( + 0, + k.length - b + ), t[a + 1][0] = we, t[a + 1][1] = g.substring(b), a++), a++; + } + a++; + } +} +var Ra = /[^a-zA-Z0-9]/, Ba = /\s/, Ma = /[\r\n]/, Ek = /\n\r?\n$/, Sk = /^\r?\n\r?\n/; +function Tk(t) { + function e(b, v) { + if (!b || !v) + return 6; + var E = b.charAt(b.length - 1), S = v.charAt(0), _ = E.match(Ra), O = S.match(Ra), D = _ && E.match(Ba), U = O && S.match(Ba), M = D && E.match(Ma), G = U && S.match(Ma), Z = M && b.match(Ek), te = G && v.match(Sk); + return Z || te ? 5 : M || G ? 4 : _ && !D && U ? 3 : D || U ? 2 : _ || O ? 1 : 0; + } + for (var s = 1; s < t.length - 1; ) { + if (t[s - 1][0] == oe && t[s + 1][0] == oe) { + var r = t[s - 1][1], i = t[s][1], a = t[s + 1][1], n = Xn(r, i); + if (n) { + var o = i.substring(i.length - n); + r = r.substring(0, r.length - n), i = o + i.substring(0, i.length - n), a = o + a; + } + for (var u = r, p = i, g = a, k = e(r, i) + e(i, a); i.charAt(0) === a.charAt(0); ) { + r += i.charAt(0), i = i.substring(1) + a.charAt(0), a = a.substring(1); + var y = e(r, i) + e(i, a); + y >= k && (k = y, u = r, p = i, g = a); + } + t[s - 1][1] != u && (u ? t[s - 1][1] = u : (t.splice(s - 1, 1), s--), t[s][1] = p, g ? t[s + 1][1] = g : (t.splice(s + 1, 1), s--)); + } + s++; + } +} +function Wi(t, e) { + t.push([oe, ""]); + for (var s = 0, r = 0, i = 0, a = "", n = "", o; s < t.length; ) { + if (s < t.length - 1 && !t[s][1]) { + t.splice(s, 1); + continue; + } + switch (t[s][0]) { + case be: + i++, n += t[s][1], s++; + break; + case we: + r++, a += t[s][1], s++; + break; + case oe: + var u = s - i - r - 1; + if (e) { + if (u >= 0 && Wo(t[u][1])) { + var p = t[u][1].slice(-1); + if (t[u][1] = t[u][1].slice( + 0, + -1 + ), a = p + a, n = p + n, !t[u][1]) { + t.splice(u, 1), s--; + var g = u - 1; + t[g] && t[g][0] === be && (i++, n = t[g][1] + n, g--), t[g] && t[g][0] === we && (r++, a = t[g][1] + a, g--), u = g; + } + } + if (Ko(t[s][1])) { + var p = t[s][1].charAt(0); + t[s][1] = t[s][1].slice(1), a += p, n += p; + } + } + if (s < t.length - 1 && !t[s][1]) { + t.splice(s, 1); + break; + } + if (a.length > 0 || n.length > 0) { + a.length > 0 && n.length > 0 && (o = Ki(n, a), o !== 0 && (u >= 0 ? t[u][1] += n.substring( + 0, + o + ) : (t.splice(0, 0, [ + oe, + n.substring(0, o) + ]), s++), n = n.substring(o), a = a.substring(o)), o = Xn(n, a), o !== 0 && (t[s][1] = n.substring(n.length - o) + t[s][1], n = n.substring( + 0, + n.length - o + ), a = a.substring( + 0, + a.length - o + ))); + var k = i + r; + a.length === 0 && n.length === 0 ? (t.splice(s - k, k), s = s - k) : a.length === 0 ? (t.splice(s - k, k, [be, n]), s = s - k + 1) : n.length === 0 ? (t.splice(s - k, k, [we, a]), s = s - k + 1) : (t.splice( + s - k, + k, + [we, a], + [be, n] + ), s = s - k + 2); + } + s !== 0 && t[s - 1][0] === oe ? (t[s - 1][1] += t[s][1], t.splice(s, 1)) : s++, i = 0, r = 0, a = "", n = ""; + break; + } + } + t[t.length - 1][1] === "" && t.pop(); + var y = !1; + for (s = 1; s < t.length - 1; ) + t[s - 1][0] === oe && t[s + 1][0] === oe && (t[s][1].substring( + t[s][1].length - t[s - 1][1].length + ) === t[s - 1][1] ? (t[s][1] = t[s - 1][1] + t[s][1].substring( + 0, + t[s][1].length - t[s - 1][1].length + ), t[s + 1][1] = t[s - 1][1] + t[s + 1][1], t.splice(s - 1, 1), y = !0) : t[s][1].substring(0, t[s + 1][1].length) == t[s + 1][1] && (t[s - 1][1] += t[s + 1][1], t[s][1] = t[s][1].substring(t[s + 1][1].length) + t[s + 1][1], t.splice(s + 1, 1), y = !0)), s++; + y && Wi(t, e); +} +function zo(t) { + return t >= 55296 && t <= 56319; +} +function Go(t) { + return t >= 56320 && t <= 57343; +} +function Ko(t) { + return Go(t.charCodeAt(0)); +} +function Wo(t) { + return zo(t.charCodeAt(t.length - 1)); +} +function _k(t) { + for (var e = [], s = 0; s < t.length; s++) + t[s][1].length > 0 && e.push(t[s]); + return e; +} +function xr(t, e, s, r) { + return Wo(t) || Ko(r) ? null : _k([ + [oe, t], + [we, e], + [be, s], + [oe, r] + ]); +} +function Nk(t, e, s) { + var r = typeof s == "number" ? { index: s, length: 0 } : s.oldRange, i = typeof s == "number" ? null : s.newRange, a = t.length, n = e.length; + if (r.length === 0 && (i === null || i.length === 0)) { + var o = r.index, u = t.slice(0, o), p = t.slice(o), g = i ? i.index : null; + e: { + var k = o + n - a; + if (g !== null && g !== k || k < 0 || k > n) + break e; + var y = e.slice(0, k), b = e.slice(k); + if (b !== p) + break e; + var v = Math.min(o, k), E = u.slice(0, v), S = y.slice(0, v); + if (E !== S) + break e; + var _ = u.slice(v), O = y.slice(v); + return xr(E, _, O, p); + } + e: { + if (g !== null && g !== o) + break e; + var D = o, y = e.slice(0, D), b = e.slice(D); + if (y !== u) + break e; + var U = Math.min(a - D, n - D), M = p.slice(p.length - U), G = b.slice(b.length - U); + if (M !== G) + break e; + var _ = p.slice(0, p.length - U), O = b.slice(0, b.length - U); + return xr(u, _, O, M); + } + } + if (r.length > 0 && i && i.length === 0) + e: { + var E = t.slice(0, r.index), M = t.slice(r.index + r.length), v = E.length, U = M.length; + if (n < v + U) + break e; + var S = e.slice(0, v), G = e.slice(n - U); + if (E !== S || M !== G) + break e; + var _ = t.slice(v, a - U), O = e.slice(v, n - U); + return xr(E, _, O, M); + } + return null; +} +function Yn(t, e, s, r) { + return Ks(t, e, s, r, !0); +} +Yn.INSERT = be; +Yn.DELETE = we; +Yn.EQUAL = oe; +var Ik = Yn, Vn = { exports: {} }; +Vn.exports; +(function(t, e) { + var s = 200, r = "__lodash_hash_undefined__", i = 9007199254740991, a = "[object Arguments]", n = "[object Array]", o = "[object Boolean]", u = "[object Date]", p = "[object Error]", g = "[object Function]", k = "[object GeneratorFunction]", y = "[object Map]", b = "[object Number]", v = "[object Object]", E = "[object Promise]", S = "[object RegExp]", _ = "[object Set]", O = "[object String]", D = "[object Symbol]", U = "[object WeakMap]", M = "[object ArrayBuffer]", G = "[object DataView]", Z = "[object Float32Array]", te = "[object Float64Array]", tt = "[object Int8Array]", ft = "[object Int16Array]", Ct = "[object Int32Array]", At = "[object Uint8Array]", tn = "[object Uint8ClampedArray]", sn = "[object Uint16Array]", nn = "[object Uint32Array]", tr = /[\\^$.*+?()[\]{}|]/g, sr = /\w*$/, nr = /^\[object .+?Constructor\]$/, rr = /^(?:0|[1-9]\d*)$/, J = {}; + J[a] = J[n] = J[M] = J[G] = J[o] = J[u] = J[Z] = J[te] = J[tt] = J[ft] = J[Ct] = J[y] = J[b] = J[v] = J[S] = J[_] = J[O] = J[D] = J[At] = J[tn] = J[sn] = J[nn] = !0, J[p] = J[g] = J[U] = !1; + var ir = typeof mt == "object" && mt && mt.Object === Object && mt, ar = typeof self == "object" && self && self.Object === Object && self, Le = ir || ar || Function("return this")(), rn = e && !e.nodeType && e, x = rn && !0 && t && !t.nodeType && t, an = x && x.exports === rn; + function or(l, h) { + return l.set(h[0], h[1]), l; + } + function qe(l, h) { + return l.add(h), l; + } + function on(l, h) { + for (var $ = -1, T = l ? l.length : 0; ++$ < T && h(l[$], $, l) !== !1; ) + ; + return l; + } + function ln(l, h) { + for (var $ = -1, T = h.length, K = l.length; ++$ < T; ) + l[K + $] = h[$]; + return l; + } + function hs(l, h, $, T) { + for (var K = -1, z = l ? l.length : 0; ++K < z; ) + $ = h($, l[K], K, l); + return $; + } + function gs(l, h) { + for (var $ = -1, T = Array(l); ++$ < l; ) + T[$] = h($); + return T; + } + function un(l, h) { + return l == null ? void 0 : l[h]; + } + function ms(l) { + var h = !1; + if (l != null && typeof l.toString != "function") + try { + h = !!(l + ""); + } catch { + } + return h; + } + function dn(l) { + var h = -1, $ = Array(l.size); + return l.forEach(function(T, K) { + $[++h] = [K, T]; + }), $; + } + function vs(l, h) { + return function($) { + return l(h($)); + }; + } + function cn(l) { + var h = -1, $ = Array(l.size); + return l.forEach(function(T) { + $[++h] = T; + }), $; + } + var lr = Array.prototype, ur = Function.prototype, jt = Object.prototype, bs = Le["__core-js_shared__"], fn = function() { + var l = /[^.]+$/.exec(bs && bs.keys && bs.keys.IE_PROTO || ""); + return l ? "Symbol(src)_1." + l : ""; + }(), pn = ur.toString, Ge = jt.hasOwnProperty, Vt = jt.toString, dr = RegExp( + "^" + pn.call(Ge).replace(tr, "\\$&").replace(/hasOwnProperty|(function).*?(?=\\\()| for .+?(?=\\\])/g, "$1.*?") + "$" + ), Et = an ? Le.Buffer : void 0, Ht = Le.Symbol, ys = Le.Uint8Array, Ee = vs(Object.getPrototypeOf, Object), hn = Object.create, gn = jt.propertyIsEnumerable, cr = lr.splice, $s = Object.getOwnPropertySymbols, zt = Et ? Et.isBuffer : void 0, mn = vs(Object.keys, Object), Gt = Pe(Le, "DataView"), St = Pe(Le, "Map"), Oe = Pe(Le, "Promise"), Kt = Pe(Le, "Set"), ks = Pe(Le, "WeakMap"), Tt = Pe(Object, "create"), ws = ve(Gt), _t = ve(St), Cs = ve(Oe), As = ve(Kt), Es = ve(ks), pt = Ht ? Ht.prototype : void 0, vn = pt ? pt.valueOf : void 0; + function st(l) { + var h = -1, $ = l ? l.length : 0; + for (this.clear(); ++h < $; ) { + var T = l[h]; + this.set(T[0], T[1]); + } + } + function fr() { + this.__data__ = Tt ? Tt(null) : {}; + } + function pr(l) { + return this.has(l) && delete this.__data__[l]; + } + function hr(l) { + var h = this.__data__; + if (Tt) { + var $ = h[l]; + return $ === r ? void 0 : $; + } + return Ge.call(h, l) ? h[l] : void 0; + } + function bn(l) { + var h = this.__data__; + return Tt ? h[l] !== void 0 : Ge.call(h, l); + } + function Ss(l, h) { + var $ = this.__data__; + return $[l] = Tt && h === void 0 ? r : h, this; + } + st.prototype.clear = fr, st.prototype.delete = pr, st.prototype.get = hr, st.prototype.has = bn, st.prototype.set = Ss; + function le(l) { + var h = -1, $ = l ? l.length : 0; + for (this.clear(); ++h < $; ) { + var T = l[h]; + this.set(T[0], T[1]); + } + } + function gr() { + this.__data__ = []; + } + function mr(l) { + var h = this.__data__, $ = Zt(h, l); + if ($ < 0) + return !1; + var T = h.length - 1; + return $ == T ? h.pop() : cr.call(h, $, 1), !0; + } + function vr(l) { + var h = this.__data__, $ = Zt(h, l); + return $ < 0 ? void 0 : h[$][1]; + } + function br(l) { + return Zt(this.__data__, l) > -1; + } + function yr(l, h) { + var $ = this.__data__, T = Zt($, l); + return T < 0 ? $.push([l, h]) : $[T][1] = h, this; + } + le.prototype.clear = gr, le.prototype.delete = mr, le.prototype.get = vr, le.prototype.has = br, le.prototype.set = yr; + function fe(l) { + var h = -1, $ = l ? l.length : 0; + for (this.clear(); ++h < $; ) { + var T = l[h]; + this.set(T[0], T[1]); + } + } + function $r() { + this.__data__ = { + hash: new st(), + map: new (St || le)(), + string: new st() + }; + } + function kr(l) { + return It(this, l).delete(l); + } + function wr(l) { + return It(this, l).get(l); + } + function Cr(l) { + return It(this, l).has(l); + } + function Ar(l, h) { + return It(this, l).set(l, h), this; + } + fe.prototype.clear = $r, fe.prototype.delete = kr, fe.prototype.get = wr, fe.prototype.has = Cr, fe.prototype.set = Ar; + function $e(l) { + this.__data__ = new le(l); + } + function Er() { + this.__data__ = new le(); + } + function Sr(l) { + return this.__data__.delete(l); + } + function Tr(l) { + return this.__data__.get(l); + } + function _r(l) { + return this.__data__.has(l); + } + function Nr(l, h) { + var $ = this.__data__; + if ($ instanceof le) { + var T = $.__data__; + if (!St || T.length < s - 1) + return T.push([l, h]), this; + $ = this.__data__ = new fe(T); + } + return $.set(l, h), this; + } + $e.prototype.clear = Er, $e.prototype.delete = Sr, $e.prototype.get = Tr, $e.prototype.has = _r, $e.prototype.set = Nr; + function Wt(l, h) { + var $ = Is(l) || Yt(l) ? gs(l.length, String) : [], T = $.length, K = !!T; + for (var z in l) + Ge.call(l, z) && !(K && (z == "length" || Hr(z, T))) && $.push(z); + return $; + } + function yn(l, h, $) { + var T = l[h]; + (!(Ge.call(l, h) && An(T, $)) || $ === void 0 && !(h in l)) && (l[h] = $); + } + function Zt(l, h) { + for (var $ = l.length; $--; ) + if (An(l[$][0], h)) + return $; + return -1; + } + function Ke(l, h) { + return l && Ns(h, qs(h), l); + } + function Ts(l, h, $, T, K, z, Y) { + var X; + if (T && (X = z ? T(l, K, z, Y) : T(l)), X !== void 0) + return X; + if (!Ze(l)) + return l; + var re = Is(l); + if (re) { + if (X = jr(l), !h) + return Mr(l, X); + } else { + var Q = rt(l), pe = Q == g || Q == k; + if (En(l)) + return Xt(l, h); + if (Q == v || Q == a || pe && !z) { + if (ms(l)) + return z ? l : {}; + if (X = We(pe ? {} : l), !h) + return Fr(l, Ke(X, l)); + } else { + if (!J[Q]) + return z ? l : {}; + X = Vr(l, Q, Ts, h); + } + } + Y || (Y = new $e()); + var ke = Y.get(l); + if (ke) + return ke; + if (Y.set(l, X), !re) + var ae = $ ? Ur(l) : qs(l); + return on(ae || l, function(he, ue) { + ae && (ue = he, he = l[ue]), yn(X, ue, Ts(he, h, $, T, ue, l, Y)); + }), X; + } + function Ir(l) { + return Ze(l) ? hn(l) : {}; + } + function Lr(l, h, $) { + var T = h(l); + return Is(l) ? T : ln(T, $(l)); + } + function qr(l) { + return Vt.call(l); + } + function Or(l) { + if (!Ze(l) || Gr(l)) + return !1; + var h = Ls(l) || ms(l) ? dr : nr; + return h.test(ve(l)); + } + function Pr(l) { + if (!wn(l)) + return mn(l); + var h = []; + for (var $ in Object(l)) + Ge.call(l, $) && $ != "constructor" && h.push($); + return h; + } + function Xt(l, h) { + if (h) + return l.slice(); + var $ = new l.constructor(l.length); + return l.copy($), $; + } + function _s(l) { + var h = new l.constructor(l.byteLength); + return new ys(h).set(new ys(l)), h; + } + function Nt(l, h) { + var $ = h ? _s(l.buffer) : l.buffer; + return new l.constructor($, l.byteOffset, l.byteLength); + } + function $n(l, h, $) { + var T = h ? $(dn(l), !0) : dn(l); + return hs(T, or, new l.constructor()); + } + function kn(l) { + var h = new l.constructor(l.source, sr.exec(l)); + return h.lastIndex = l.lastIndex, h; + } + function Dr(l, h, $) { + var T = h ? $(cn(l), !0) : cn(l); + return hs(T, qe, new l.constructor()); + } + function Rr(l) { + return vn ? Object(vn.call(l)) : {}; + } + function Br(l, h) { + var $ = h ? _s(l.buffer) : l.buffer; + return new l.constructor($, l.byteOffset, l.length); + } + function Mr(l, h) { + var $ = -1, T = l.length; + for (h || (h = Array(T)); ++$ < T; ) + h[$] = l[$]; + return h; + } + function Ns(l, h, $, T) { + $ || ($ = {}); + for (var K = -1, z = h.length; ++K < z; ) { + var Y = h[K], X = void 0; + yn($, Y, X === void 0 ? l[Y] : X); + } + return $; + } + function Fr(l, h) { + return Ns(l, nt(l), h); + } + function Ur(l) { + return Lr(l, qs, nt); + } + function It(l, h) { + var $ = l.__data__; + return zr(h) ? $[typeof h == "string" ? "string" : "hash"] : $.map; + } + function Pe(l, h) { + var $ = un(l, h); + return Or($) ? $ : void 0; + } + var nt = $s ? vs($s, Object) : Wr, rt = qr; + (Gt && rt(new Gt(new ArrayBuffer(1))) != G || St && rt(new St()) != y || Oe && rt(Oe.resolve()) != E || Kt && rt(new Kt()) != _ || ks && rt(new ks()) != U) && (rt = function(l) { + var h = Vt.call(l), $ = h == v ? l.constructor : void 0, T = $ ? ve($) : void 0; + if (T) + switch (T) { + case ws: + return G; + case _t: + return y; + case Cs: + return E; + case As: + return _; + case Es: + return U; + } + return h; + }); + function jr(l) { + var h = l.length, $ = l.constructor(h); + return h && typeof l[0] == "string" && Ge.call(l, "index") && ($.index = l.index, $.input = l.input), $; + } + function We(l) { + return typeof l.constructor == "function" && !wn(l) ? Ir(Ee(l)) : {}; + } + function Vr(l, h, $, T) { + var K = l.constructor; + switch (h) { + case M: + return _s(l); + case o: + case u: + return new K(+l); + case G: + return Nt(l, T); + case Z: + case te: + case tt: + case ft: + case Ct: + case At: + case tn: + case sn: + case nn: + return Br(l, T); + case y: + return $n(l, T, $); + case b: + case O: + return new K(l); + case S: + return kn(l); + case _: + return Dr(l, T, $); + case D: + return Rr(l); + } + } + function Hr(l, h) { + return h = h ?? i, !!h && (typeof l == "number" || rr.test(l)) && l > -1 && l % 1 == 0 && l < h; + } + function zr(l) { + var h = typeof l; + return h == "string" || h == "number" || h == "symbol" || h == "boolean" ? l !== "__proto__" : l === null; + } + function Gr(l) { + return !!fn && fn in l; + } + function wn(l) { + var h = l && l.constructor, $ = typeof h == "function" && h.prototype || jt; + return l === $; + } + function ve(l) { + if (l != null) { + try { + return pn.call(l); + } catch { + } + try { + return l + ""; + } catch { + } + } + return ""; + } + function Cn(l) { + return Ts(l, !0, !0); + } + function An(l, h) { + return l === h || l !== l && h !== h; + } + function Yt(l) { + return Kr(l) && Ge.call(l, "callee") && (!gn.call(l, "callee") || Vt.call(l) == a); + } + var Is = Array.isArray; + function Qt(l) { + return l != null && Sn(l.length) && !Ls(l); + } + function Kr(l) { + return Tn(l) && Qt(l); + } + var En = zt || Zr; + function Ls(l) { + var h = Ze(l) ? Vt.call(l) : ""; + return h == g || h == k; + } + function Sn(l) { + return typeof l == "number" && l > -1 && l % 1 == 0 && l <= i; + } + function Ze(l) { + var h = typeof l; + return !!l && (h == "object" || h == "function"); + } + function Tn(l) { + return !!l && typeof l == "object"; + } + function qs(l) { + return Qt(l) ? Wt(l) : Pr(l); + } + function Wr() { + return []; + } + function Zr() { + return !1; + } + t.exports = Cn; +})(Vn, Vn.exports); +var Zo = Vn.exports, Hn = { exports: {} }; +Hn.exports; +(function(t, e) { + var s = 200, r = "__lodash_hash_undefined__", i = 1, a = 2, n = 9007199254740991, o = "[object Arguments]", u = "[object Array]", p = "[object AsyncFunction]", g = "[object Boolean]", k = "[object Date]", y = "[object Error]", b = "[object Function]", v = "[object GeneratorFunction]", E = "[object Map]", S = "[object Number]", _ = "[object Null]", O = "[object Object]", D = "[object Promise]", U = "[object Proxy]", M = "[object RegExp]", G = "[object Set]", Z = "[object String]", te = "[object Symbol]", tt = "[object Undefined]", ft = "[object WeakMap]", Ct = "[object ArrayBuffer]", At = "[object DataView]", tn = "[object Float32Array]", sn = "[object Float64Array]", nn = "[object Int8Array]", tr = "[object Int16Array]", sr = "[object Int32Array]", nr = "[object Uint8Array]", rr = "[object Uint8ClampedArray]", J = "[object Uint16Array]", ir = "[object Uint32Array]", ar = /[\\^$.*+?()[\]{}|]/g, Le = /^\[object .+?Constructor\]$/, rn = /^(?:0|[1-9]\d*)$/, x = {}; + x[tn] = x[sn] = x[nn] = x[tr] = x[sr] = x[nr] = x[rr] = x[J] = x[ir] = !0, x[o] = x[u] = x[Ct] = x[g] = x[At] = x[k] = x[y] = x[b] = x[E] = x[S] = x[O] = x[M] = x[G] = x[Z] = x[ft] = !1; + var an = typeof mt == "object" && mt && mt.Object === Object && mt, or = typeof self == "object" && self && self.Object === Object && self, qe = an || or || Function("return this")(), on = e && !e.nodeType && e, ln = on && !0 && t && !t.nodeType && t, hs = ln && ln.exports === on, gs = hs && an.process, un = function() { + try { + return gs && gs.binding && gs.binding("util"); + } catch { + } + }(), ms = un && un.isTypedArray; + function dn(l, h) { + for (var $ = -1, T = l == null ? 0 : l.length, K = 0, z = []; ++$ < T; ) { + var Y = l[$]; + h(Y, $, l) && (z[K++] = Y); + } + return z; + } + function vs(l, h) { + for (var $ = -1, T = h.length, K = l.length; ++$ < T; ) + l[K + $] = h[$]; + return l; + } + function cn(l, h) { + for (var $ = -1, T = l == null ? 0 : l.length; ++$ < T; ) + if (h(l[$], $, l)) + return !0; + return !1; + } + function lr(l, h) { + for (var $ = -1, T = Array(l); ++$ < l; ) + T[$] = h($); + return T; + } + function ur(l) { + return function(h) { + return l(h); + }; + } + function jt(l, h) { + return l.has(h); + } + function bs(l, h) { + return l == null ? void 0 : l[h]; + } + function fn(l) { + var h = -1, $ = Array(l.size); + return l.forEach(function(T, K) { + $[++h] = [K, T]; + }), $; + } + function pn(l, h) { + return function($) { + return l(h($)); + }; + } + function Ge(l) { + var h = -1, $ = Array(l.size); + return l.forEach(function(T) { + $[++h] = T; + }), $; + } + var Vt = Array.prototype, dr = Function.prototype, Et = Object.prototype, Ht = qe["__core-js_shared__"], ys = dr.toString, Ee = Et.hasOwnProperty, hn = function() { + var l = /[^.]+$/.exec(Ht && Ht.keys && Ht.keys.IE_PROTO || ""); + return l ? "Symbol(src)_1." + l : ""; + }(), gn = Et.toString, cr = RegExp( + "^" + ys.call(Ee).replace(ar, "\\$&").replace(/hasOwnProperty|(function).*?(?=\\\()| for .+?(?=\\\])/g, "$1.*?") + "$" + ), $s = hs ? qe.Buffer : void 0, zt = qe.Symbol, mn = qe.Uint8Array, Gt = Et.propertyIsEnumerable, St = Vt.splice, Oe = zt ? zt.toStringTag : void 0, Kt = Object.getOwnPropertySymbols, ks = $s ? $s.isBuffer : void 0, Tt = pn(Object.keys, Object), ws = nt(qe, "DataView"), _t = nt(qe, "Map"), Cs = nt(qe, "Promise"), As = nt(qe, "Set"), Es = nt(qe, "WeakMap"), pt = nt(Object, "create"), vn = ve(ws), st = ve(_t), fr = ve(Cs), pr = ve(As), hr = ve(Es), bn = zt ? zt.prototype : void 0, Ss = bn ? bn.valueOf : void 0; + function le(l) { + var h = -1, $ = l == null ? 0 : l.length; + for (this.clear(); ++h < $; ) { + var T = l[h]; + this.set(T[0], T[1]); + } + } + function gr() { + this.__data__ = pt ? pt(null) : {}, this.size = 0; + } + function mr(l) { + var h = this.has(l) && delete this.__data__[l]; + return this.size -= h ? 1 : 0, h; + } + function vr(l) { + var h = this.__data__; + if (pt) { + var $ = h[l]; + return $ === r ? void 0 : $; + } + return Ee.call(h, l) ? h[l] : void 0; + } + function br(l) { + var h = this.__data__; + return pt ? h[l] !== void 0 : Ee.call(h, l); + } + function yr(l, h) { + var $ = this.__data__; + return this.size += this.has(l) ? 0 : 1, $[l] = pt && h === void 0 ? r : h, this; + } + le.prototype.clear = gr, le.prototype.delete = mr, le.prototype.get = vr, le.prototype.has = br, le.prototype.set = yr; + function fe(l) { + var h = -1, $ = l == null ? 0 : l.length; + for (this.clear(); ++h < $; ) { + var T = l[h]; + this.set(T[0], T[1]); + } + } + function $r() { + this.__data__ = [], this.size = 0; + } + function kr(l) { + var h = this.__data__, $ = Xt(h, l); + if ($ < 0) + return !1; + var T = h.length - 1; + return $ == T ? h.pop() : St.call(h, $, 1), --this.size, !0; + } + function wr(l) { + var h = this.__data__, $ = Xt(h, l); + return $ < 0 ? void 0 : h[$][1]; + } + function Cr(l) { + return Xt(this.__data__, l) > -1; + } + function Ar(l, h) { + var $ = this.__data__, T = Xt($, l); + return T < 0 ? (++this.size, $.push([l, h])) : $[T][1] = h, this; + } + fe.prototype.clear = $r, fe.prototype.delete = kr, fe.prototype.get = wr, fe.prototype.has = Cr, fe.prototype.set = Ar; + function $e(l) { + var h = -1, $ = l == null ? 0 : l.length; + for (this.clear(); ++h < $; ) { + var T = l[h]; + this.set(T[0], T[1]); + } + } + function Er() { + this.size = 0, this.__data__ = { + hash: new le(), + map: new (_t || fe)(), + string: new le() + }; + } + function Sr(l) { + var h = Pe(this, l).delete(l); + return this.size -= h ? 1 : 0, h; + } + function Tr(l) { + return Pe(this, l).get(l); + } + function _r(l) { + return Pe(this, l).has(l); + } + function Nr(l, h) { + var $ = Pe(this, l), T = $.size; + return $.set(l, h), this.size += $.size == T ? 0 : 1, this; + } + $e.prototype.clear = Er, $e.prototype.delete = Sr, $e.prototype.get = Tr, $e.prototype.has = _r, $e.prototype.set = Nr; + function Wt(l) { + var h = -1, $ = l == null ? 0 : l.length; + for (this.__data__ = new $e(); ++h < $; ) + this.add(l[h]); + } + function yn(l) { + return this.__data__.set(l, r), this; + } + function Zt(l) { + return this.__data__.has(l); + } + Wt.prototype.add = Wt.prototype.push = yn, Wt.prototype.has = Zt; + function Ke(l) { + var h = this.__data__ = new fe(l); + this.size = h.size; + } + function Ts() { + this.__data__ = new fe(), this.size = 0; + } + function Ir(l) { + var h = this.__data__, $ = h.delete(l); + return this.size = h.size, $; + } + function Lr(l) { + return this.__data__.get(l); + } + function qr(l) { + return this.__data__.has(l); + } + function Or(l, h) { + var $ = this.__data__; + if ($ instanceof fe) { + var T = $.__data__; + if (!_t || T.length < s - 1) + return T.push([l, h]), this.size = ++$.size, this; + $ = this.__data__ = new $e(T); + } + return $.set(l, h), this.size = $.size, this; + } + Ke.prototype.clear = Ts, Ke.prototype.delete = Ir, Ke.prototype.get = Lr, Ke.prototype.has = qr, Ke.prototype.set = Or; + function Pr(l, h) { + var $ = Yt(l), T = !$ && An(l), K = !$ && !T && Qt(l), z = !$ && !T && !K && Tn(l), Y = $ || T || K || z, X = Y ? lr(l.length, String) : [], re = X.length; + for (var Q in l) + Ee.call(l, Q) && !(Y && // Safari 9 has enumerable `arguments.length` in strict mode. + (Q == "length" || // Node.js 0.10 has enumerable non-index properties on buffers. + K && (Q == "offset" || Q == "parent") || // PhantomJS 2 has enumerable non-index properties on typed arrays. + z && (Q == "buffer" || Q == "byteLength" || Q == "byteOffset") || // Skip index properties. + Vr(Q, re))) && X.push(Q); + return X; + } + function Xt(l, h) { + for (var $ = l.length; $--; ) + if (Cn(l[$][0], h)) + return $; + return -1; + } + function _s(l, h, $) { + var T = h(l); + return Yt(l) ? T : vs(T, $(l)); + } + function Nt(l) { + return l == null ? l === void 0 ? tt : _ : Oe && Oe in Object(l) ? rt(l) : wn(l); + } + function $n(l) { + return Ze(l) && Nt(l) == o; + } + function kn(l, h, $, T, K) { + return l === h ? !0 : l == null || h == null || !Ze(l) && !Ze(h) ? l !== l && h !== h : Dr(l, h, $, T, kn, K); + } + function Dr(l, h, $, T, K, z) { + var Y = Yt(l), X = Yt(h), re = Y ? u : We(l), Q = X ? u : We(h); + re = re == o ? O : re, Q = Q == o ? O : Q; + var pe = re == O, ke = Q == O, ae = re == Q; + if (ae && Qt(l)) { + if (!Qt(h)) + return !1; + Y = !0, pe = !1; + } + if (ae && !pe) + return z || (z = new Ke()), Y || Tn(l) ? Ns(l, h, $, T, K, z) : Fr(l, h, re, $, T, K, z); + if (!($ & i)) { + var he = pe && Ee.call(l, "__wrapped__"), ue = ke && Ee.call(h, "__wrapped__"); + if (he || ue) { + var ht = he ? l.value() : l, it = ue ? h.value() : h; + return z || (z = new Ke()), K(ht, it, $, T, z); + } + } + return ae ? (z || (z = new Ke()), Ur(l, h, $, T, K, z)) : !1; + } + function Rr(l) { + if (!Sn(l) || zr(l)) + return !1; + var h = En(l) ? cr : Le; + return h.test(ve(l)); + } + function Br(l) { + return Ze(l) && Ls(l.length) && !!x[Nt(l)]; + } + function Mr(l) { + if (!Gr(l)) + return Tt(l); + var h = []; + for (var $ in Object(l)) + Ee.call(l, $) && $ != "constructor" && h.push($); + return h; + } + function Ns(l, h, $, T, K, z) { + var Y = $ & i, X = l.length, re = h.length; + if (X != re && !(Y && re > X)) + return !1; + var Q = z.get(l); + if (Q && z.get(h)) + return Q == h; + var pe = -1, ke = !0, ae = $ & a ? new Wt() : void 0; + for (z.set(l, h), z.set(h, l); ++pe < X; ) { + var he = l[pe], ue = h[pe]; + if (T) + var ht = Y ? T(ue, he, pe, h, l, z) : T(he, ue, pe, l, h, z); + if (ht !== void 0) { + if (ht) + continue; + ke = !1; + break; + } + if (ae) { + if (!cn(h, function(it, Lt) { + if (!jt(ae, Lt) && (he === it || K(he, it, $, T, z))) + return ae.push(Lt); + })) { + ke = !1; + break; + } + } else if (!(he === ue || K(he, ue, $, T, z))) { + ke = !1; + break; + } + } + return z.delete(l), z.delete(h), ke; + } + function Fr(l, h, $, T, K, z, Y) { + switch ($) { + case At: + if (l.byteLength != h.byteLength || l.byteOffset != h.byteOffset) + return !1; + l = l.buffer, h = h.buffer; + case Ct: + return !(l.byteLength != h.byteLength || !z(new mn(l), new mn(h))); + case g: + case k: + case S: + return Cn(+l, +h); + case y: + return l.name == h.name && l.message == h.message; + case M: + case Z: + return l == h + ""; + case E: + var X = fn; + case G: + var re = T & i; + if (X || (X = Ge), l.size != h.size && !re) + return !1; + var Q = Y.get(l); + if (Q) + return Q == h; + T |= a, Y.set(l, h); + var pe = Ns(X(l), X(h), T, K, z, Y); + return Y.delete(l), pe; + case te: + if (Ss) + return Ss.call(l) == Ss.call(h); + } + return !1; + } + function Ur(l, h, $, T, K, z) { + var Y = $ & i, X = It(l), re = X.length, Q = It(h), pe = Q.length; + if (re != pe && !Y) + return !1; + for (var ke = re; ke--; ) { + var ae = X[ke]; + if (!(Y ? ae in h : Ee.call(h, ae))) + return !1; + } + var he = z.get(l); + if (he && z.get(h)) + return he == h; + var ue = !0; + z.set(l, h), z.set(h, l); + for (var ht = Y; ++ke < re; ) { + ae = X[ke]; + var it = l[ae], Lt = h[ae]; + if (T) + var aa = Y ? T(Lt, it, ae, h, l, z) : T(it, Lt, ae, l, h, z); + if (!(aa === void 0 ? it === Lt || K(it, Lt, $, T, z) : aa)) { + ue = !1; + break; + } + ht || (ht = ae == "constructor"); + } + if (ue && !ht) { + var _n = l.constructor, Nn = h.constructor; + _n != Nn && "constructor" in l && "constructor" in h && !(typeof _n == "function" && _n instanceof _n && typeof Nn == "function" && Nn instanceof Nn) && (ue = !1); + } + return z.delete(l), z.delete(h), ue; + } + function It(l) { + return _s(l, qs, jr); + } + function Pe(l, h) { + var $ = l.__data__; + return Hr(h) ? $[typeof h == "string" ? "string" : "hash"] : $.map; + } + function nt(l, h) { + var $ = bs(l, h); + return Rr($) ? $ : void 0; + } + function rt(l) { + var h = Ee.call(l, Oe), $ = l[Oe]; + try { + l[Oe] = void 0; + var T = !0; + } catch { + } + var K = gn.call(l); + return T && (h ? l[Oe] = $ : delete l[Oe]), K; + } + var jr = Kt ? function(l) { + return l == null ? [] : (l = Object(l), dn(Kt(l), function(h) { + return Gt.call(l, h); + })); + } : Wr, We = Nt; + (ws && We(new ws(new ArrayBuffer(1))) != At || _t && We(new _t()) != E || Cs && We(Cs.resolve()) != D || As && We(new As()) != G || Es && We(new Es()) != ft) && (We = function(l) { + var h = Nt(l), $ = h == O ? l.constructor : void 0, T = $ ? ve($) : ""; + if (T) + switch (T) { + case vn: + return At; + case st: + return E; + case fr: + return D; + case pr: + return G; + case hr: + return ft; + } + return h; + }); + function Vr(l, h) { + return h = h ?? n, !!h && (typeof l == "number" || rn.test(l)) && l > -1 && l % 1 == 0 && l < h; + } + function Hr(l) { + var h = typeof l; + return h == "string" || h == "number" || h == "symbol" || h == "boolean" ? l !== "__proto__" : l === null; + } + function zr(l) { + return !!hn && hn in l; + } + function Gr(l) { + var h = l && l.constructor, $ = typeof h == "function" && h.prototype || Et; + return l === $; + } + function wn(l) { + return gn.call(l); + } + function ve(l) { + if (l != null) { + try { + return ys.call(l); + } catch { + } + try { + return l + ""; + } catch { + } + } + return ""; + } + function Cn(l, h) { + return l === h || l !== l && h !== h; + } + var An = $n(/* @__PURE__ */ function() { + return arguments; + }()) ? $n : function(l) { + return Ze(l) && Ee.call(l, "callee") && !Gt.call(l, "callee"); + }, Yt = Array.isArray; + function Is(l) { + return l != null && Ls(l.length) && !En(l); + } + var Qt = ks || Zr; + function Kr(l, h) { + return kn(l, h); + } + function En(l) { + if (!Sn(l)) + return !1; + var h = Nt(l); + return h == b || h == v || h == p || h == U; + } + function Ls(l) { + return typeof l == "number" && l > -1 && l % 1 == 0 && l <= n; + } + function Sn(l) { + var h = typeof l; + return l != null && (h == "object" || h == "function"); + } + function Ze(l) { + return l != null && typeof l == "object"; + } + var Tn = ms ? ur(ms) : Br; + function qs(l) { + return Is(l) ? Pr(l) : Mr(l); + } + function Wr() { + return []; + } + function Zr() { + return !1; + } + t.exports = Kr; +})(Hn, Hn.exports); +var Xo = Hn.exports, Zi = {}; +Object.defineProperty(Zi, "__esModule", { value: !0 }); +const Lk = Zo, qk = Xo; +var wi; +(function(t) { + function e(a = {}, n = {}, o = !1) { + typeof a != "object" && (a = {}), typeof n != "object" && (n = {}); + let u = Lk(n); + o || (u = Object.keys(u).reduce((p, g) => (u[g] != null && (p[g] = u[g]), p), {})); + for (const p in a) + a[p] !== void 0 && n[p] === void 0 && (u[p] = a[p]); + return Object.keys(u).length > 0 ? u : void 0; + } + t.compose = e; + function s(a = {}, n = {}) { + typeof a != "object" && (a = {}), typeof n != "object" && (n = {}); + const o = Object.keys(a).concat(Object.keys(n)).reduce((u, p) => (qk(a[p], n[p]) || (u[p] = n[p] === void 0 ? null : n[p]), u), {}); + return Object.keys(o).length > 0 ? o : void 0; + } + t.diff = s; + function r(a = {}, n = {}) { + a = a || {}; + const o = Object.keys(n).reduce((u, p) => (n[p] !== a[p] && a[p] !== void 0 && (u[p] = n[p]), u), {}); + return Object.keys(a).reduce((u, p) => (a[p] !== n[p] && n[p] === void 0 && (u[p] = null), u), o); + } + t.invert = r; + function i(a, n, o = !1) { + if (typeof a != "object") + return n; + if (typeof n != "object") + return; + if (!o) + return n; + const u = Object.keys(n).reduce((p, g) => (a[g] === void 0 && (p[g] = n[g]), p), {}); + return Object.keys(u).length > 0 ? u : void 0; + } + t.transform = i; +})(wi || (wi = {})); +Zi.default = wi; +var Qn = {}; +Object.defineProperty(Qn, "__esModule", { value: !0 }); +var Ci; +(function(t) { + function e(s) { + return typeof s.delete == "number" ? s.delete : typeof s.retain == "number" ? s.retain : typeof s.retain == "object" && s.retain !== null ? 1 : typeof s.insert == "string" ? s.insert.length : 1; + } + t.length = e; +})(Ci || (Ci = {})); +Qn.default = Ci; +var Xi = {}; +Object.defineProperty(Xi, "__esModule", { value: !0 }); +const Fa = Qn; +class Ok { + constructor(e) { + this.ops = e, this.index = 0, this.offset = 0; + } + hasNext() { + return this.peekLength() < 1 / 0; + } + next(e) { + e || (e = 1 / 0); + const s = this.ops[this.index]; + if (s) { + const r = this.offset, i = Fa.default.length(s); + if (e >= i - r ? (e = i - r, this.index += 1, this.offset = 0) : this.offset += e, typeof s.delete == "number") + return { delete: e }; + { + const a = {}; + return s.attributes && (a.attributes = s.attributes), typeof s.retain == "number" ? a.retain = e : typeof s.retain == "object" && s.retain !== null ? a.retain = s.retain : typeof s.insert == "string" ? a.insert = s.insert.substr(r, e) : a.insert = s.insert, a; + } + } else + return { retain: 1 / 0 }; + } + peek() { + return this.ops[this.index]; + } + peekLength() { + return this.ops[this.index] ? Fa.default.length(this.ops[this.index]) - this.offset : 1 / 0; + } + peekType() { + const e = this.ops[this.index]; + return e ? typeof e.delete == "number" ? "delete" : typeof e.retain == "number" || typeof e.retain == "object" && e.retain !== null ? "retain" : "insert" : "retain"; + } + rest() { + if (this.hasNext()) { + if (this.offset === 0) + return this.ops.slice(this.index); + { + const e = this.offset, s = this.index, r = this.next(), i = this.ops.slice(this.index); + return this.offset = e, this.index = s, [r].concat(i); + } + } else return []; + } +} +Xi.default = Ok; +(function(t, e) { + Object.defineProperty(e, "__esModule", { value: !0 }), e.AttributeMap = e.OpIterator = e.Op = void 0; + const s = Ik, r = Zo, i = Xo, a = Zi; + e.AttributeMap = a.default; + const n = Qn; + e.Op = n.default; + const o = Xi; + e.OpIterator = o.default; + const u = "\0", p = (k, y) => { + if (typeof k != "object" || k === null) + throw new Error(`cannot retain a ${typeof k}`); + if (typeof y != "object" || y === null) + throw new Error(`cannot retain a ${typeof y}`); + const b = Object.keys(k)[0]; + if (!b || b !== Object.keys(y)[0]) + throw new Error(`embed types not matched: ${b} != ${Object.keys(y)[0]}`); + return [b, k[b], y[b]]; + }; + class g { + constructor(y) { + Array.isArray(y) ? this.ops = y : y != null && Array.isArray(y.ops) ? this.ops = y.ops : this.ops = []; + } + static registerEmbed(y, b) { + this.handlers[y] = b; + } + static unregisterEmbed(y) { + delete this.handlers[y]; + } + static getHandler(y) { + const b = this.handlers[y]; + if (!b) + throw new Error(`no handlers for embed type "${y}"`); + return b; + } + insert(y, b) { + const v = {}; + return typeof y == "string" && y.length === 0 ? this : (v.insert = y, b != null && typeof b == "object" && Object.keys(b).length > 0 && (v.attributes = b), this.push(v)); + } + delete(y) { + return y <= 0 ? this : this.push({ delete: y }); + } + retain(y, b) { + if (typeof y == "number" && y <= 0) + return this; + const v = { retain: y }; + return b != null && typeof b == "object" && Object.keys(b).length > 0 && (v.attributes = b), this.push(v); + } + push(y) { + let b = this.ops.length, v = this.ops[b - 1]; + if (y = r(y), typeof v == "object") { + if (typeof y.delete == "number" && typeof v.delete == "number") + return this.ops[b - 1] = { delete: v.delete + y.delete }, this; + if (typeof v.delete == "number" && y.insert != null && (b -= 1, v = this.ops[b - 1], typeof v != "object")) + return this.ops.unshift(y), this; + if (i(y.attributes, v.attributes)) { + if (typeof y.insert == "string" && typeof v.insert == "string") + return this.ops[b - 1] = { insert: v.insert + y.insert }, typeof y.attributes == "object" && (this.ops[b - 1].attributes = y.attributes), this; + if (typeof y.retain == "number" && typeof v.retain == "number") + return this.ops[b - 1] = { retain: v.retain + y.retain }, typeof y.attributes == "object" && (this.ops[b - 1].attributes = y.attributes), this; + } + } + return b === this.ops.length ? this.ops.push(y) : this.ops.splice(b, 0, y), this; + } + chop() { + const y = this.ops[this.ops.length - 1]; + return y && typeof y.retain == "number" && !y.attributes && this.ops.pop(), this; + } + filter(y) { + return this.ops.filter(y); + } + forEach(y) { + this.ops.forEach(y); + } + map(y) { + return this.ops.map(y); + } + partition(y) { + const b = [], v = []; + return this.forEach((E) => { + (y(E) ? b : v).push(E); + }), [b, v]; + } + reduce(y, b) { + return this.ops.reduce(y, b); + } + changeLength() { + return this.reduce((y, b) => b.insert ? y + n.default.length(b) : b.delete ? y - b.delete : y, 0); + } + length() { + return this.reduce((y, b) => y + n.default.length(b), 0); + } + slice(y = 0, b = 1 / 0) { + const v = [], E = new o.default(this.ops); + let S = 0; + for (; S < b && E.hasNext(); ) { + let _; + S < y ? _ = E.next(y - S) : (_ = E.next(b - S), v.push(_)), S += n.default.length(_); + } + return new g(v); + } + compose(y) { + const b = new o.default(this.ops), v = new o.default(y.ops), E = [], S = v.peek(); + if (S != null && typeof S.retain == "number" && S.attributes == null) { + let O = S.retain; + for (; b.peekType() === "insert" && b.peekLength() <= O; ) + O -= b.peekLength(), E.push(b.next()); + S.retain - O > 0 && v.next(S.retain - O); + } + const _ = new g(E); + for (; b.hasNext() || v.hasNext(); ) + if (v.peekType() === "insert") + _.push(v.next()); + else if (b.peekType() === "delete") + _.push(b.next()); + else { + const O = Math.min(b.peekLength(), v.peekLength()), D = b.next(O), U = v.next(O); + if (U.retain) { + const M = {}; + if (typeof D.retain == "number") + M.retain = typeof U.retain == "number" ? O : U.retain; + else if (typeof U.retain == "number") + D.retain == null ? M.insert = D.insert : M.retain = D.retain; + else { + const Z = D.retain == null ? "insert" : "retain", [te, tt, ft] = p(D[Z], U.retain), Ct = g.getHandler(te); + M[Z] = { + [te]: Ct.compose(tt, ft, Z === "retain") + }; + } + const G = a.default.compose(D.attributes, U.attributes, typeof D.retain == "number"); + if (G && (M.attributes = G), _.push(M), !v.hasNext() && i(_.ops[_.ops.length - 1], M)) { + const Z = new g(b.rest()); + return _.concat(Z).chop(); + } + } else typeof U.delete == "number" && (typeof D.retain == "number" || typeof D.retain == "object" && D.retain !== null) && _.push(U); + } + return _.chop(); + } + concat(y) { + const b = new g(this.ops.slice()); + return y.ops.length > 0 && (b.push(y.ops[0]), b.ops = b.ops.concat(y.ops.slice(1))), b; + } + diff(y, b) { + if (this.ops === y.ops) + return new g(); + const v = [this, y].map((D) => D.map((U) => { + if (U.insert != null) + return typeof U.insert == "string" ? U.insert : u; + const M = D === y ? "on" : "with"; + throw new Error("diff() called " + M + " non-document"); + }).join("")), E = new g(), S = s(v[0], v[1], b, !0), _ = new o.default(this.ops), O = new o.default(y.ops); + return S.forEach((D) => { + let U = D[1].length; + for (; U > 0; ) { + let M = 0; + switch (D[0]) { + case s.INSERT: + M = Math.min(O.peekLength(), U), E.push(O.next(M)); + break; + case s.DELETE: + M = Math.min(U, _.peekLength()), _.next(M), E.delete(M); + break; + case s.EQUAL: + M = Math.min(_.peekLength(), O.peekLength(), U); + const G = _.next(M), Z = O.next(M); + i(G.insert, Z.insert) ? E.retain(M, a.default.diff(G.attributes, Z.attributes)) : E.push(Z).delete(M); + break; + } + U -= M; + } + }), E.chop(); + } + eachLine(y, b = ` +`) { + const v = new o.default(this.ops); + let E = new g(), S = 0; + for (; v.hasNext(); ) { + if (v.peekType() !== "insert") + return; + const _ = v.peek(), O = n.default.length(_) - v.peekLength(), D = typeof _.insert == "string" ? _.insert.indexOf(b, O) - O : -1; + if (D < 0) + E.push(v.next()); + else if (D > 0) + E.push(v.next(D)); + else { + if (y(E, v.next(1).attributes || {}, S) === !1) + return; + S += 1, E = new g(); + } + } + E.length() > 0 && y(E, {}, S); + } + invert(y) { + const b = new g(); + return this.reduce((v, E) => { + if (E.insert) + b.delete(n.default.length(E)); + else { + if (typeof E.retain == "number" && E.attributes == null) + return b.retain(E.retain), v + E.retain; + if (E.delete || typeof E.retain == "number") { + const S = E.delete || E.retain; + return y.slice(v, v + S).forEach((O) => { + E.delete ? b.push(O) : E.retain && E.attributes && b.retain(n.default.length(O), a.default.invert(E.attributes, O.attributes)); + }), v + S; + } else if (typeof E.retain == "object" && E.retain !== null) { + const S = y.slice(v, v + 1), _ = new o.default(S.ops).next(), [O, D, U] = p(E.retain, _.insert), M = g.getHandler(O); + return b.retain({ [O]: M.invert(D, U) }, a.default.invert(E.attributes, _.attributes)), v + 1; + } + } + return v; + }, 0), b.chop(); + } + transform(y, b = !1) { + if (b = !!b, typeof y == "number") + return this.transformPosition(y, b); + const v = y, E = new o.default(this.ops), S = new o.default(v.ops), _ = new g(); + for (; E.hasNext() || S.hasNext(); ) + if (E.peekType() === "insert" && (b || S.peekType() !== "insert")) + _.retain(n.default.length(E.next())); + else if (S.peekType() === "insert") + _.push(S.next()); + else { + const O = Math.min(E.peekLength(), S.peekLength()), D = E.next(O), U = S.next(O); + if (D.delete) + continue; + if (U.delete) + _.push(U); + else { + const M = D.retain, G = U.retain; + let Z = typeof G == "object" && G !== null ? G : O; + if (typeof M == "object" && M !== null && typeof G == "object" && G !== null) { + const te = Object.keys(M)[0]; + if (te === Object.keys(G)[0]) { + const tt = g.getHandler(te); + tt && (Z = { + [te]: tt.transform(M[te], G[te], b) + }); + } + } + _.retain(Z, a.default.transform(D.attributes, U.attributes, b)); + } + } + return _.chop(); + } + transformPosition(y, b = !1) { + b = !!b; + const v = new o.default(this.ops); + let E = 0; + for (; v.hasNext() && E <= y; ) { + const S = v.peekLength(), _ = v.peekType(); + if (v.next(), _ === "delete") { + y -= Math.min(S, y - E); + continue; + } else _ === "insert" && (E < y || !b) && (y += S); + E += S; + } + return y; + } + } + g.Op = n.default, g.OpIterator = o.default, g.AttributeMap = a.default, g.handlers = {}, e.default = g, t.exports = g, t.exports.default = g; +})(ki, ki.exports); +var Ie = ki.exports; +const j = /* @__PURE__ */ Ho(Ie); +class He extends Ae { + static value() { + } + optimize() { + (this.prev || this.next) && this.remove(); + } + length() { + return 0; + } + value() { + return ""; + } +} +He.blotName = "break"; +He.tagName = "BR"; +let je = class extends jn { +}; +const Pk = { + "&": "&", + "<": "<", + ">": ">", + '"': """, + "'": "'" +}; +function Jn(t) { + return t.replace(/[&<>"']/g, (e) => Pk[e]); +} +const Xe = class Xe extends zi { + static compare(e, s) { + const r = Xe.order.indexOf(e), i = Xe.order.indexOf(s); + return r >= 0 || i >= 0 ? r - i : e === s ? 0 : e < s ? -1 : 1; + } + formatAt(e, s, r, i) { + if (Xe.compare(this.statics.blotName, r) < 0 && this.scroll.query(r, V.BLOT)) { + const a = this.isolate(e, s); + i && a.wrap(r, i); + } else + super.formatAt(e, s, r, i); + } + optimize(e) { + if (super.optimize(e), this.parent instanceof Xe && Xe.compare(this.statics.blotName, this.parent.statics.blotName) > 0) { + const s = this.parent.isolate(this.offset(), this.length()); + this.moveChildren(s), s.wrap(this); + } + } +}; +B(Xe, "allowedChildren", [Xe, He, Ae, je]), // Lower index means deeper in the DOM tree, since not found (-1) is for embeds +B(Xe, "order", [ + "cursor", + "inline", + // Must be lower + "link", + // Chrome wants to be lower + "underline", + "strike", + "italic", + "bold", + "script", + "code" + // Must be higher +]); +let xe = Xe; +const Ua = 1; +class ce extends Gs { + constructor() { + super(...arguments); + B(this, "cache", {}); + } + delta() { + return this.cache.delta == null && (this.cache.delta = Yo(this)), this.cache.delta; + } + deleteAt(s, r) { + super.deleteAt(s, r), this.cache = {}; + } + formatAt(s, r, i, a) { + r <= 0 || (this.scroll.query(i, V.BLOCK) ? s + r === this.length() && this.format(i, a) : super.formatAt(s, Math.min(r, this.length() - s - 1), i, a), this.cache = {}); + } + insertAt(s, r, i) { + if (i != null) { + super.insertAt(s, r, i), this.cache = {}; + return; + } + if (r.length === 0) return; + const a = r.split(` +`), n = a.shift(); + n.length > 0 && (s < this.length() - 1 || this.children.tail == null ? super.insertAt(Math.min(s, this.length() - 1), n) : this.children.tail.insertAt(this.children.tail.length(), n), this.cache = {}); + let o = this; + a.reduce((u, p) => (o = o.split(u, !0), o.insertAt(0, p), p.length), s + n.length); + } + insertBefore(s, r) { + const { + head: i + } = this.children; + super.insertBefore(s, r), i instanceof He && i.remove(), this.cache = {}; + } + length() { + return this.cache.length == null && (this.cache.length = super.length() + Ua), this.cache.length; + } + moveChildren(s, r) { + super.moveChildren(s, r), this.cache = {}; + } + optimize(s) { + super.optimize(s), this.cache = {}; + } + path(s) { + return super.path(s, !0); + } + removeChild(s) { + super.removeChild(s), this.cache = {}; + } + split(s) { + let r = arguments.length > 1 && arguments[1] !== void 0 ? arguments[1] : !1; + if (r && (s === 0 || s >= this.length() - Ua)) { + const a = this.clone(); + return s === 0 ? (this.parent.insertBefore(a, this), this) : (this.parent.insertBefore(a, this.next), a); + } + const i = super.split(s, r); + return this.cache = {}, i; + } +} +ce.blotName = "block"; +ce.tagName = "P"; +ce.defaultChild = He; +ce.allowedChildren = [He, xe, Ae, je]; +class Ne extends Ae { + attach() { + super.attach(), this.attributes = new Wn(this.domNode); + } + delta() { + return new j().insert(this.value(), { + ...this.formats(), + ...this.attributes.values() + }); + } + format(e, s) { + const r = this.scroll.query(e, V.BLOCK_ATTRIBUTE); + r != null && this.attributes.attribute(r, s); + } + formatAt(e, s, r, i) { + this.format(r, i); + } + insertAt(e, s, r) { + if (r != null) { + super.insertAt(e, s, r); + return; + } + const i = s.split(` +`), a = i.pop(), n = i.map((u) => { + const p = this.scroll.create(ce.blotName); + return p.insertAt(0, u), p; + }), o = this.split(e); + n.forEach((u) => { + this.parent.insertBefore(u, o); + }), a && this.parent.insertBefore(this.scroll.create("text", a), o); + } +} +Ne.scope = V.BLOCK_BLOT; +function Yo(t) { + let e = arguments.length > 1 && arguments[1] !== void 0 ? arguments[1] : !0; + return t.descendants(ge).reduce((s, r) => r.length() === 0 ? s : s.insert(r.value(), Te(r, {}, e)), new j()).insert(` +`, Te(t)); +} +function Te(t) { + let e = arguments.length > 1 && arguments[1] !== void 0 ? arguments[1] : {}, s = arguments.length > 2 && arguments[2] !== void 0 ? arguments[2] : !0; + return t == null || ("formats" in t && typeof t.formats == "function" && (e = { + ...e, + ...t.formats() + }, s && delete e["code-token"]), t.parent == null || t.parent.statics.blotName === "scroll" || t.parent.statics.scope !== t.statics.scope) ? e : Te(t.parent, e, s); +} +const Se = class Se extends Ae { + // Zero width no break space + static value() { + } + constructor(e, s, r) { + super(e, s), this.selection = r, this.textNode = document.createTextNode(Se.CONTENTS), this.domNode.appendChild(this.textNode), this.savedLength = 0; + } + detach() { + this.parent != null && this.parent.removeChild(this); + } + format(e, s) { + if (this.savedLength !== 0) { + super.format(e, s); + return; + } + let r = this, i = 0; + for (; r != null && r.statics.scope !== V.BLOCK_BLOT; ) + i += r.offset(r.parent), r = r.parent; + r != null && (this.savedLength = Se.CONTENTS.length, r.optimize(), r.formatAt(i, Se.CONTENTS.length, e, s), this.savedLength = 0); + } + index(e, s) { + return e === this.textNode ? 0 : super.index(e, s); + } + length() { + return this.savedLength; + } + position() { + return [this.textNode, this.textNode.data.length]; + } + remove() { + super.remove(), this.parent = null; + } + restore() { + if (this.selection.composing || this.parent == null) return null; + const e = this.selection.getNativeRange(); + for (; this.domNode.lastChild != null && this.domNode.lastChild !== this.textNode; ) + this.domNode.parentNode.insertBefore(this.domNode.lastChild, this.domNode); + const s = this.prev instanceof je ? this.prev : null, r = s ? s.length() : 0, i = this.next instanceof je ? this.next : null, a = i ? i.text : "", { + textNode: n + } = this, o = n.data.split(Se.CONTENTS).join(""); + n.data = Se.CONTENTS; + let u; + if (s) + u = s, (o || i) && (s.insertAt(s.length(), o + a), i && i.remove()); + else if (i) + u = i, i.insertAt(0, o); + else { + const p = document.createTextNode(o); + u = this.scroll.create(p), this.parent.insertBefore(u, this); + } + if (this.remove(), e) { + const p = (y, b) => s && y === s.domNode ? b : y === n ? r + b - 1 : i && y === i.domNode ? r + o.length + b : null, g = p(e.start.node, e.start.offset), k = p(e.end.node, e.end.offset); + if (g !== null && k !== null) + return { + startNode: u.domNode, + startOffset: g, + endNode: u.domNode, + endOffset: k + }; + } + return null; + } + update(e, s) { + if (e.some((r) => r.type === "characterData" && r.target === this.textNode)) { + const r = this.restore(); + r && (s.range = r); + } + } + // Avoid .ql-cursor being a descendant of ``. + // The reason is Safari pushes down `` on text insertion. + // That will cause DOM nodes not sync with the model. + // + // For example ({I} is the caret), given the markup: + // \uFEFF{I} + // When typing a char "x", `` will be pushed down inside the `` first: + // \uFEFF{I} + // And then "x" will be inserted after ``: + // \uFEFFd{I} + optimize(e) { + super.optimize(e); + let { + parent: s + } = this; + for (; s; ) { + if (s.domNode.tagName === "A") { + this.savedLength = Se.CONTENTS.length, s.isolate(this.offset(s), this.length()).unwrap(), this.savedLength = 0; + break; + } + s = s.parent; + } + } + value() { + return ""; + } +}; +B(Se, "blotName", "cursor"), B(Se, "className", "ql-cursor"), B(Se, "tagName", "span"), B(Se, "CONTENTS", "\uFEFF"); +let us = Se; +var Qo = { exports: {} }; +(function(t) { + var e = Object.prototype.hasOwnProperty, s = "~"; + function r() { + } + Object.create && (r.prototype = /* @__PURE__ */ Object.create(null), new r().__proto__ || (s = !1)); + function i(u, p, g) { + this.fn = u, this.context = p, this.once = g || !1; + } + function a(u, p, g, k, y) { + if (typeof g != "function") + throw new TypeError("The listener must be a function"); + var b = new i(g, k || u, y), v = s ? s + p : p; + return u._events[v] ? u._events[v].fn ? u._events[v] = [u._events[v], b] : u._events[v].push(b) : (u._events[v] = b, u._eventsCount++), u; + } + function n(u, p) { + --u._eventsCount === 0 ? u._events = new r() : delete u._events[p]; + } + function o() { + this._events = new r(), this._eventsCount = 0; + } + o.prototype.eventNames = function() { + var p = [], g, k; + if (this._eventsCount === 0) return p; + for (k in g = this._events) + e.call(g, k) && p.push(s ? k.slice(1) : k); + return Object.getOwnPropertySymbols ? p.concat(Object.getOwnPropertySymbols(g)) : p; + }, o.prototype.listeners = function(p) { + var g = s ? s + p : p, k = this._events[g]; + if (!k) return []; + if (k.fn) return [k.fn]; + for (var y = 0, b = k.length, v = new Array(b); y < b; y++) + v[y] = k[y].fn; + return v; + }, o.prototype.listenerCount = function(p) { + var g = s ? s + p : p, k = this._events[g]; + return k ? k.fn ? 1 : k.length : 0; + }, o.prototype.emit = function(p, g, k, y, b, v) { + var E = s ? s + p : p; + if (!this._events[E]) return !1; + var S = this._events[E], _ = arguments.length, O, D; + if (S.fn) { + switch (S.once && this.removeListener(p, S.fn, void 0, !0), _) { + case 1: + return S.fn.call(S.context), !0; + case 2: + return S.fn.call(S.context, g), !0; + case 3: + return S.fn.call(S.context, g, k), !0; + case 4: + return S.fn.call(S.context, g, k, y), !0; + case 5: + return S.fn.call(S.context, g, k, y, b), !0; + case 6: + return S.fn.call(S.context, g, k, y, b, v), !0; + } + for (D = 1, O = new Array(_ - 1); D < _; D++) + O[D - 1] = arguments[D]; + S.fn.apply(S.context, O); + } else { + var U = S.length, M; + for (D = 0; D < U; D++) + switch (S[D].once && this.removeListener(p, S[D].fn, void 0, !0), _) { + case 1: + S[D].fn.call(S[D].context); + break; + case 2: + S[D].fn.call(S[D].context, g); + break; + case 3: + S[D].fn.call(S[D].context, g, k); + break; + case 4: + S[D].fn.call(S[D].context, g, k, y); + break; + default: + if (!O) for (M = 1, O = new Array(_ - 1); M < _; M++) + O[M - 1] = arguments[M]; + S[D].fn.apply(S[D].context, O); + } + } + return !0; + }, o.prototype.on = function(p, g, k) { + return a(this, p, g, k, !1); + }, o.prototype.once = function(p, g, k) { + return a(this, p, g, k, !0); + }, o.prototype.removeListener = function(p, g, k, y) { + var b = s ? s + p : p; + if (!this._events[b]) return this; + if (!g) + return n(this, b), this; + var v = this._events[b]; + if (v.fn) + v.fn === g && (!y || v.once) && (!k || v.context === k) && n(this, b); + else { + for (var E = 0, S = [], _ = v.length; E < _; E++) + (v[E].fn !== g || y && !v[E].once || k && v[E].context !== k) && S.push(v[E]); + S.length ? this._events[b] = S.length === 1 ? S[0] : S : n(this, b); + } + return this; + }, o.prototype.removeAllListeners = function(p) { + var g; + return p ? (g = s ? s + p : p, this._events[g] && n(this, g)) : (this._events = new r(), this._eventsCount = 0), this; + }, o.prototype.off = o.prototype.removeListener, o.prototype.addListener = o.prototype.on, o.prefixed = s, o.EventEmitter = o, t.exports = o; +})(Qo); +var Dk = Qo.exports; +const Rk = /* @__PURE__ */ Ho(Dk), Ai = /* @__PURE__ */ new WeakMap(), Ei = ["error", "warn", "log", "info"]; +let Si = "warn"; +function Jo(t) { + if (Si && Ei.indexOf(t) <= Ei.indexOf(Si)) { + for (var e = arguments.length, s = new Array(e > 1 ? e - 1 : 0), r = 1; r < e; r++) + s[r - 1] = arguments[r]; + console[t](...s); + } +} +function ct(t) { + return Ei.reduce((e, s) => (e[s] = Jo.bind(console, s, t), e), {}); +} +ct.level = (t) => { + Si = t; +}; +Jo.level = ct.level; +const ei = ct("quill:events"), Bk = ["selectionchange", "mousedown", "mouseup", "click"]; +Bk.forEach((t) => { + document.addEventListener(t, function() { + for (var e = arguments.length, s = new Array(e), r = 0; r < e; r++) + s[r] = arguments[r]; + Array.from(document.querySelectorAll(".ql-container")).forEach((i) => { + const a = Ai.get(i); + a && a.emitter && a.emitter.handleDOM(...s); + }); + }); +}); +class F extends Rk { + constructor() { + super(), this.domListeners = {}, this.on("error", ei.error); + } + emit() { + for (var e = arguments.length, s = new Array(e), r = 0; r < e; r++) + s[r] = arguments[r]; + return ei.log.call(ei, ...s), super.emit(...s); + } + handleDOM(e) { + for (var s = arguments.length, r = new Array(s > 1 ? s - 1 : 0), i = 1; i < s; i++) + r[i - 1] = arguments[i]; + (this.domListeners[e.type] || []).forEach((a) => { + let { + node: n, + handler: o + } = a; + (e.target === n || n.contains(e.target)) && o(e, ...r); + }); + } + listenDOM(e, s, r) { + this.domListeners[e] || (this.domListeners[e] = []), this.domListeners[e].push({ + node: s, + handler: r + }); + } +} +B(F, "events", { + EDITOR_CHANGE: "editor-change", + SCROLL_BEFORE_UPDATE: "scroll-before-update", + SCROLL_BLOT_MOUNT: "scroll-blot-mount", + SCROLL_BLOT_UNMOUNT: "scroll-blot-unmount", + SCROLL_OPTIMIZE: "scroll-optimize", + SCROLL_UPDATE: "scroll-update", + SCROLL_EMBED_UPDATE: "scroll-embed-update", + SELECTION_CHANGE: "selection-change", + TEXT_CHANGE: "text-change", + COMPOSITION_BEFORE_START: "composition-before-start", + COMPOSITION_START: "composition-start", + COMPOSITION_BEFORE_END: "composition-before-end", + COMPOSITION_END: "composition-end" +}), B(F, "sources", { + API: "api", + SILENT: "silent", + USER: "user" +}); +const ti = ct("quill:selection"); +class Pt { + constructor(e) { + let s = arguments.length > 1 && arguments[1] !== void 0 ? arguments[1] : 0; + this.index = e, this.length = s; + } +} +class Mk { + constructor(e, s) { + this.emitter = s, this.scroll = e, this.composing = !1, this.mouseDown = !1, this.root = this.scroll.domNode, this.cursor = this.scroll.create("cursor", this), this.savedRange = new Pt(0, 0), this.lastRange = this.savedRange, this.lastNative = null, this.handleComposition(), this.handleDragging(), this.emitter.listenDOM("selectionchange", document, () => { + !this.mouseDown && !this.composing && setTimeout(this.update.bind(this, F.sources.USER), 1); + }), this.emitter.on(F.events.SCROLL_BEFORE_UPDATE, () => { + if (!this.hasFocus()) return; + const r = this.getNativeRange(); + r != null && r.start.node !== this.cursor.textNode && this.emitter.once(F.events.SCROLL_UPDATE, (i, a) => { + try { + this.root.contains(r.start.node) && this.root.contains(r.end.node) && this.setNativeRange(r.start.node, r.start.offset, r.end.node, r.end.offset); + const n = a.some((o) => o.type === "characterData" || o.type === "childList" || o.type === "attributes" && o.target === this.root); + this.update(n ? F.sources.SILENT : i); + } catch { + } + }); + }), this.emitter.on(F.events.SCROLL_OPTIMIZE, (r, i) => { + if (i.range) { + const { + startNode: a, + startOffset: n, + endNode: o, + endOffset: u + } = i.range; + this.setNativeRange(a, n, o, u), this.update(F.sources.SILENT); + } + }), this.update(F.sources.SILENT); + } + handleComposition() { + this.emitter.on(F.events.COMPOSITION_BEFORE_START, () => { + this.composing = !0; + }), this.emitter.on(F.events.COMPOSITION_END, () => { + if (this.composing = !1, this.cursor.parent) { + const e = this.cursor.restore(); + if (!e) return; + setTimeout(() => { + this.setNativeRange(e.startNode, e.startOffset, e.endNode, e.endOffset); + }, 1); + } + }); + } + handleDragging() { + this.emitter.listenDOM("mousedown", document.body, () => { + this.mouseDown = !0; + }), this.emitter.listenDOM("mouseup", document.body, () => { + this.mouseDown = !1, this.update(F.sources.USER); + }); + } + focus() { + this.hasFocus() || (this.root.focus({ + preventScroll: !0 + }), this.setRange(this.savedRange)); + } + format(e, s) { + this.scroll.update(); + const r = this.getNativeRange(); + if (!(r == null || !r.native.collapsed || this.scroll.query(e, V.BLOCK))) { + if (r.start.node !== this.cursor.textNode) { + const i = this.scroll.find(r.start.node, !1); + if (i == null) return; + if (i instanceof ge) { + const a = i.split(r.start.offset); + i.parent.insertBefore(this.cursor, a); + } else + i.insertBefore(this.cursor, r.start.node); + this.cursor.attach(); + } + this.cursor.format(e, s), this.scroll.optimize(), this.setNativeRange(this.cursor.textNode, this.cursor.textNode.data.length), this.update(); + } + } + getBounds(e) { + let s = arguments.length > 1 && arguments[1] !== void 0 ? arguments[1] : 0; + const r = this.scroll.length(); + e = Math.min(e, r - 1), s = Math.min(e + s, r - 1) - e; + let i, [a, n] = this.scroll.leaf(e); + if (a == null) return null; + if (s > 0 && n === a.length()) { + const [g] = this.scroll.leaf(e + 1); + if (g) { + const [k] = this.scroll.line(e), [y] = this.scroll.line(e + 1); + k === y && (a = g, n = 0); + } + } + [i, n] = a.position(n, !0); + const o = document.createRange(); + if (s > 0) + return o.setStart(i, n), [a, n] = this.scroll.leaf(e + s), a == null ? null : ([i, n] = a.position(n, !0), o.setEnd(i, n), o.getBoundingClientRect()); + let u = "left", p; + if (i instanceof Text) { + if (!i.data.length) + return null; + n < i.data.length ? (o.setStart(i, n), o.setEnd(i, n + 1)) : (o.setStart(i, n - 1), o.setEnd(i, n), u = "right"), p = o.getBoundingClientRect(); + } else { + if (!(a.domNode instanceof Element)) return null; + p = a.domNode.getBoundingClientRect(), n > 0 && (u = "right"); + } + return { + bottom: p.top + p.height, + height: p.height, + left: p[u], + right: p[u], + top: p.top, + width: 0 + }; + } + getNativeRange() { + const e = document.getSelection(); + if (e == null || e.rangeCount <= 0) return null; + const s = e.getRangeAt(0); + if (s == null) return null; + const r = this.normalizeNative(s); + return ti.info("getNativeRange", r), r; + } + getRange() { + const e = this.scroll.domNode; + if ("isConnected" in e && !e.isConnected) + return [null, null]; + const s = this.getNativeRange(); + return s == null ? [null, null] : [this.normalizedToRange(s), s]; + } + hasFocus() { + return document.activeElement === this.root || document.activeElement != null && si(this.root, document.activeElement); + } + normalizedToRange(e) { + const s = [[e.start.node, e.start.offset]]; + e.native.collapsed || s.push([e.end.node, e.end.offset]); + const r = s.map((n) => { + const [o, u] = n, p = this.scroll.find(o, !0), g = p.offset(this.scroll); + return u === 0 ? g : p instanceof ge ? g + p.index(o, u) : g + p.length(); + }), i = Math.min(Math.max(...r), this.scroll.length() - 1), a = Math.min(i, ...r); + return new Pt(a, i - a); + } + normalizeNative(e) { + if (!si(this.root, e.startContainer) || !e.collapsed && !si(this.root, e.endContainer)) + return null; + const s = { + start: { + node: e.startContainer, + offset: e.startOffset + }, + end: { + node: e.endContainer, + offset: e.endOffset + }, + native: e + }; + return [s.start, s.end].forEach((r) => { + let { + node: i, + offset: a + } = r; + for (; !(i instanceof Text) && i.childNodes.length > 0; ) + if (i.childNodes.length > a) + i = i.childNodes[a], a = 0; + else if (i.childNodes.length === a) + i = i.lastChild, i instanceof Text ? a = i.data.length : i.childNodes.length > 0 ? a = i.childNodes.length : a = i.childNodes.length + 1; + else + break; + r.node = i, r.offset = a; + }), s; + } + rangeToNative(e) { + const s = this.scroll.length(), r = (i, a) => { + i = Math.min(s - 1, i); + const [n, o] = this.scroll.leaf(i); + return n ? n.position(o, a) : [null, -1]; + }; + return [...r(e.index, !1), ...r(e.index + e.length, !0)]; + } + setNativeRange(e, s) { + let r = arguments.length > 2 && arguments[2] !== void 0 ? arguments[2] : e, i = arguments.length > 3 && arguments[3] !== void 0 ? arguments[3] : s, a = arguments.length > 4 && arguments[4] !== void 0 ? arguments[4] : !1; + if (ti.info("setNativeRange", e, s, r, i), e != null && (this.root.parentNode == null || e.parentNode == null || // @ts-expect-error Fix me later + r.parentNode == null)) + return; + const n = document.getSelection(); + if (n != null) + if (e != null) { + this.hasFocus() || this.root.focus({ + preventScroll: !0 + }); + const { + native: o + } = this.getNativeRange() || {}; + if (o == null || a || e !== o.startContainer || s !== o.startOffset || r !== o.endContainer || i !== o.endOffset) { + e instanceof Element && e.tagName === "BR" && (s = Array.from(e.parentNode.childNodes).indexOf(e), e = e.parentNode), r instanceof Element && r.tagName === "BR" && (i = Array.from(r.parentNode.childNodes).indexOf(r), r = r.parentNode); + const u = document.createRange(); + u.setStart(e, s), u.setEnd(r, i), n.removeAllRanges(), n.addRange(u); + } + } else + n.removeAllRanges(), this.root.blur(); + } + setRange(e) { + let s = arguments.length > 1 && arguments[1] !== void 0 ? arguments[1] : !1, r = arguments.length > 2 && arguments[2] !== void 0 ? arguments[2] : F.sources.API; + if (typeof s == "string" && (r = s, s = !1), ti.info("setRange", e), e != null) { + const i = this.rangeToNative(e); + this.setNativeRange(...i, s); + } else + this.setNativeRange(null); + this.update(r); + } + update() { + let e = arguments.length > 0 && arguments[0] !== void 0 ? arguments[0] : F.sources.USER; + const s = this.lastRange, [r, i] = this.getRange(); + if (this.lastRange = r, this.lastNative = i, this.lastRange != null && (this.savedRange = this.lastRange), !Hi(s, this.lastRange)) { + if (!this.composing && i != null && i.native.collapsed && i.start.node !== this.cursor.textNode) { + const n = this.cursor.restore(); + n && this.setNativeRange(n.startNode, n.startOffset, n.endNode, n.endOffset); + } + const a = [F.events.SELECTION_CHANGE, rs(this.lastRange), rs(s), e]; + this.emitter.emit(F.events.EDITOR_CHANGE, ...a), e !== F.sources.SILENT && this.emitter.emit(...a); + } + } +} +function si(t, e) { + try { + e.parentNode; + } catch { + return !1; + } + return t.contains(e); +} +const Fk = /^[ -~]*$/; +class Uk { + constructor(e) { + this.scroll = e, this.delta = this.getDelta(); + } + applyDelta(e) { + this.scroll.update(); + let s = this.scroll.length(); + this.scroll.batchStart(); + const r = ja(e), i = new j(); + return Vk(r.ops.slice()).reduce((n, o) => { + const u = Ie.Op.length(o); + let p = o.attributes || {}, g = !1, k = !1; + if (o.insert != null) { + if (i.retain(u), typeof o.insert == "string") { + const v = o.insert; + k = !v.endsWith(` +`) && (s <= n || !!this.scroll.descendant(Ne, n)[0]), this.scroll.insertAt(n, v); + const [E, S] = this.scroll.line(n); + let _ = yt({}, Te(E)); + if (E instanceof ce) { + const [O] = E.descendant(ge, S); + O && (_ = yt(_, Te(O))); + } + p = Ie.AttributeMap.diff(_, p) || {}; + } else if (typeof o.insert == "object") { + const v = Object.keys(o.insert)[0]; + if (v == null) return n; + const E = this.scroll.query(v, V.INLINE) != null; + if (E) + (s <= n || this.scroll.descendant(Ne, n)[0]) && (k = !0); + else if (n > 0) { + const [S, _] = this.scroll.descendant(ge, n - 1); + S instanceof je ? S.value()[_] !== ` +` && (g = !0) : S instanceof Ae && S.statics.scope === V.INLINE_BLOT && (g = !0); + } + if (this.scroll.insertAt(n, v, o.insert[v]), E) { + const [S] = this.scroll.descendant(ge, n); + if (S) { + const _ = yt({}, Te(S)); + p = Ie.AttributeMap.diff(_, p) || {}; + } + } + } + s += u; + } else if (i.push(o), o.retain !== null && typeof o.retain == "object") { + const v = Object.keys(o.retain)[0]; + if (v == null) return n; + this.scroll.updateEmbedAt(n, v, o.retain[v]); + } + Object.keys(p).forEach((v) => { + this.scroll.formatAt(n, u, v, p[v]); + }); + const y = g ? 1 : 0, b = k ? 1 : 0; + return s += y + b, i.retain(y), i.delete(b), n + u + y + b; + }, 0), i.reduce((n, o) => typeof o.delete == "number" ? (this.scroll.deleteAt(n, o.delete), n) : n + Ie.Op.length(o), 0), this.scroll.batchEnd(), this.scroll.optimize(), this.update(r); + } + deleteText(e, s) { + return this.scroll.deleteAt(e, s), this.update(new j().retain(e).delete(s)); + } + formatLine(e, s) { + let r = arguments.length > 2 && arguments[2] !== void 0 ? arguments[2] : {}; + this.scroll.update(), Object.keys(r).forEach((a) => { + this.scroll.lines(e, Math.max(s, 1)).forEach((n) => { + n.format(a, r[a]); + }); + }), this.scroll.optimize(); + const i = new j().retain(e).retain(s, rs(r)); + return this.update(i); + } + formatText(e, s) { + let r = arguments.length > 2 && arguments[2] !== void 0 ? arguments[2] : {}; + Object.keys(r).forEach((a) => { + this.scroll.formatAt(e, s, a, r[a]); + }); + const i = new j().retain(e).retain(s, rs(r)); + return this.update(i); + } + getContents(e, s) { + return this.delta.slice(e, e + s); + } + getDelta() { + return this.scroll.lines().reduce((e, s) => e.concat(s.delta()), new j()); + } + getFormat(e) { + let s = arguments.length > 1 && arguments[1] !== void 0 ? arguments[1] : 0, r = [], i = []; + s === 0 ? this.scroll.path(e).forEach((o) => { + const [u] = o; + u instanceof ce ? r.push(u) : u instanceof ge && i.push(u); + }) : (r = this.scroll.lines(e, s), i = this.scroll.descendants(ge, e, s)); + const [a, n] = [r, i].map((o) => { + const u = o.shift(); + if (u == null) return {}; + let p = Te(u); + for (; Object.keys(p).length > 0; ) { + const g = o.shift(); + if (g == null) return p; + p = jk(Te(g), p); + } + return p; + }); + return { + ...a, + ...n + }; + } + getHTML(e, s) { + const [r, i] = this.scroll.line(e); + if (r) { + const a = r.length(); + return r.length() >= i + s && !(i === 0 && s === a) ? Ws(r, i, s, !0) : Ws(this.scroll, e, s, !0); + } + return ""; + } + getText(e, s) { + return this.getContents(e, s).filter((r) => typeof r.insert == "string").map((r) => r.insert).join(""); + } + insertContents(e, s) { + const r = ja(s), i = new j().retain(e).concat(r); + return this.scroll.insertContents(e, r), this.update(i); + } + insertEmbed(e, s, r) { + return this.scroll.insertAt(e, s, r), this.update(new j().retain(e).insert({ + [s]: r + })); + } + insertText(e, s) { + let r = arguments.length > 2 && arguments[2] !== void 0 ? arguments[2] : {}; + return s = s.replace(/\r\n/g, ` +`).replace(/\r/g, ` +`), this.scroll.insertAt(e, s), Object.keys(r).forEach((i) => { + this.scroll.formatAt(e, s.length, i, r[i]); + }), this.update(new j().retain(e).insert(s, rs(r))); + } + isBlank() { + if (this.scroll.children.length === 0) return !0; + if (this.scroll.children.length > 1) return !1; + const e = this.scroll.children.head; + if ((e == null ? void 0 : e.statics.blotName) !== ce.blotName) return !1; + const s = e; + return s.children.length > 1 ? !1 : s.children.head instanceof He; + } + removeFormat(e, s) { + const r = this.getText(e, s), [i, a] = this.scroll.line(e + s); + let n = 0, o = new j(); + i != null && (n = i.length() - a, o = i.delta().slice(a, a + n - 1).insert(` +`)); + const p = this.getContents(e, s + n).diff(new j().insert(r).concat(o)), g = new j().retain(e).concat(p); + return this.applyDelta(g); + } + update(e) { + let s = arguments.length > 1 && arguments[1] !== void 0 ? arguments[1] : [], r = arguments.length > 2 && arguments[2] !== void 0 ? arguments[2] : void 0; + const i = this.delta; + if (s.length === 1 && s[0].type === "characterData" && // @ts-expect-error Fix me later + s[0].target.data.match(Fk) && this.scroll.find(s[0].target)) { + const a = this.scroll.find(s[0].target), n = Te(a), o = a.offset(this.scroll), u = s[0].oldValue.replace(us.CONTENTS, ""), p = new j().insert(u), g = new j().insert(a.value()), k = r && { + oldRange: Va(r.oldRange, -o), + newRange: Va(r.newRange, -o) + }; + e = new j().retain(o).concat(p.diff(g, k)).reduce((b, v) => v.insert ? b.insert(v.insert, n) : b.push(v), new j()), this.delta = i.compose(e); + } else + this.delta = this.getDelta(), (!e || !Hi(i.compose(e), this.delta)) && (e = i.diff(this.delta, r)); + return e; + } +} +function ss(t, e, s) { + if (t.length === 0) { + const [b] = ni(s.pop()); + return e <= 0 ? `` : `${ss([], e - 1, s)}`; + } + const [{ + child: r, + offset: i, + length: a, + indent: n, + type: o + }, ...u] = t, [p, g] = ni(o); + if (n > e) + return s.push(o), n === e + 1 ? `<${p}>${Ws(r, i, a)}${ss(u, n, s)}` : `<${p}>
  • ${ss(t, e + 1, s)}`; + const k = s[s.length - 1]; + if (n === e && o === k) + return `
  • ${Ws(r, i, a)}${ss(u, n, s)}`; + const [y] = ni(s.pop()); + return `${ss(t, e - 1, s)}`; +} +function Ws(t, e, s) { + let r = arguments.length > 3 && arguments[3] !== void 0 ? arguments[3] : !1; + if ("html" in t && typeof t.html == "function") + return t.html(e, s); + if (t instanceof je) + return Jn(t.value().slice(e, e + s)).replaceAll(" ", " "); + if (t instanceof Ue) { + if (t.statics.blotName === "list-container") { + const p = []; + return t.children.forEachAt(e, s, (g, k, y) => { + const b = "formats" in g && typeof g.formats == "function" ? g.formats() : {}; + p.push({ + child: g, + offset: k, + length: y, + indent: b.indent || 0, + type: b.list + }); + }), ss(p, -1, []); + } + const i = []; + if (t.children.forEachAt(e, s, (p, g, k) => { + i.push(Ws(p, g, k)); + }), r || t.statics.blotName === "list") + return i.join(""); + const { + outerHTML: a, + innerHTML: n + } = t.domNode, [o, u] = a.split(`>${n}<`); + return o === "${i.join("")}<${u}` : `${o}>${i.join("")}<${u}`; + } + return t.domNode instanceof Element ? t.domNode.outerHTML : ""; +} +function jk(t, e) { + return Object.keys(e).reduce((s, r) => { + if (t[r] == null) return s; + const i = e[r]; + return i === t[r] ? s[r] = i : Array.isArray(i) ? i.indexOf(t[r]) < 0 ? s[r] = i.concat([t[r]]) : s[r] = i : s[r] = [i, t[r]], s; + }, {}); +} +function ni(t) { + const e = t === "ordered" ? "ol" : "ul"; + switch (t) { + case "checked": + return [e, ' data-list="checked"']; + case "unchecked": + return [e, ' data-list="unchecked"']; + default: + return [e, ""]; + } +} +function ja(t) { + return t.reduce((e, s) => { + if (typeof s.insert == "string") { + const r = s.insert.replace(/\r\n/g, ` +`).replace(/\r/g, ` +`); + return e.insert(r, s.attributes); + } + return e.push(s); + }, new j()); +} +function Va(t, e) { + let { + index: s, + length: r + } = t; + return new Pt(s + e, r); +} +function Vk(t) { + const e = []; + return t.forEach((s) => { + typeof s.insert == "string" ? s.insert.split(` +`).forEach((i, a) => { + a && e.push({ + insert: ` +`, + attributes: s.attributes + }), i && e.push({ + insert: i, + attributes: s.attributes + }); + }) : e.push(s); + }), e; +} +class ze { + constructor(e) { + let s = arguments.length > 1 && arguments[1] !== void 0 ? arguments[1] : {}; + this.quill = e, this.options = s; + } +} +B(ze, "DEFAULTS", {}); +const Ln = "\uFEFF"; +class Yi extends Ae { + constructor(e, s) { + super(e, s), this.contentNode = document.createElement("span"), this.contentNode.setAttribute("contenteditable", "false"), Array.from(this.domNode.childNodes).forEach((r) => { + this.contentNode.appendChild(r); + }), this.leftGuard = document.createTextNode(Ln), this.rightGuard = document.createTextNode(Ln), this.domNode.appendChild(this.leftGuard), this.domNode.appendChild(this.contentNode), this.domNode.appendChild(this.rightGuard); + } + index(e, s) { + return e === this.leftGuard ? 0 : e === this.rightGuard ? 1 : super.index(e, s); + } + restore(e) { + let s = null, r; + const i = e.data.split(Ln).join(""); + if (e === this.leftGuard) + if (this.prev instanceof je) { + const a = this.prev.length(); + this.prev.insertAt(a, i), s = { + startNode: this.prev.domNode, + startOffset: a + i.length + }; + } else + r = document.createTextNode(i), this.parent.insertBefore(this.scroll.create(r), this), s = { + startNode: r, + startOffset: i.length + }; + else e === this.rightGuard && (this.next instanceof je ? (this.next.insertAt(0, i), s = { + startNode: this.next.domNode, + startOffset: i.length + }) : (r = document.createTextNode(i), this.parent.insertBefore(this.scroll.create(r), this.next), s = { + startNode: r, + startOffset: i.length + })); + return e.data = Ln, s; + } + update(e, s) { + e.forEach((r) => { + if (r.type === "characterData" && (r.target === this.leftGuard || r.target === this.rightGuard)) { + const i = this.restore(r.target); + i && (s.range = i); + } + }); + } +} +class Hk { + constructor(e, s) { + B(this, "isComposing", !1); + this.scroll = e, this.emitter = s, this.setupListeners(); + } + setupListeners() { + this.scroll.domNode.addEventListener("compositionstart", (e) => { + this.isComposing || this.handleCompositionStart(e); + }), this.scroll.domNode.addEventListener("compositionend", (e) => { + this.isComposing && queueMicrotask(() => { + this.handleCompositionEnd(e); + }); + }); + } + handleCompositionStart(e) { + const s = e.target instanceof Node ? this.scroll.find(e.target, !0) : null; + s && !(s instanceof Yi) && (this.emitter.emit(F.events.COMPOSITION_BEFORE_START, e), this.scroll.batchStart(), this.emitter.emit(F.events.COMPOSITION_START, e), this.isComposing = !0); + } + handleCompositionEnd(e) { + this.emitter.emit(F.events.COMPOSITION_BEFORE_END, e), this.scroll.batchEnd(), this.emitter.emit(F.events.COMPOSITION_END, e), this.isComposing = !1; + } +} +const Us = class Us { + constructor(e, s) { + B(this, "modules", {}); + this.quill = e, this.options = s; + } + init() { + Object.keys(this.options.modules).forEach((e) => { + this.modules[e] == null && this.addModule(e); + }); + } + addModule(e) { + const s = this.quill.constructor.import(`modules/${e}`); + return this.modules[e] = new s(this.quill, this.options.modules[e] || {}), this.modules[e]; + } +}; +B(Us, "DEFAULTS", { + modules: {} +}), B(Us, "themes", { + default: Us +}); +let ds = Us; +const zk = (t) => t.parentElement || t.getRootNode().host || null, Gk = (t) => { + const e = t.getBoundingClientRect(), s = "offsetWidth" in t && Math.abs(e.width) / t.offsetWidth || 1, r = "offsetHeight" in t && Math.abs(e.height) / t.offsetHeight || 1; + return { + top: e.top, + right: e.left + t.clientWidth * s, + bottom: e.top + t.clientHeight * r, + left: e.left + }; +}, qn = (t) => { + const e = parseInt(t, 10); + return Number.isNaN(e) ? 0 : e; +}, Ha = (t, e, s, r, i, a) => t < s && e > r ? 0 : t < s ? -(s - t + i) : e > r ? e - t > r - s ? t + i - s : e - r + a : 0, Kk = (t, e) => { + var a, n, o; + const s = t.ownerDocument; + let r = e, i = t; + for (; i; ) { + const u = i === s.body, p = u ? { + top: 0, + right: ((a = window.visualViewport) == null ? void 0 : a.width) ?? s.documentElement.clientWidth, + bottom: ((n = window.visualViewport) == null ? void 0 : n.height) ?? s.documentElement.clientHeight, + left: 0 + } : Gk(i), g = getComputedStyle(i), k = Ha(r.left, r.right, p.left, p.right, qn(g.scrollPaddingLeft), qn(g.scrollPaddingRight)), y = Ha(r.top, r.bottom, p.top, p.bottom, qn(g.scrollPaddingTop), qn(g.scrollPaddingBottom)); + if (k || y) + if (u) + (o = s.defaultView) == null || o.scrollBy(k, y); + else { + const { + scrollLeft: b, + scrollTop: v + } = i; + y && (i.scrollTop += y), k && (i.scrollLeft += k); + const E = i.scrollLeft - b, S = i.scrollTop - v; + r = { + left: r.left - E, + top: r.top - S, + right: r.right - E, + bottom: r.bottom - S + }; + } + i = u || g.position === "fixed" ? null : zk(i); + } +}, Wk = 100, Zk = ["block", "break", "cursor", "inline", "scroll", "text"], Xk = (t, e, s) => { + const r = new ls(); + return Zk.forEach((i) => { + const a = e.query(i); + a && r.register(a); + }), t.forEach((i) => { + let a = e.query(i); + a || s.error(`Cannot register "${i}" specified in "formats" config. Are you sure it was registered?`); + let n = 0; + for (; a; ) + if (r.register(a), a = "blotName" in a ? a.requiredContainer ?? null : null, n += 1, n > Wk) { + s.error(`Cycle detected in registering blot requiredContainer: "${i}"`); + break; + } + }), r; +}, as = ct("quill"), On = new ls(); +Ue.uiClass = "ql-ui"; +const Re = class Re { + static debug(e) { + e === !0 && (e = "log"), ct.level(e); + } + static find(e) { + let s = arguments.length > 1 && arguments[1] !== void 0 ? arguments[1] : !1; + return Ai.get(e) || On.find(e, s); + } + static import(e) { + return this.imports[e] == null && as.error(`Cannot import ${e}. Are you sure it was registered?`), this.imports[e]; + } + static register() { + if (typeof (arguments.length <= 0 ? void 0 : arguments[0]) != "string") { + const e = arguments.length <= 0 ? void 0 : arguments[0], s = !!(!(arguments.length <= 1) && arguments[1]), r = "attrName" in e ? e.attrName : e.blotName; + typeof r == "string" ? this.register(`formats/${r}`, e, s) : Object.keys(e).forEach((i) => { + this.register(i, e[i], s); + }); + } else { + const e = arguments.length <= 0 ? void 0 : arguments[0], s = arguments.length <= 1 ? void 0 : arguments[1], r = !!(!(arguments.length <= 2) && arguments[2]); + this.imports[e] != null && !r && as.warn(`Overwriting ${e} with`, s), this.imports[e] = s, (e.startsWith("blots/") || e.startsWith("formats/")) && s && typeof s != "boolean" && s.blotName !== "abstract" && On.register(s), typeof s.register == "function" && s.register(On); + } + } + constructor(e) { + let s = arguments.length > 1 && arguments[1] !== void 0 ? arguments[1] : {}; + if (this.options = Yk(e, s), this.container = this.options.container, this.container == null) { + as.error("Invalid Quill container", e); + return; + } + this.options.debug && Re.debug(this.options.debug); + const r = this.container.innerHTML.trim(); + this.container.classList.add("ql-container"), this.container.innerHTML = "", Ai.set(this.container, this), this.root = this.addContainer("ql-editor"), this.root.classList.add("ql-blank"), this.emitter = new F(); + const i = Gi.blotName, a = this.options.registry.query(i); + if (!a || !("blotName" in a)) + throw new Error(`Cannot initialize Quill without "${i}" blot`); + if (this.scroll = new a(this.options.registry, this.root, { + emitter: this.emitter + }), this.editor = new Uk(this.scroll), this.selection = new Mk(this.scroll, this.emitter), this.composition = new Hk(this.scroll, this.emitter), this.theme = new this.options.theme(this, this.options), this.keyboard = this.theme.addModule("keyboard"), this.clipboard = this.theme.addModule("clipboard"), this.history = this.theme.addModule("history"), this.uploader = this.theme.addModule("uploader"), this.theme.addModule("input"), this.theme.addModule("uiNode"), this.theme.init(), this.emitter.on(F.events.EDITOR_CHANGE, (n) => { + n === F.events.TEXT_CHANGE && this.root.classList.toggle("ql-blank", this.editor.isBlank()); + }), this.emitter.on(F.events.SCROLL_UPDATE, (n, o) => { + const u = this.selection.lastRange, [p] = this.selection.getRange(), g = u && p ? { + oldRange: u, + newRange: p + } : void 0; + De.call(this, () => this.editor.update(null, o, g), n); + }), this.emitter.on(F.events.SCROLL_EMBED_UPDATE, (n, o) => { + const u = this.selection.lastRange, [p] = this.selection.getRange(), g = u && p ? { + oldRange: u, + newRange: p + } : void 0; + De.call(this, () => { + const k = new j().retain(n.offset(this)).retain({ + [n.statics.blotName]: o + }); + return this.editor.update(k, [], g); + }, Re.sources.USER); + }), r) { + const n = this.clipboard.convert({ + html: `${r}


    `, + text: ` +` + }); + this.setContents(n); + } + this.history.clear(), this.options.placeholder && this.root.setAttribute("data-placeholder", this.options.placeholder), this.options.readOnly && this.disable(), this.allowReadOnlyEdits = !1; + } + addContainer(e) { + let s = arguments.length > 1 && arguments[1] !== void 0 ? arguments[1] : null; + if (typeof e == "string") { + const r = e; + e = document.createElement("div"), e.classList.add(r); + } + return this.container.insertBefore(e, s), e; + } + blur() { + this.selection.setRange(null); + } + deleteText(e, s, r) { + return [e, s, , r] = at(e, s, r), De.call(this, () => this.editor.deleteText(e, s), r, e, -1 * s); + } + disable() { + this.enable(!1); + } + editReadOnly(e) { + this.allowReadOnlyEdits = !0; + const s = e(); + return this.allowReadOnlyEdits = !1, s; + } + enable() { + let e = arguments.length > 0 && arguments[0] !== void 0 ? arguments[0] : !0; + this.scroll.enable(e), this.container.classList.toggle("ql-disabled", !e); + } + focus() { + let e = arguments.length > 0 && arguments[0] !== void 0 ? arguments[0] : {}; + this.selection.focus(), e.preventScroll || this.scrollSelectionIntoView(); + } + format(e, s) { + let r = arguments.length > 2 && arguments[2] !== void 0 ? arguments[2] : F.sources.API; + return De.call(this, () => { + const i = this.getSelection(!0); + let a = new j(); + if (i == null) return a; + if (this.scroll.query(e, V.BLOCK)) + a = this.editor.formatLine(i.index, i.length, { + [e]: s + }); + else { + if (i.length === 0) + return this.selection.format(e, s), a; + a = this.editor.formatText(i.index, i.length, { + [e]: s + }); + } + return this.setSelection(i, F.sources.SILENT), a; + }, r); + } + formatLine(e, s, r, i, a) { + let n; + return [e, s, n, a] = at( + e, + s, + // @ts-expect-error + r, + i, + a + ), De.call(this, () => this.editor.formatLine(e, s, n), a, e, 0); + } + formatText(e, s, r, i, a) { + let n; + return [e, s, n, a] = at( + // @ts-expect-error + e, + s, + r, + i, + a + ), De.call(this, () => this.editor.formatText(e, s, n), a, e, 0); + } + getBounds(e) { + let s = arguments.length > 1 && arguments[1] !== void 0 ? arguments[1] : 0, r = null; + if (typeof e == "number" ? r = this.selection.getBounds(e, s) : r = this.selection.getBounds(e.index, e.length), !r) return null; + const i = this.container.getBoundingClientRect(); + return { + bottom: r.bottom - i.top, + height: r.height, + left: r.left - i.left, + right: r.right - i.left, + top: r.top - i.top, + width: r.width + }; + } + getContents() { + let e = arguments.length > 0 && arguments[0] !== void 0 ? arguments[0] : 0, s = arguments.length > 1 && arguments[1] !== void 0 ? arguments[1] : this.getLength() - e; + return [e, s] = at(e, s), this.editor.getContents(e, s); + } + getFormat() { + let e = arguments.length > 0 && arguments[0] !== void 0 ? arguments[0] : this.getSelection(!0), s = arguments.length > 1 && arguments[1] !== void 0 ? arguments[1] : 0; + return typeof e == "number" ? this.editor.getFormat(e, s) : this.editor.getFormat(e.index, e.length); + } + getIndex(e) { + return e.offset(this.scroll); + } + getLength() { + return this.scroll.length(); + } + getLeaf(e) { + return this.scroll.leaf(e); + } + getLine(e) { + return this.scroll.line(e); + } + getLines() { + let e = arguments.length > 0 && arguments[0] !== void 0 ? arguments[0] : 0, s = arguments.length > 1 && arguments[1] !== void 0 ? arguments[1] : Number.MAX_VALUE; + return typeof e != "number" ? this.scroll.lines(e.index, e.length) : this.scroll.lines(e, s); + } + getModule(e) { + return this.theme.modules[e]; + } + getSelection() { + return (arguments.length > 0 && arguments[0] !== void 0 ? arguments[0] : !1) && this.focus(), this.update(), this.selection.getRange()[0]; + } + getSemanticHTML() { + let e = arguments.length > 0 && arguments[0] !== void 0 ? arguments[0] : 0, s = arguments.length > 1 ? arguments[1] : void 0; + return typeof e == "number" && (s = s ?? this.getLength() - e), [e, s] = at(e, s), this.editor.getHTML(e, s); + } + getText() { + let e = arguments.length > 0 && arguments[0] !== void 0 ? arguments[0] : 0, s = arguments.length > 1 ? arguments[1] : void 0; + return typeof e == "number" && (s = s ?? this.getLength() - e), [e, s] = at(e, s), this.editor.getText(e, s); + } + hasFocus() { + return this.selection.hasFocus(); + } + insertEmbed(e, s, r) { + let i = arguments.length > 3 && arguments[3] !== void 0 ? arguments[3] : Re.sources.API; + return De.call(this, () => this.editor.insertEmbed(e, s, r), i, e); + } + insertText(e, s, r, i, a) { + let n; + return [e, , n, a] = at(e, 0, r, i, a), De.call(this, () => this.editor.insertText(e, s, n), a, e, s.length); + } + isEnabled() { + return this.scroll.isEnabled(); + } + off() { + return this.emitter.off(...arguments); + } + on() { + return this.emitter.on(...arguments); + } + once() { + return this.emitter.once(...arguments); + } + removeFormat(e, s, r) { + return [e, s, , r] = at(e, s, r), De.call(this, () => this.editor.removeFormat(e, s), r, e); + } + scrollRectIntoView(e) { + Kk(this.root, e); + } + /** + * @deprecated Use Quill#scrollSelectionIntoView() instead. + */ + scrollIntoView() { + console.warn("Quill#scrollIntoView() has been deprecated and will be removed in the near future. Please use Quill#scrollSelectionIntoView() instead."), this.scrollSelectionIntoView(); + } + /** + * Scroll the current selection into the visible area. + * If the selection is already visible, no scrolling will occur. + */ + scrollSelectionIntoView() { + const e = this.selection.lastRange, s = e && this.selection.getBounds(e.index, e.length); + s && this.scrollRectIntoView(s); + } + setContents(e) { + let s = arguments.length > 1 && arguments[1] !== void 0 ? arguments[1] : F.sources.API; + return De.call(this, () => { + e = new j(e); + const r = this.getLength(), i = this.editor.deleteText(0, r), a = this.editor.insertContents(0, e), n = this.editor.deleteText(this.getLength() - 1, 1); + return i.compose(a).compose(n); + }, s); + } + setSelection(e, s, r) { + e == null ? this.selection.setRange(null, s || Re.sources.API) : ([e, s, , r] = at(e, s, r), this.selection.setRange(new Pt(Math.max(0, e), s), r), r !== F.sources.SILENT && this.scrollSelectionIntoView()); + } + setText(e) { + let s = arguments.length > 1 && arguments[1] !== void 0 ? arguments[1] : F.sources.API; + const r = new j().insert(e); + return this.setContents(r, s); + } + update() { + let e = arguments.length > 0 && arguments[0] !== void 0 ? arguments[0] : F.sources.USER; + const s = this.scroll.update(e); + return this.selection.update(e), s; + } + updateContents(e) { + let s = arguments.length > 1 && arguments[1] !== void 0 ? arguments[1] : F.sources.API; + return De.call(this, () => (e = new j(e), this.editor.applyDelta(e)), s, !0); + } +}; +B(Re, "DEFAULTS", { + bounds: null, + modules: { + clipboard: !0, + keyboard: !0, + history: !0, + uploader: !0 + }, + placeholder: "", + readOnly: !1, + registry: On, + theme: "default" +}), B(Re, "events", F.events), B(Re, "sources", F.sources), B(Re, "version", "2.0.3"), B(Re, "imports", { + delta: j, + parchment: $k, + "core/module": ze, + "core/theme": ds +}); +let N = Re; +function za(t) { + return typeof t == "string" ? document.querySelector(t) : t; +} +function ri(t) { + return Object.entries(t ?? {}).reduce((e, s) => { + let [r, i] = s; + return { + ...e, + [r]: i === !0 ? {} : i + }; + }, {}); +} +function Ga(t) { + return Object.fromEntries(Object.entries(t).filter((e) => e[1] !== void 0)); +} +function Yk(t, e) { + const s = za(t); + if (!s) + throw new Error("Invalid Quill container"); + const i = !e.theme || e.theme === N.DEFAULTS.theme ? ds : N.import(`themes/${e.theme}`); + if (!i) + throw new Error(`Invalid theme ${e.theme}. Did you register it?`); + const { + modules: a, + ...n + } = N.DEFAULTS, { + modules: o, + ...u + } = i.DEFAULTS; + let p = ri(e.modules); + p != null && p.toolbar && p.toolbar.constructor !== Object && (p = { + ...p, + toolbar: { + container: p.toolbar + } + }); + const g = yt({}, ri(a), ri(o), p), k = { + ...n, + ...Ga(u), + ...Ga(e) + }; + let y = e.registry; + return y ? e.formats && as.warn('Ignoring "formats" option because "registry" is specified') : y = e.formats ? Xk(e.formats, k.registry, as) : k.registry, { + ...k, + registry: y, + container: s, + theme: i, + modules: Object.entries(g).reduce((b, v) => { + let [E, S] = v; + if (!S) return b; + const _ = N.import(`modules/${E}`); + return _ == null ? (as.error(`Cannot load ${E} module. Are you sure you registered it?`), b) : { + ...b, + // @ts-expect-error + [E]: yt({}, _.DEFAULTS || {}, S) + }; + }, {}), + bounds: za(k.bounds) + }; +} +function De(t, e, s, r) { + if (!this.isEnabled() && e === F.sources.USER && !this.allowReadOnlyEdits) + return new j(); + let i = s == null ? null : this.getSelection(); + const a = this.editor.delta, n = t(); + if (i != null && (s === !0 && (s = i.index), r == null ? i = Ka(i, n, e) : r !== 0 && (i = Ka(i, s, r, e)), this.setSelection(i, F.sources.SILENT)), n.length() > 0) { + const o = [F.events.TEXT_CHANGE, n, a, e]; + this.emitter.emit(F.events.EDITOR_CHANGE, ...o), e !== F.sources.SILENT && this.emitter.emit(...o); + } + return n; +} +function at(t, e, s, r, i) { + let a = {}; + return typeof t.index == "number" && typeof t.length == "number" ? typeof e != "number" ? (i = r, r = s, s = e, e = t.length, t = t.index) : (e = t.length, t = t.index) : typeof e != "number" && (i = r, r = s, s = e, e = 0), typeof s == "object" ? (a = s, i = r) : typeof s == "string" && (r != null ? a[s] = r : i = s), i = i || F.sources.API, [t, e, a, i]; +} +function Ka(t, e, s, r) { + const i = typeof s == "number" ? s : 0; + if (t == null) return null; + let a, n; + return e && typeof e.transformPosition == "function" ? [a, n] = [t.index, t.index + t.length].map((o) => ( + // @ts-expect-error -- TODO: add a better type guard around `index` + e.transformPosition(o, r !== F.sources.USER) + )) : [a, n] = [t.index, t.index + t.length].map((o) => o < e || o === e && r === F.sources.USER ? o : i >= 0 ? o + i : Math.max(e, o + i)), new Pt(a, n - a); +} +class Mt extends Zn { +} +function Wa(t) { + return t instanceof ce || t instanceof Ne; +} +function Za(t) { + return typeof t.updateContent == "function"; +} +class ns extends Gi { + constructor(e, s, r) { + let { + emitter: i + } = r; + super(e, s), this.emitter = i, this.batch = !1, this.optimize(), this.enable(), this.domNode.addEventListener("dragstart", (a) => this.handleDragStart(a)); + } + batchStart() { + Array.isArray(this.batch) || (this.batch = []); + } + batchEnd() { + if (!this.batch) return; + const e = this.batch; + this.batch = !1, this.update(e); + } + emitMount(e) { + this.emitter.emit(F.events.SCROLL_BLOT_MOUNT, e); + } + emitUnmount(e) { + this.emitter.emit(F.events.SCROLL_BLOT_UNMOUNT, e); + } + emitEmbedUpdate(e, s) { + this.emitter.emit(F.events.SCROLL_EMBED_UPDATE, e, s); + } + deleteAt(e, s) { + const [r, i] = this.line(e), [a] = this.line(e + s); + if (super.deleteAt(e, s), a != null && r !== a && i > 0) { + if (r instanceof Ne || a instanceof Ne) { + this.optimize(); + return; + } + const n = a.children.head instanceof He ? null : a.children.head; + r.moveChildren(a, n), r.remove(); + } + this.optimize(); + } + enable() { + let e = arguments.length > 0 && arguments[0] !== void 0 ? arguments[0] : !0; + this.domNode.setAttribute("contenteditable", e ? "true" : "false"); + } + formatAt(e, s, r, i) { + super.formatAt(e, s, r, i), this.optimize(); + } + insertAt(e, s, r) { + if (e >= this.length()) + if (r == null || this.scroll.query(s, V.BLOCK) == null) { + const i = this.scroll.create(this.statics.defaultChild.blotName); + this.appendChild(i), r == null && s.endsWith(` +`) ? i.insertAt(0, s.slice(0, -1), r) : i.insertAt(0, s, r); + } else { + const i = this.scroll.create(s, r); + this.appendChild(i); + } + else + super.insertAt(e, s, r); + this.optimize(); + } + insertBefore(e, s) { + if (e.statics.scope === V.INLINE_BLOT) { + const r = this.scroll.create(this.statics.defaultChild.blotName); + r.appendChild(e), super.insertBefore(r, s); + } else + super.insertBefore(e, s); + } + insertContents(e, s) { + const r = this.deltaToRenderBlocks(s.concat(new j().insert(` +`))), i = r.pop(); + if (i == null) return; + this.batchStart(); + const a = r.shift(); + if (a) { + const u = a.type === "block" && (a.delta.length() === 0 || !this.descendant(Ne, e)[0] && e < this.length()), p = a.type === "block" ? a.delta : new j().insert({ + [a.key]: a.value + }); + ii(this, e, p); + const g = a.type === "block" ? 1 : 0, k = e + p.length() + g; + u && this.insertAt(k - 1, ` +`); + const y = Te(this.line(e)[0]), b = Ie.AttributeMap.diff(y, a.attributes) || {}; + Object.keys(b).forEach((v) => { + this.formatAt(k - 1, 1, v, b[v]); + }), e = k; + } + let [n, o] = this.children.find(e); + if (r.length && (n && (n = n.split(o), o = 0), r.forEach((u) => { + if (u.type === "block") { + const p = this.createBlock(u.attributes, n || void 0); + ii(p, 0, u.delta); + } else { + const p = this.create(u.key, u.value); + this.insertBefore(p, n || void 0), Object.keys(u.attributes).forEach((g) => { + p.format(g, u.attributes[g]); + }); + } + })), i.type === "block" && i.delta.length()) { + const u = n ? n.offset(n.scroll) + o : this.length(); + ii(this, u, i.delta); + } + this.batchEnd(), this.optimize(); + } + isEnabled() { + return this.domNode.getAttribute("contenteditable") === "true"; + } + leaf(e) { + const s = this.path(e).pop(); + if (!s) + return [null, -1]; + const [r, i] = s; + return r instanceof ge ? [r, i] : [null, -1]; + } + line(e) { + return e === this.length() ? this.line(e - 1) : this.descendant(Wa, e); + } + lines() { + let e = arguments.length > 0 && arguments[0] !== void 0 ? arguments[0] : 0, s = arguments.length > 1 && arguments[1] !== void 0 ? arguments[1] : Number.MAX_VALUE; + const r = (i, a, n) => { + let o = [], u = n; + return i.children.forEachAt(a, n, (p, g, k) => { + Wa(p) ? o.push(p) : p instanceof Zn && (o = o.concat(r(p, g, u))), u -= k; + }), o; + }; + return r(this, e, s); + } + optimize() { + let e = arguments.length > 0 && arguments[0] !== void 0 ? arguments[0] : [], s = arguments.length > 1 && arguments[1] !== void 0 ? arguments[1] : {}; + this.batch || (super.optimize(e, s), e.length > 0 && this.emitter.emit(F.events.SCROLL_OPTIMIZE, e, s)); + } + path(e) { + return super.path(e).slice(1); + } + remove() { + } + update(e) { + if (this.batch) { + Array.isArray(e) && (this.batch = this.batch.concat(e)); + return; + } + let s = F.sources.USER; + typeof e == "string" && (s = e), Array.isArray(e) || (e = this.observer.takeRecords()), e = e.filter((r) => { + let { + target: i + } = r; + const a = this.find(i, !0); + return a && !Za(a); + }), e.length > 0 && this.emitter.emit(F.events.SCROLL_BEFORE_UPDATE, s, e), super.update(e.concat([])), e.length > 0 && this.emitter.emit(F.events.SCROLL_UPDATE, s, e); + } + updateEmbedAt(e, s, r) { + const [i] = this.descendant((a) => a instanceof Ne, e); + i && i.statics.blotName === s && Za(i) && i.updateContent(r); + } + handleDragStart(e) { + e.preventDefault(); + } + deltaToRenderBlocks(e) { + const s = []; + let r = new j(); + return e.forEach((i) => { + const a = i == null ? void 0 : i.insert; + if (a) + if (typeof a == "string") { + const n = a.split(` +`); + n.slice(0, -1).forEach((u) => { + r.insert(u, i.attributes), s.push({ + type: "block", + delta: r, + attributes: i.attributes ?? {} + }), r = new j(); + }); + const o = n[n.length - 1]; + o && r.insert(o, i.attributes); + } else { + const n = Object.keys(a)[0]; + if (!n) return; + this.query(n, V.INLINE) ? r.push(i) : (r.length() && s.push({ + type: "block", + delta: r, + attributes: {} + }), r = new j(), s.push({ + type: "blockEmbed", + key: n, + value: a[n], + attributes: i.attributes ?? {} + })); + } + }), r.length() && s.push({ + type: "block", + delta: r, + attributes: {} + }), s; + } + createBlock(e, s) { + let r; + const i = {}; + Object.entries(e).forEach((o) => { + let [u, p] = o; + this.query(u, V.BLOCK & V.BLOT) != null ? r = u : i[u] = p; + }); + const a = this.create(r || this.statics.defaultChild.blotName, r ? e[r] : void 0); + this.insertBefore(a, s || void 0); + const n = a.length(); + return Object.entries(i).forEach((o) => { + let [u, p] = o; + a.formatAt(0, n, u, p); + }), a; + } +} +B(ns, "blotName", "scroll"), B(ns, "className", "ql-editor"), B(ns, "tagName", "DIV"), B(ns, "defaultChild", ce), B(ns, "allowedChildren", [ce, Ne, Mt]); +function ii(t, e, s) { + s.reduce((r, i) => { + const a = Ie.Op.length(i); + let n = i.attributes || {}; + if (i.insert != null) { + if (typeof i.insert == "string") { + const o = i.insert; + t.insertAt(r, o); + const [u] = t.descendant(ge, r), p = Te(u); + n = Ie.AttributeMap.diff(p, n) || {}; + } else if (typeof i.insert == "object") { + const o = Object.keys(i.insert)[0]; + if (o == null) return r; + if (t.insertAt(r, o, i.insert[o]), t.scroll.query(o, V.INLINE) != null) { + const [p] = t.descendant(ge, r), g = Te(p); + n = Ie.AttributeMap.diff(g, n) || {}; + } + } + } + return Object.keys(n).forEach((o) => { + t.formatAt(r, a, o, n[o]); + }), r + a; + }, e); +} +const Qi = { + scope: V.BLOCK, + whitelist: ["right", "center", "justify"] +}, Qk = new Je("align", "align", Qi), xo = new Ve("align", "ql-align", Qi), el = new wt("align", "text-align", Qi); +class tl extends wt { + value(e) { + let s = super.value(e); + return s.startsWith("rgb(") ? (s = s.replace(/^[^\d]+/, "").replace(/[^\d]+$/, ""), `#${s.split(",").map((i) => `00${parseInt(i, 10).toString(16)}`.slice(-2)).join("")}`) : s; + } +} +const Jk = new Ve("color", "ql-color", { + scope: V.INLINE +}), Ji = new tl("color", "color", { + scope: V.INLINE +}), xk = new Ve("background", "ql-bg", { + scope: V.INLINE +}), xi = new tl("background", "background-color", { + scope: V.INLINE +}); +class Ft extends Mt { + static create(e) { + const s = super.create(e); + return s.setAttribute("spellcheck", "false"), s; + } + code(e, s) { + return this.children.map((r) => r.length() <= 1 ? "" : r.domNode.innerText).join(` +`).slice(e, e + s); + } + html(e, s) { + return `
    +${Jn(this.code(e, s))}
    +
    `; + } +} +class me extends ce { + static register() { + N.register(Ft); + } +} +B(me, "TAB", " "); +class ea extends xe { +} +ea.blotName = "code"; +ea.tagName = "CODE"; +me.blotName = "code-block"; +me.className = "ql-code-block"; +me.tagName = "DIV"; +Ft.blotName = "code-block-container"; +Ft.className = "ql-code-block-container"; +Ft.tagName = "DIV"; +Ft.allowedChildren = [me]; +me.allowedChildren = [je, He, us]; +me.requiredContainer = Ft; +const ta = { + scope: V.BLOCK, + whitelist: ["rtl"] +}, sl = new Je("direction", "dir", ta), nl = new Ve("direction", "ql-direction", ta), rl = new wt("direction", "direction", ta), il = { + scope: V.INLINE, + whitelist: ["serif", "monospace"] +}, al = new Ve("font", "ql-font", il); +class ew extends wt { + value(e) { + return super.value(e).replace(/["']/g, ""); + } +} +const ol = new ew("font", "font-family", il), ll = new Ve("size", "ql-size", { + scope: V.INLINE, + whitelist: ["small", "large", "huge"] +}), ul = new wt("size", "font-size", { + scope: V.INLINE, + whitelist: ["10px", "18px", "32px"] +}), tw = ct("quill:keyboard"), sw = /Mac/i.test(navigator.platform) ? "metaKey" : "ctrlKey"; +class xn extends ze { + static match(e, s) { + return ["altKey", "ctrlKey", "metaKey", "shiftKey"].some((r) => !!s[r] !== e[r] && s[r] !== null) ? !1 : s.key === e.key || s.key === e.which; + } + constructor(e, s) { + super(e, s), this.bindings = {}, Object.keys(this.options.bindings).forEach((r) => { + this.options.bindings[r] && this.addBinding(this.options.bindings[r]); + }), this.addBinding({ + key: "Enter", + shiftKey: null + }, this.handleEnter), this.addBinding({ + key: "Enter", + metaKey: null, + ctrlKey: null, + altKey: null + }, () => { + }), /Firefox/i.test(navigator.userAgent) ? (this.addBinding({ + key: "Backspace" + }, { + collapsed: !0 + }, this.handleBackspace), this.addBinding({ + key: "Delete" + }, { + collapsed: !0 + }, this.handleDelete)) : (this.addBinding({ + key: "Backspace" + }, { + collapsed: !0, + prefix: /^.?$/ + }, this.handleBackspace), this.addBinding({ + key: "Delete" + }, { + collapsed: !0, + suffix: /^.?$/ + }, this.handleDelete)), this.addBinding({ + key: "Backspace" + }, { + collapsed: !1 + }, this.handleDeleteRange), this.addBinding({ + key: "Delete" + }, { + collapsed: !1 + }, this.handleDeleteRange), this.addBinding({ + key: "Backspace", + altKey: null, + ctrlKey: null, + metaKey: null, + shiftKey: null + }, { + collapsed: !0, + offset: 0 + }, this.handleBackspace), this.listen(); + } + addBinding(e) { + let s = arguments.length > 1 && arguments[1] !== void 0 ? arguments[1] : {}, r = arguments.length > 2 && arguments[2] !== void 0 ? arguments[2] : {}; + const i = rw(e); + if (i == null) { + tw.warn("Attempted to add invalid keyboard binding", i); + return; + } + typeof s == "function" && (s = { + handler: s + }), typeof r == "function" && (r = { + handler: r + }), (Array.isArray(i.key) ? i.key : [i.key]).forEach((n) => { + const o = { + ...i, + key: n, + ...s, + ...r + }; + this.bindings[o.key] = this.bindings[o.key] || [], this.bindings[o.key].push(o); + }); + } + listen() { + this.quill.root.addEventListener("keydown", (e) => { + if (e.defaultPrevented || e.isComposing || e.keyCode === 229 && (e.key === "Enter" || e.key === "Backspace")) return; + const i = (this.bindings[e.key] || []).concat(this.bindings[e.which] || []).filter((_) => xn.match(e, _)); + if (i.length === 0) return; + const a = N.find(e.target, !0); + if (a && a.scroll !== this.quill.scroll) return; + const n = this.quill.getSelection(); + if (n == null || !this.quill.hasFocus()) return; + const [o, u] = this.quill.getLine(n.index), [p, g] = this.quill.getLeaf(n.index), [k, y] = n.length === 0 ? [p, g] : this.quill.getLeaf(n.index + n.length), b = p instanceof jn ? p.value().slice(0, g) : "", v = k instanceof jn ? k.value().slice(y) : "", E = { + collapsed: n.length === 0, + // @ts-expect-error Fix me later + empty: n.length === 0 && o.length() <= 1, + format: this.quill.getFormat(n), + line: o, + offset: u, + prefix: b, + suffix: v, + event: e + }; + i.some((_) => { + if (_.collapsed != null && _.collapsed !== E.collapsed || _.empty != null && _.empty !== E.empty || _.offset != null && _.offset !== E.offset) + return !1; + if (Array.isArray(_.format)) { + if (_.format.every((O) => E.format[O] == null)) + return !1; + } else if (typeof _.format == "object" && !Object.keys(_.format).every((O) => _.format[O] === !0 ? E.format[O] != null : _.format[O] === !1 ? E.format[O] == null : Hi(_.format[O], E.format[O]))) + return !1; + return _.prefix != null && !_.prefix.test(E.prefix) || _.suffix != null && !_.suffix.test(E.suffix) ? !1 : _.handler.call(this, n, E, _) !== !0; + }) && e.preventDefault(); + }); + } + handleBackspace(e, s) { + const r = /[\uD800-\uDBFF][\uDC00-\uDFFF]$/.test(s.prefix) ? 2 : 1; + if (e.index === 0 || this.quill.getLength() <= 1) return; + let i = {}; + const [a] = this.quill.getLine(e.index); + let n = new j().retain(e.index - r).delete(r); + if (s.offset === 0) { + const [o] = this.quill.getLine(e.index - 1); + if (o && !(o.statics.blotName === "block" && o.length() <= 1)) { + const p = a.formats(), g = this.quill.getFormat(e.index - 1, 1); + if (i = Ie.AttributeMap.diff(p, g) || {}, Object.keys(i).length > 0) { + const k = new j().retain(e.index + a.length() - 2).retain(1, i); + n = n.compose(k); + } + } + } + this.quill.updateContents(n, N.sources.USER), this.quill.focus(); + } + handleDelete(e, s) { + const r = /^[\uD800-\uDBFF][\uDC00-\uDFFF]/.test(s.suffix) ? 2 : 1; + if (e.index >= this.quill.getLength() - r) return; + let i = {}; + const [a] = this.quill.getLine(e.index); + let n = new j().retain(e.index).delete(r); + if (s.offset >= a.length() - 1) { + const [o] = this.quill.getLine(e.index + 1); + if (o) { + const u = a.formats(), p = this.quill.getFormat(e.index, 1); + i = Ie.AttributeMap.diff(u, p) || {}, Object.keys(i).length > 0 && (n = n.retain(o.length() - 1).retain(1, i)); + } + } + this.quill.updateContents(n, N.sources.USER), this.quill.focus(); + } + handleDeleteRange(e) { + sa({ + range: e, + quill: this.quill + }), this.quill.focus(); + } + handleEnter(e, s) { + const r = Object.keys(s.format).reduce((a, n) => (this.quill.scroll.query(n, V.BLOCK) && !Array.isArray(s.format[n]) && (a[n] = s.format[n]), a), {}), i = new j().retain(e.index).delete(e.length).insert(` +`, r); + this.quill.updateContents(i, N.sources.USER), this.quill.setSelection(e.index + 1, N.sources.SILENT), this.quill.focus(); + } +} +const nw = { + bindings: { + bold: ai("bold"), + italic: ai("italic"), + underline: ai("underline"), + indent: { + // highlight tab or tab at beginning of list, indent or blockquote + key: "Tab", + format: ["blockquote", "indent", "list"], + handler(t, e) { + return e.collapsed && e.offset !== 0 ? !0 : (this.quill.format("indent", "+1", N.sources.USER), !1); + } + }, + outdent: { + key: "Tab", + shiftKey: !0, + format: ["blockquote", "indent", "list"], + // highlight tab or tab at beginning of list, indent or blockquote + handler(t, e) { + return e.collapsed && e.offset !== 0 ? !0 : (this.quill.format("indent", "-1", N.sources.USER), !1); + } + }, + "outdent backspace": { + key: "Backspace", + collapsed: !0, + shiftKey: null, + metaKey: null, + ctrlKey: null, + altKey: null, + format: ["indent", "list"], + offset: 0, + handler(t, e) { + e.format.indent != null ? this.quill.format("indent", "-1", N.sources.USER) : e.format.list != null && this.quill.format("list", !1, N.sources.USER); + } + }, + "indent code-block": Xa(!0), + "outdent code-block": Xa(!1), + "remove tab": { + key: "Tab", + shiftKey: !0, + collapsed: !0, + prefix: /\t$/, + handler(t) { + this.quill.deleteText(t.index - 1, 1, N.sources.USER); + } + }, + tab: { + key: "Tab", + handler(t, e) { + if (e.format.table) return !0; + this.quill.history.cutoff(); + const s = new j().retain(t.index).delete(t.length).insert(" "); + return this.quill.updateContents(s, N.sources.USER), this.quill.history.cutoff(), this.quill.setSelection(t.index + 1, N.sources.SILENT), !1; + } + }, + "blockquote empty enter": { + key: "Enter", + collapsed: !0, + format: ["blockquote"], + empty: !0, + handler() { + this.quill.format("blockquote", !1, N.sources.USER); + } + }, + "list empty enter": { + key: "Enter", + collapsed: !0, + format: ["list"], + empty: !0, + handler(t, e) { + const s = { + list: !1 + }; + e.format.indent && (s.indent = !1), this.quill.formatLine(t.index, t.length, s, N.sources.USER); + } + }, + "checklist enter": { + key: "Enter", + collapsed: !0, + format: { + list: "checked" + }, + handler(t) { + const [e, s] = this.quill.getLine(t.index), r = { + // @ts-expect-error Fix me later + ...e.formats(), + list: "checked" + }, i = new j().retain(t.index).insert(` +`, r).retain(e.length() - s - 1).retain(1, { + list: "unchecked" + }); + this.quill.updateContents(i, N.sources.USER), this.quill.setSelection(t.index + 1, N.sources.SILENT), this.quill.scrollSelectionIntoView(); + } + }, + "header enter": { + key: "Enter", + collapsed: !0, + format: ["header"], + suffix: /^$/, + handler(t, e) { + const [s, r] = this.quill.getLine(t.index), i = new j().retain(t.index).insert(` +`, e.format).retain(s.length() - r - 1).retain(1, { + header: null + }); + this.quill.updateContents(i, N.sources.USER), this.quill.setSelection(t.index + 1, N.sources.SILENT), this.quill.scrollSelectionIntoView(); + } + }, + "table backspace": { + key: "Backspace", + format: ["table"], + collapsed: !0, + offset: 0, + handler() { + } + }, + "table delete": { + key: "Delete", + format: ["table"], + collapsed: !0, + suffix: /^$/, + handler() { + } + }, + "table enter": { + key: "Enter", + shiftKey: null, + format: ["table"], + handler(t) { + const e = this.quill.getModule("table"); + if (e) { + const [s, r, i, a] = e.getTable(t), n = iw(s, r, i, a); + if (n == null) return; + let o = s.offset(); + if (n < 0) { + const u = new j().retain(o).insert(` +`); + this.quill.updateContents(u, N.sources.USER), this.quill.setSelection(t.index + 1, t.length, N.sources.SILENT); + } else if (n > 0) { + o += s.length(); + const u = new j().retain(o).insert(` +`); + this.quill.updateContents(u, N.sources.USER), this.quill.setSelection(o, N.sources.USER); + } + } + } + }, + "table tab": { + key: "Tab", + shiftKey: null, + format: ["table"], + handler(t, e) { + const { + event: s, + line: r + } = e, i = r.offset(this.quill.scroll); + s.shiftKey ? this.quill.setSelection(i - 1, N.sources.USER) : this.quill.setSelection(i + r.length(), N.sources.USER); + } + }, + "list autofill": { + key: " ", + shiftKey: null, + collapsed: !0, + format: { + "code-block": !1, + blockquote: !1, + table: !1 + }, + prefix: /^\s*?(\d+\.|-|\*|\[ ?\]|\[x\])$/, + handler(t, e) { + if (this.quill.scroll.query("list") == null) return !0; + const { + length: s + } = e.prefix, [r, i] = this.quill.getLine(t.index); + if (i > s) return !0; + let a; + switch (e.prefix.trim()) { + case "[]": + case "[ ]": + a = "unchecked"; + break; + case "[x]": + a = "checked"; + break; + case "-": + case "*": + a = "bullet"; + break; + default: + a = "ordered"; + } + this.quill.insertText(t.index, " ", N.sources.USER), this.quill.history.cutoff(); + const n = new j().retain(t.index - i).delete(s + 1).retain(r.length() - 2 - i).retain(1, { + list: a + }); + return this.quill.updateContents(n, N.sources.USER), this.quill.history.cutoff(), this.quill.setSelection(t.index - s, N.sources.SILENT), !1; + } + }, + "code exit": { + key: "Enter", + collapsed: !0, + format: ["code-block"], + prefix: /^$/, + suffix: /^\s*$/, + handler(t) { + const [e, s] = this.quill.getLine(t.index); + let r = 2, i = e; + for (; i != null && i.length() <= 1 && i.formats()["code-block"]; ) + if (i = i.prev, r -= 1, r <= 0) { + const a = new j().retain(t.index + e.length() - s - 2).retain(1, { + "code-block": null + }).delete(1); + return this.quill.updateContents(a, N.sources.USER), this.quill.setSelection(t.index - 1, N.sources.SILENT), !1; + } + return !0; + } + }, + "embed left": Pn("ArrowLeft", !1), + "embed left shift": Pn("ArrowLeft", !0), + "embed right": Pn("ArrowRight", !1), + "embed right shift": Pn("ArrowRight", !0), + "table down": Ya(!1), + "table up": Ya(!0) + } +}; +xn.DEFAULTS = nw; +function Xa(t) { + return { + key: "Tab", + shiftKey: !t, + format: { + "code-block": !0 + }, + handler(e, s) { + let { + event: r + } = s; + const i = this.quill.scroll.query("code-block"), { + TAB: a + } = i; + if (e.length === 0 && !r.shiftKey) { + this.quill.insertText(e.index, a, N.sources.USER), this.quill.setSelection(e.index + a.length, N.sources.SILENT); + return; + } + const n = e.length === 0 ? this.quill.getLines(e.index, 1) : this.quill.getLines(e); + let { + index: o, + length: u + } = e; + n.forEach((p, g) => { + t ? (p.insertAt(0, a), g === 0 ? o += a.length : u += a.length) : p.domNode.textContent.startsWith(a) && (p.deleteAt(0, a.length), g === 0 ? o -= a.length : u -= a.length); + }), this.quill.update(N.sources.USER), this.quill.setSelection(o, u, N.sources.SILENT); + } + }; +} +function Pn(t, e) { + return { + key: t, + shiftKey: e, + altKey: null, + [t === "ArrowLeft" ? "prefix" : "suffix"]: /^$/, + handler(r) { + let { + index: i + } = r; + t === "ArrowRight" && (i += r.length + 1); + const [a] = this.quill.getLeaf(i); + return a instanceof Ae ? (t === "ArrowLeft" ? e ? this.quill.setSelection(r.index - 1, r.length + 1, N.sources.USER) : this.quill.setSelection(r.index - 1, N.sources.USER) : e ? this.quill.setSelection(r.index, r.length + 1, N.sources.USER) : this.quill.setSelection(r.index + r.length + 1, N.sources.USER), !1) : !0; + } + }; +} +function ai(t) { + return { + key: t[0], + shortKey: !0, + handler(e, s) { + this.quill.format(t, !s.format[t], N.sources.USER); + } + }; +} +function Ya(t) { + return { + key: t ? "ArrowUp" : "ArrowDown", + collapsed: !0, + format: ["table"], + handler(e, s) { + const r = t ? "prev" : "next", i = s.line, a = i.parent[r]; + if (a != null) { + if (a.statics.blotName === "table-row") { + let n = a.children.head, o = i; + for (; o.prev != null; ) + o = o.prev, n = n.next; + const u = n.offset(this.quill.scroll) + Math.min(s.offset, n.length() - 1); + this.quill.setSelection(u, 0, N.sources.USER); + } + } else { + const n = i.table()[r]; + n != null && (t ? this.quill.setSelection(n.offset(this.quill.scroll) + n.length() - 1, 0, N.sources.USER) : this.quill.setSelection(n.offset(this.quill.scroll), 0, N.sources.USER)); + } + return !1; + } + }; +} +function rw(t) { + if (typeof t == "string" || typeof t == "number") + t = { + key: t + }; + else if (typeof t == "object") + t = rs(t); + else + return null; + return t.shortKey && (t[sw] = t.shortKey, delete t.shortKey), t; +} +function sa(t) { + let { + quill: e, + range: s + } = t; + const r = e.getLines(s); + let i = {}; + if (r.length > 1) { + const a = r[0].formats(), n = r[r.length - 1].formats(); + i = Ie.AttributeMap.diff(n, a) || {}; + } + e.deleteText(s, N.sources.USER), Object.keys(i).length > 0 && e.formatLine(s.index, 1, i, N.sources.USER), e.setSelection(s.index, N.sources.SILENT); +} +function iw(t, e, s, r) { + return e.prev == null && e.next == null ? s.prev == null && s.next == null ? r === 0 ? -1 : 1 : s.prev == null ? -1 : 1 : e.prev == null ? -1 : e.next == null ? 1 : null; +} +const aw = /font-weight:\s*normal/, ow = ["P", "OL", "UL"], Qa = (t) => t && ow.includes(t.tagName), lw = (t) => { + Array.from(t.querySelectorAll("br")).filter((e) => Qa(e.previousElementSibling) && Qa(e.nextElementSibling)).forEach((e) => { + var s; + (s = e.parentNode) == null || s.removeChild(e); + }); +}, uw = (t) => { + Array.from(t.querySelectorAll('b[style*="font-weight"]')).filter((e) => { + var s; + return (s = e.getAttribute("style")) == null ? void 0 : s.match(aw); + }).forEach((e) => { + var r; + const s = t.createDocumentFragment(); + s.append(...e.childNodes), (r = e.parentNode) == null || r.replaceChild(s, e); + }); +}; +function dw(t) { + t.querySelector('[id^="docs-internal-guid-"]') && (uw(t), lw(t)); +} +const cw = /\bmso-list:[^;]*ignore/i, fw = /\bmso-list:[^;]*\bl(\d+)/i, pw = /\bmso-list:[^;]*\blevel(\d+)/i, hw = (t, e) => { + const s = t.getAttribute("style"), r = s == null ? void 0 : s.match(fw); + if (!r) + return null; + const i = Number(r[1]), a = s == null ? void 0 : s.match(pw), n = a ? Number(a[1]) : 1, o = new RegExp(`@list l${i}:level${n}\\s*\\{[^\\}]*mso-level-number-format:\\s*([\\w-]+)`, "i"), u = e.match(o), p = u && u[1] === "bullet" ? "bullet" : "ordered"; + return { + id: i, + indent: n, + type: p, + element: t + }; +}, gw = (t) => { + var n, o; + const e = Array.from(t.querySelectorAll("[style*=mso-list]")), s = [], r = []; + e.forEach((u) => { + (u.getAttribute("style") || "").match(cw) ? s.push(u) : r.push(u); + }), s.forEach((u) => { + var p; + return (p = u.parentNode) == null ? void 0 : p.removeChild(u); + }); + const i = t.documentElement.innerHTML, a = r.map((u) => hw(u, i)).filter((u) => u); + for (; a.length; ) { + const u = []; + let p = a.shift(); + for (; p; ) + u.push(p), p = a.length && ((n = a[0]) == null ? void 0 : n.element) === p.element.nextElementSibling && // Different id means the next item doesn't belong to this group. + a[0].id === p.id ? a.shift() : null; + const g = document.createElement("ul"); + u.forEach((b) => { + const v = document.createElement("li"); + v.setAttribute("data-list", b.type), b.indent > 1 && v.setAttribute("class", `ql-indent-${b.indent - 1}`), v.innerHTML = b.element.innerHTML, g.appendChild(v); + }); + const k = (o = u[0]) == null ? void 0 : o.element, { + parentNode: y + } = k ?? {}; + k && (y == null || y.replaceChild(g, k)), u.slice(1).forEach((b) => { + let { + element: v + } = b; + y == null || y.removeChild(v); + }); + } +}; +function mw(t) { + t.documentElement.getAttribute("xmlns:w") === "urn:schemas-microsoft-com:office:word" && gw(t); +} +const vw = [mw, dw], bw = (t) => { + t.documentElement && vw.forEach((e) => { + e(t); + }); +}, yw = ct("quill:clipboard"), $w = [[Node.TEXT_NODE, qw], [Node.TEXT_NODE, xa], ["br", Ew], [Node.ELEMENT_NODE, xa], [Node.ELEMENT_NODE, Aw], [Node.ELEMENT_NODE, Cw], [Node.ELEMENT_NODE, Iw], ["li", _w], ["ol, ul", Nw], ["pre", Sw], ["tr", Lw], ["b", oi("bold")], ["i", oi("italic")], ["strike", oi("strike")], ["style", Tw]], kw = [Qk, sl].reduce((t, e) => (t[e.keyName] = e, t), {}), Ja = [el, xi, Ji, rl, ol, ul].reduce((t, e) => (t[e.keyName] = e, t), {}); +class dl extends ze { + constructor(e, s) { + super(e, s), this.quill.root.addEventListener("copy", (r) => this.onCaptureCopy(r, !1)), this.quill.root.addEventListener("cut", (r) => this.onCaptureCopy(r, !0)), this.quill.root.addEventListener("paste", this.onCapturePaste.bind(this)), this.matchers = [], $w.concat(this.options.matchers ?? []).forEach((r) => { + let [i, a] = r; + this.addMatcher(i, a); + }); + } + addMatcher(e, s) { + this.matchers.push([e, s]); + } + convert(e) { + let { + html: s, + text: r + } = e, i = arguments.length > 1 && arguments[1] !== void 0 ? arguments[1] : {}; + if (i[me.blotName]) + return new j().insert(r || "", { + [me.blotName]: i[me.blotName] + }); + if (!s) + return new j().insert(r || "", i); + const a = this.convertHTML(s); + return Qs(a, ` +`) && (a.ops[a.ops.length - 1].attributes == null || i.table) ? a.compose(new j().retain(a.length() - 1).delete(1)) : a; + } + normalizeHTML(e) { + bw(e); + } + convertHTML(e) { + const s = new DOMParser().parseFromString(e, "text/html"); + this.normalizeHTML(s); + const r = s.body, i = /* @__PURE__ */ new WeakMap(), [a, n] = this.prepareMatching(r, i); + return na(this.quill.scroll, r, a, n, i); + } + dangerouslyPasteHTML(e, s) { + let r = arguments.length > 2 && arguments[2] !== void 0 ? arguments[2] : N.sources.API; + if (typeof e == "string") { + const i = this.convert({ + html: e, + text: "" + }); + this.quill.setContents(i, s), this.quill.setSelection(0, N.sources.SILENT); + } else { + const i = this.convert({ + html: s, + text: "" + }); + this.quill.updateContents(new j().retain(e).concat(i), r), this.quill.setSelection(e + i.length(), N.sources.SILENT); + } + } + onCaptureCopy(e) { + var n, o; + let s = arguments.length > 1 && arguments[1] !== void 0 ? arguments[1] : !1; + if (e.defaultPrevented) return; + e.preventDefault(); + const [r] = this.quill.selection.getRange(); + if (r == null) return; + const { + html: i, + text: a + } = this.onCopy(r, s); + (n = e.clipboardData) == null || n.setData("text/plain", a), (o = e.clipboardData) == null || o.setData("text/html", i), s && sa({ + range: r, + quill: this.quill + }); + } + /* + * https://www.iana.org/assignments/media-types/text/uri-list + */ + normalizeURIList(e) { + return e.split(/\r?\n/).filter((s) => s[0] !== "#").join(` +`); + } + onCapturePaste(e) { + var n, o, u, p, g; + if (e.defaultPrevented || !this.quill.isEnabled()) return; + e.preventDefault(); + const s = this.quill.getSelection(!0); + if (s == null) return; + const r = (n = e.clipboardData) == null ? void 0 : n.getData("text/html"); + let i = (o = e.clipboardData) == null ? void 0 : o.getData("text/plain"); + if (!r && !i) { + const k = (u = e.clipboardData) == null ? void 0 : u.getData("text/uri-list"); + k && (i = this.normalizeURIList(k)); + } + const a = Array.from(((p = e.clipboardData) == null ? void 0 : p.files) || []); + if (!r && a.length > 0) { + this.quill.uploader.upload(s, a); + return; + } + if (r && a.length > 0) { + const k = new DOMParser().parseFromString(r, "text/html"); + if (k.body.childElementCount === 1 && ((g = k.body.firstElementChild) == null ? void 0 : g.tagName) === "IMG") { + this.quill.uploader.upload(s, a); + return; + } + } + this.onPaste(s, { + html: r, + text: i + }); + } + onCopy(e) { + const s = this.quill.getText(e); + return { + html: this.quill.getSemanticHTML(e), + text: s + }; + } + onPaste(e, s) { + let { + text: r, + html: i + } = s; + const a = this.quill.getFormat(e.index), n = this.convert({ + text: r, + html: i + }, a); + yw.log("onPaste", n, { + text: r, + html: i + }); + const o = new j().retain(e.index).delete(e.length).concat(n); + this.quill.updateContents(o, N.sources.USER), this.quill.setSelection(o.length() - e.length, N.sources.SILENT), this.quill.scrollSelectionIntoView(); + } + prepareMatching(e, s) { + const r = [], i = []; + return this.matchers.forEach((a) => { + const [n, o] = a; + switch (n) { + case Node.TEXT_NODE: + i.push(o); + break; + case Node.ELEMENT_NODE: + r.push(o); + break; + default: + Array.from(e.querySelectorAll(n)).forEach((u) => { + if (s.has(u)) { + const p = s.get(u); + p == null || p.push(o); + } else + s.set(u, [o]); + }); + break; + } + }), [r, i]; + } +} +B(dl, "DEFAULTS", { + matchers: [] +}); +function Ut(t, e, s, r) { + return r.query(e) ? t.reduce((i, a) => { + if (!a.insert) return i; + if (a.attributes && a.attributes[e]) + return i.push(a); + const n = s ? { + [e]: s + } : {}; + return i.insert(a.insert, { + ...n, + ...a.attributes + }); + }, new j()) : t; +} +function Qs(t, e) { + let s = ""; + for (let r = t.ops.length - 1; r >= 0 && s.length < e.length; --r) { + const i = t.ops[r]; + if (typeof i.insert != "string") break; + s = i.insert + s; + } + return s.slice(-1 * e.length) === e; +} +function vt(t, e) { + if (!(t instanceof Element)) return !1; + const s = e.query(t); + return s && s.prototype instanceof Ae ? !1 : ["address", "article", "blockquote", "canvas", "dd", "div", "dl", "dt", "fieldset", "figcaption", "figure", "footer", "form", "h1", "h2", "h3", "h4", "h5", "h6", "header", "iframe", "li", "main", "nav", "ol", "output", "p", "pre", "section", "table", "td", "tr", "ul", "video"].includes(t.tagName.toLowerCase()); +} +function ww(t, e) { + return t.previousElementSibling && t.nextElementSibling && !vt(t.previousElementSibling, e) && !vt(t.nextElementSibling, e); +} +const Dn = /* @__PURE__ */ new WeakMap(); +function cl(t) { + return t == null ? !1 : (Dn.has(t) || (t.tagName === "PRE" ? Dn.set(t, !0) : Dn.set(t, cl(t.parentNode))), Dn.get(t)); +} +function na(t, e, s, r, i) { + return e.nodeType === e.TEXT_NODE ? r.reduce((a, n) => n(e, a, t), new j()) : e.nodeType === e.ELEMENT_NODE ? Array.from(e.childNodes || []).reduce((a, n) => { + let o = na(t, n, s, r, i); + return n.nodeType === e.ELEMENT_NODE && (o = s.reduce((u, p) => p(n, u, t), o), o = (i.get(n) || []).reduce((u, p) => p(n, u, t), o)), a.concat(o); + }, new j()) : new j(); +} +function oi(t) { + return (e, s, r) => Ut(s, t, !0, r); +} +function Cw(t, e, s) { + const r = Je.keys(t), i = Ve.keys(t), a = wt.keys(t), n = {}; + return r.concat(i).concat(a).forEach((o) => { + let u = s.query(o, V.ATTRIBUTE); + u != null && (n[u.attrName] = u.value(t), n[u.attrName]) || (u = kw[o], u != null && (u.attrName === o || u.keyName === o) && (n[u.attrName] = u.value(t) || void 0), u = Ja[o], u != null && (u.attrName === o || u.keyName === o) && (u = Ja[o], n[u.attrName] = u.value(t) || void 0)); + }), Object.entries(n).reduce((o, u) => { + let [p, g] = u; + return Ut(o, p, g, s); + }, e); +} +function Aw(t, e, s) { + const r = s.query(t); + if (r == null) return e; + if (r.prototype instanceof Ae) { + const i = {}, a = r.value(t); + if (a != null) + return i[r.blotName] = a, new j().insert(i, r.formats(t, s)); + } else if (r.prototype instanceof Gs && !Qs(e, ` +`) && e.insert(` +`), "blotName" in r && "formats" in r && typeof r.formats == "function") + return Ut(e, r.blotName, r.formats(t, s), s); + return e; +} +function Ew(t, e) { + return Qs(e, ` +`) || e.insert(` +`), e; +} +function Sw(t, e, s) { + const r = s.query("code-block"), i = r && "formats" in r && typeof r.formats == "function" ? r.formats(t, s) : !0; + return Ut(e, "code-block", i, s); +} +function Tw() { + return new j(); +} +function _w(t, e, s) { + const r = s.query(t); + if (r == null || // @ts-expect-error + r.blotName !== "list" || !Qs(e, ` +`)) + return e; + let i = -1, a = t.parentNode; + for (; a != null; ) + ["OL", "UL"].includes(a.tagName) && (i += 1), a = a.parentNode; + return i <= 0 ? e : e.reduce((n, o) => o.insert ? o.attributes && typeof o.attributes.indent == "number" ? n.push(o) : n.insert(o.insert, { + indent: i, + ...o.attributes || {} + }) : n, new j()); +} +function Nw(t, e, s) { + const r = t; + let i = r.tagName === "OL" ? "ordered" : "bullet"; + const a = r.getAttribute("data-checked"); + return a && (i = a === "true" ? "checked" : "unchecked"), Ut(e, "list", i, s); +} +function xa(t, e, s) { + if (!Qs(e, ` +`)) { + if (vt(t, s) && (t.childNodes.length > 0 || t instanceof HTMLParagraphElement)) + return e.insert(` +`); + if (e.length() > 0 && t.nextSibling) { + let r = t.nextSibling; + for (; r != null; ) { + if (vt(r, s)) + return e.insert(` +`); + const i = s.query(r); + if (i && i.prototype instanceof Ne) + return e.insert(` +`); + r = r.firstChild; + } + } + } + return e; +} +function Iw(t, e, s) { + var a; + const r = {}, i = t.style || {}; + return i.fontStyle === "italic" && (r.italic = !0), i.textDecoration === "underline" && (r.underline = !0), i.textDecoration === "line-through" && (r.strike = !0), ((a = i.fontWeight) != null && a.startsWith("bold") || // @ts-expect-error Fix me later + parseInt(i.fontWeight, 10) >= 700) && (r.bold = !0), e = Object.entries(r).reduce((n, o) => { + let [u, p] = o; + return Ut(n, u, p, s); + }, e), parseFloat(i.textIndent || 0) > 0 ? new j().insert(" ").concat(e) : e; +} +function Lw(t, e, s) { + var i, a; + const r = ((i = t.parentElement) == null ? void 0 : i.tagName) === "TABLE" ? t.parentElement : (a = t.parentElement) == null ? void 0 : a.parentElement; + if (r != null) { + const o = Array.from(r.querySelectorAll("tr")).indexOf(t) + 1; + return Ut(e, "table", o, s); + } + return e; +} +function qw(t, e, s) { + var i; + let r = t.data; + if (((i = t.parentElement) == null ? void 0 : i.tagName) === "O:P") + return e.insert(r.trim()); + if (!cl(t)) { + if (r.trim().length === 0 && r.includes(` +`) && !ww(t, s)) + return e; + r = r.replace(/[^\S\u00a0]/g, " "), r = r.replace(/ {2,}/g, " "), (t.previousSibling == null && t.parentElement != null && vt(t.parentElement, s) || t.previousSibling instanceof Element && vt(t.previousSibling, s)) && (r = r.replace(/^ /, "")), (t.nextSibling == null && t.parentElement != null && vt(t.parentElement, s) || t.nextSibling instanceof Element && vt(t.nextSibling, s)) && (r = r.replace(/ $/, "")), r = r.replaceAll(" ", " "); + } + return e.insert(r); +} +class fl extends ze { + constructor(s, r) { + super(s, r); + B(this, "lastRecorded", 0); + B(this, "ignoreChange", !1); + B(this, "stack", { + undo: [], + redo: [] + }); + B(this, "currentRange", null); + this.quill.on(N.events.EDITOR_CHANGE, (i, a, n, o) => { + i === N.events.SELECTION_CHANGE ? a && o !== N.sources.SILENT && (this.currentRange = a) : i === N.events.TEXT_CHANGE && (this.ignoreChange || (!this.options.userOnly || o === N.sources.USER ? this.record(a, n) : this.transform(a)), this.currentRange = Ti(this.currentRange, a)); + }), this.quill.keyboard.addBinding({ + key: "z", + shortKey: !0 + }, this.undo.bind(this)), this.quill.keyboard.addBinding({ + key: ["z", "Z"], + shortKey: !0, + shiftKey: !0 + }, this.redo.bind(this)), /Win/i.test(navigator.platform) && this.quill.keyboard.addBinding({ + key: "y", + shortKey: !0 + }, this.redo.bind(this)), this.quill.root.addEventListener("beforeinput", (i) => { + i.inputType === "historyUndo" ? (this.undo(), i.preventDefault()) : i.inputType === "historyRedo" && (this.redo(), i.preventDefault()); + }); + } + change(s, r) { + if (this.stack[s].length === 0) return; + const i = this.stack[s].pop(); + if (!i) return; + const a = this.quill.getContents(), n = i.delta.invert(a); + this.stack[r].push({ + delta: n, + range: Ti(i.range, n) + }), this.lastRecorded = 0, this.ignoreChange = !0, this.quill.updateContents(i.delta, N.sources.USER), this.ignoreChange = !1, this.restoreSelection(i); + } + clear() { + this.stack = { + undo: [], + redo: [] + }; + } + cutoff() { + this.lastRecorded = 0; + } + record(s, r) { + if (s.ops.length === 0) return; + this.stack.redo = []; + let i = s.invert(r), a = this.currentRange; + const n = Date.now(); + if ( + // @ts-expect-error Fix me later + this.lastRecorded + this.options.delay > n && this.stack.undo.length > 0 + ) { + const o = this.stack.undo.pop(); + o && (i = i.compose(o.delta), a = o.range); + } else + this.lastRecorded = n; + i.length() !== 0 && (this.stack.undo.push({ + delta: i, + range: a + }), this.stack.undo.length > this.options.maxStack && this.stack.undo.shift()); + } + redo() { + this.change("redo", "undo"); + } + transform(s) { + eo(this.stack.undo, s), eo(this.stack.redo, s); + } + undo() { + this.change("undo", "redo"); + } + restoreSelection(s) { + if (s.range) + this.quill.setSelection(s.range, N.sources.USER); + else { + const r = Pw(this.quill.scroll, s.delta); + this.quill.setSelection(r, N.sources.USER); + } + } +} +B(fl, "DEFAULTS", { + delay: 1e3, + maxStack: 100, + userOnly: !1 +}); +function eo(t, e) { + let s = e; + for (let r = t.length - 1; r >= 0; r -= 1) { + const i = t[r]; + t[r] = { + delta: s.transform(i.delta, !0), + range: i.range && Ti(i.range, s) + }, s = i.delta.transform(s), t[r].delta.length() === 0 && t.splice(r, 1); + } +} +function Ow(t, e) { + const s = e.ops[e.ops.length - 1]; + return s == null ? !1 : s.insert != null ? typeof s.insert == "string" && s.insert.endsWith(` +`) : s.attributes != null ? Object.keys(s.attributes).some((r) => t.query(r, V.BLOCK) != null) : !1; +} +function Pw(t, e) { + const s = e.reduce((i, a) => i + (a.delete || 0), 0); + let r = e.length() - s; + return Ow(t, e) && (r -= 1), r; +} +function Ti(t, e) { + if (!t) return t; + const s = e.transformPosition(t.index), r = e.transformPosition(t.index + t.length); + return { + index: s, + length: r - s + }; +} +class pl extends ze { + constructor(e, s) { + super(e, s), e.root.addEventListener("drop", (r) => { + var n; + r.preventDefault(); + let i = null; + if (document.caretRangeFromPoint) + i = document.caretRangeFromPoint(r.clientX, r.clientY); + else if (document.caretPositionFromPoint) { + const o = document.caretPositionFromPoint(r.clientX, r.clientY); + i = document.createRange(), i.setStart(o.offsetNode, o.offset), i.setEnd(o.offsetNode, o.offset); + } + const a = i && e.selection.normalizeNative(i); + if (a) { + const o = e.selection.normalizedToRange(a); + (n = r.dataTransfer) != null && n.files && this.upload(o, r.dataTransfer.files); + } + }); + } + upload(e, s) { + const r = []; + Array.from(s).forEach((i) => { + var a; + i && ((a = this.options.mimetypes) != null && a.includes(i.type)) && r.push(i); + }), r.length > 0 && this.options.handler.call(this, e, r); + } +} +pl.DEFAULTS = { + mimetypes: ["image/png", "image/jpeg"], + handler(t, e) { + if (!this.quill.scroll.query("image")) + return; + const s = e.map((r) => new Promise((i) => { + const a = new FileReader(); + a.onload = () => { + i(a.result); + }, a.readAsDataURL(r); + })); + Promise.all(s).then((r) => { + const i = r.reduce((a, n) => a.insert({ + image: n + }), new j().retain(t.index).delete(t.length)); + this.quill.updateContents(i, F.sources.USER), this.quill.setSelection(t.index + r.length, F.sources.SILENT); + }); + } +}; +const Dw = ["insertText", "insertReplacementText"]; +class Rw extends ze { + constructor(e, s) { + super(e, s), e.root.addEventListener("beforeinput", (r) => { + this.handleBeforeInput(r); + }), /Android/i.test(navigator.userAgent) || e.on(N.events.COMPOSITION_BEFORE_START, () => { + this.handleCompositionStart(); + }); + } + deleteRange(e) { + sa({ + range: e, + quill: this.quill + }); + } + replaceText(e) { + let s = arguments.length > 1 && arguments[1] !== void 0 ? arguments[1] : ""; + if (e.length === 0) return !1; + if (s) { + const r = this.quill.getFormat(e.index, 1); + this.deleteRange(e), this.quill.updateContents(new j().retain(e.index).insert(s, r), N.sources.USER); + } else + this.deleteRange(e); + return this.quill.setSelection(e.index + s.length, 0, N.sources.SILENT), !0; + } + handleBeforeInput(e) { + if (this.quill.composition.isComposing || e.defaultPrevented || !Dw.includes(e.inputType)) + return; + const s = e.getTargetRanges ? e.getTargetRanges()[0] : null; + if (!s || s.collapsed === !0) + return; + const r = Bw(e); + if (r == null) + return; + const i = this.quill.selection.normalizeNative(s), a = i ? this.quill.selection.normalizedToRange(i) : null; + a && this.replaceText(a, r) && e.preventDefault(); + } + handleCompositionStart() { + const e = this.quill.getSelection(); + e && this.replaceText(e); + } +} +function Bw(t) { + var e; + return typeof t.data == "string" ? t.data : (e = t.dataTransfer) != null && e.types.includes("text/plain") ? t.dataTransfer.getData("text/plain") : null; +} +const Mw = /Mac/i.test(navigator.platform), Fw = 100, Uw = (t) => !!(t.key === "ArrowLeft" || t.key === "ArrowRight" || // RTL scripts or moving from the end of the previous line +t.key === "ArrowUp" || t.key === "ArrowDown" || t.key === "Home" || Mw && t.key === "a" && t.ctrlKey === !0); +class jw extends ze { + constructor(s, r) { + super(s, r); + B(this, "isListening", !1); + B(this, "selectionChangeDeadline", 0); + this.handleArrowKeys(), this.handleNavigationShortcuts(); + } + handleArrowKeys() { + this.quill.keyboard.addBinding({ + key: ["ArrowLeft", "ArrowRight"], + offset: 0, + shiftKey: null, + handler(s, r) { + let { + line: i, + event: a + } = r; + if (!(i instanceof Ue) || !i.uiNode) + return !0; + const n = getComputedStyle(i.domNode).direction === "rtl"; + return n && a.key !== "ArrowRight" || !n && a.key !== "ArrowLeft" ? !0 : (this.quill.setSelection(s.index - 1, s.length + (a.shiftKey ? 1 : 0), N.sources.USER), !1); + } + }); + } + handleNavigationShortcuts() { + this.quill.root.addEventListener("keydown", (s) => { + !s.defaultPrevented && Uw(s) && this.ensureListeningToSelectionChange(); + }); + } + /** + * We only listen to the `selectionchange` event when + * there is an intention of moving the caret to the beginning using shortcuts. + * This is primarily implemented to prevent infinite loops, as we are changing + * the selection within the handler of a `selectionchange` event. + */ + ensureListeningToSelectionChange() { + if (this.selectionChangeDeadline = Date.now() + Fw, this.isListening) return; + this.isListening = !0; + const s = () => { + this.isListening = !1, Date.now() <= this.selectionChangeDeadline && this.handleSelectionChange(); + }; + document.addEventListener("selectionchange", s, { + once: !0 + }); + } + handleSelectionChange() { + const s = document.getSelection(); + if (!s) return; + const r = s.getRangeAt(0); + if (r.collapsed !== !0 || r.startOffset !== 0) return; + const i = this.quill.scroll.find(r.startContainer); + if (!(i instanceof Ue) || !i.uiNode) return; + const a = document.createRange(); + a.setStartAfter(i.uiNode), a.setEndAfter(i.uiNode), s.removeAllRanges(), s.addRange(a); + } +} +N.register({ + "blots/block": ce, + "blots/block/embed": Ne, + "blots/break": He, + "blots/container": Mt, + "blots/cursor": us, + "blots/embed": Yi, + "blots/inline": xe, + "blots/scroll": ns, + "blots/text": je, + "modules/clipboard": dl, + "modules/history": fl, + "modules/keyboard": xn, + "modules/uploader": pl, + "modules/input": Rw, + "modules/uiNode": jw +}); +class Vw extends Ve { + add(e, s) { + let r = 0; + if (s === "+1" || s === "-1") { + const i = this.value(e) || 0; + r = s === "+1" ? i + 1 : i - 1; + } else typeof s == "number" && (r = s); + return r === 0 ? (this.remove(e), !0) : super.add(e, r.toString()); + } + canAdd(e, s) { + return super.canAdd(e, s) || super.canAdd(e, parseInt(s, 10)); + } + value(e) { + return parseInt(super.value(e), 10) || void 0; + } +} +const Hw = new Vw("indent", "ql-indent", { + scope: V.BLOCK, + // @ts-expect-error + whitelist: [1, 2, 3, 4, 5, 6, 7, 8] +}); +class _i extends ce { +} +B(_i, "blotName", "blockquote"), B(_i, "tagName", "blockquote"); +class Ni extends ce { + static formats(e) { + return this.tagName.indexOf(e.tagName) + 1; + } +} +B(Ni, "blotName", "header"), B(Ni, "tagName", ["H1", "H2", "H3", "H4", "H5", "H6"]); +class Js extends Mt { +} +Js.blotName = "list-container"; +Js.tagName = "OL"; +class xs extends ce { + static create(e) { + const s = super.create(); + return s.setAttribute("data-list", e), s; + } + static formats(e) { + return e.getAttribute("data-list") || void 0; + } + static register() { + N.register(Js); + } + constructor(e, s) { + super(e, s); + const r = s.ownerDocument.createElement("span"), i = (a) => { + if (!e.isEnabled()) return; + const n = this.statics.formats(s, e); + n === "checked" ? (this.format("list", "unchecked"), a.preventDefault()) : n === "unchecked" && (this.format("list", "checked"), a.preventDefault()); + }; + r.addEventListener("mousedown", i), r.addEventListener("touchstart", i), this.attachUI(r); + } + format(e, s) { + e === this.statics.blotName && s ? this.domNode.setAttribute("data-list", s) : super.format(e, s); + } +} +xs.blotName = "list"; +xs.tagName = "LI"; +Js.allowedChildren = [xs]; +xs.requiredContainer = Js; +class Zs extends xe { + static create() { + return super.create(); + } + static formats() { + return !0; + } + optimize(e) { + super.optimize(e), this.domNode.tagName !== this.statics.tagName[0] && this.replaceWith(this.statics.blotName); + } +} +B(Zs, "blotName", "bold"), B(Zs, "tagName", ["STRONG", "B"]); +class Ii extends Zs { +} +B(Ii, "blotName", "italic"), B(Ii, "tagName", ["EM", "I"]); +class bt extends xe { + static create(e) { + const s = super.create(e); + return s.setAttribute("href", this.sanitize(e)), s.setAttribute("rel", "noopener noreferrer"), s.setAttribute("target", "_blank"), s; + } + static formats(e) { + return e.getAttribute("href"); + } + static sanitize(e) { + return hl(e, this.PROTOCOL_WHITELIST) ? e : this.SANITIZED_URL; + } + format(e, s) { + e !== this.statics.blotName || !s ? super.format(e, s) : this.domNode.setAttribute("href", this.constructor.sanitize(s)); + } +} +B(bt, "blotName", "link"), B(bt, "tagName", "A"), B(bt, "SANITIZED_URL", "about:blank"), B(bt, "PROTOCOL_WHITELIST", ["http", "https", "mailto", "tel", "sms"]); +function hl(t, e) { + const s = document.createElement("a"); + s.href = t; + const r = s.href.slice(0, s.href.indexOf(":")); + return e.indexOf(r) > -1; +} +class Li extends xe { + static create(e) { + return e === "super" ? document.createElement("sup") : e === "sub" ? document.createElement("sub") : super.create(e); + } + static formats(e) { + if (e.tagName === "SUB") return "sub"; + if (e.tagName === "SUP") return "super"; + } +} +B(Li, "blotName", "script"), B(Li, "tagName", ["SUB", "SUP"]); +class qi extends Zs { +} +B(qi, "blotName", "strike"), B(qi, "tagName", ["S", "STRIKE"]); +class Oi extends xe { +} +B(Oi, "blotName", "underline"), B(Oi, "tagName", "U"); +class Bn extends Yi { + static create(e) { + if (window.katex == null) + throw new Error("Formula module requires KaTeX."); + const s = super.create(e); + return typeof e == "string" && (window.katex.render(e, s, { + throwOnError: !1, + errorColor: "#f00" + }), s.setAttribute("data-value", e)), s; + } + static value(e) { + return e.getAttribute("data-value"); + } + html() { + const { + formula: e + } = this.value(); + return `${e}`; + } +} +B(Bn, "blotName", "formula"), B(Bn, "className", "ql-formula"), B(Bn, "tagName", "SPAN"); +const to = ["alt", "height", "width"]; +class Pi extends Ae { + static create(e) { + const s = super.create(e); + return typeof e == "string" && s.setAttribute("src", this.sanitize(e)), s; + } + static formats(e) { + return to.reduce((s, r) => (e.hasAttribute(r) && (s[r] = e.getAttribute(r)), s), {}); + } + static match(e) { + return /\.(jpe?g|gif|png)$/.test(e) || /^data:image\/.+;base64/.test(e); + } + static sanitize(e) { + return hl(e, ["http", "https", "data"]) ? e : "//:0"; + } + static value(e) { + return e.getAttribute("src"); + } + format(e, s) { + to.indexOf(e) > -1 ? s ? this.domNode.setAttribute(e, s) : this.domNode.removeAttribute(e) : super.format(e, s); + } +} +B(Pi, "blotName", "image"), B(Pi, "tagName", "IMG"); +const so = ["height", "width"]; +var Ds; +let zw = (Ds = class extends Ne { + static create(e) { + const s = super.create(e); + return s.setAttribute("frameborder", "0"), s.setAttribute("allowfullscreen", "true"), s.setAttribute("src", this.sanitize(e)), s; + } + static formats(e) { + return so.reduce((s, r) => (e.hasAttribute(r) && (s[r] = e.getAttribute(r)), s), {}); + } + static sanitize(e) { + return bt.sanitize(e); + } + static value(e) { + return e.getAttribute("src"); + } + format(e, s) { + so.indexOf(e) > -1 ? s ? this.domNode.setAttribute(e, s) : this.domNode.removeAttribute(e) : super.format(e, s); + } + html() { + const { + video: e + } = this.value(); + return `${e}`; + } +}, B(Ds, "blotName", "video"), B(Ds, "className", "ql-video"), B(Ds, "tagName", "IFRAME"), Ds); +const Bs = new Ve("code-token", "hljs", { + scope: V.INLINE +}); +class lt extends xe { + static formats(e, s) { + for (; e != null && e !== s.domNode; ) { + if (e.classList && e.classList.contains(me.className)) + return super.formats(e, s); + e = e.parentNode; + } + } + constructor(e, s, r) { + super(e, s, r), Bs.add(this.domNode, r); + } + format(e, s) { + e !== lt.blotName ? super.format(e, s) : s ? Bs.add(this.domNode, s) : (Bs.remove(this.domNode), this.domNode.classList.remove(this.statics.className)); + } + optimize() { + super.optimize(...arguments), Bs.value(this.domNode) || this.unwrap(); + } +} +lt.blotName = "code-token"; +lt.className = "ql-token"; +class _e extends me { + static create(e) { + const s = super.create(e); + return typeof e == "string" && s.setAttribute("data-language", e), s; + } + static formats(e) { + return e.getAttribute("data-language") || "plain"; + } + static register() { + } + // Syntax module will register + format(e, s) { + e === this.statics.blotName && s ? this.domNode.setAttribute("data-language", s) : super.format(e, s); + } + replaceWith(e, s) { + return this.formatAt(0, this.length(), lt.blotName, !1), super.replaceWith(e, s); + } +} +class Fs extends Ft { + attach() { + super.attach(), this.forceNext = !1, this.scroll.emitMount(this); + } + format(e, s) { + e === _e.blotName && (this.forceNext = !0, this.children.forEach((r) => { + r.format(e, s); + })); + } + formatAt(e, s, r, i) { + r === _e.blotName && (this.forceNext = !0), super.formatAt(e, s, r, i); + } + highlight(e) { + let s = arguments.length > 1 && arguments[1] !== void 0 ? arguments[1] : !1; + if (this.children.head == null) return; + const i = `${Array.from(this.domNode.childNodes).filter((n) => n !== this.uiNode).map((n) => n.textContent).join(` +`)} +`, a = _e.formats(this.children.head.domNode); + if (s || this.forceNext || this.cachedText !== i) { + if (i.trim().length > 0 || this.cachedText == null) { + const n = this.children.reduce((u, p) => u.concat(Yo(p, !1)), new j()), o = e(i, a); + n.diff(o).reduce((u, p) => { + let { + retain: g, + attributes: k + } = p; + return g ? (k && Object.keys(k).forEach((y) => { + [_e.blotName, lt.blotName].includes(y) && this.formatAt(u, g, y, k[y]); + }), u + g) : u; + }, 0); + } + this.cachedText = i, this.forceNext = !1; + } + } + html(e, s) { + const [r] = this.children.find(e); + return `
    +${Jn(this.code(e, s))}
    +
    `; + } + optimize(e) { + if (super.optimize(e), this.parent != null && this.children.head != null && this.uiNode != null) { + const s = _e.formats(this.children.head.domNode); + s !== this.uiNode.value && (this.uiNode.value = s); + } + } +} +Fs.allowedChildren = [_e]; +_e.requiredContainer = Fs; +_e.allowedChildren = [lt, us, je, He]; +const Gw = (t, e, s) => { + if (typeof t.versionString == "string") { + const r = t.versionString.split(".")[0]; + if (parseInt(r, 10) >= 11) + return t.highlight(s, { + language: e + }).value; + } + return t.highlight(e, s).value; +}; +class gl extends ze { + static register() { + N.register(lt, !0), N.register(_e, !0), N.register(Fs, !0); + } + constructor(e, s) { + if (super(e, s), this.options.hljs == null) + throw new Error("Syntax module requires highlight.js. Please include the library on the page before Quill."); + this.languages = this.options.languages.reduce((r, i) => { + let { + key: a + } = i; + return r[a] = !0, r; + }, {}), this.highlightBlot = this.highlightBlot.bind(this), this.initListener(), this.initTimer(); + } + initListener() { + this.quill.on(N.events.SCROLL_BLOT_MOUNT, (e) => { + if (!(e instanceof Fs)) return; + const s = this.quill.root.ownerDocument.createElement("select"); + this.options.languages.forEach((r) => { + let { + key: i, + label: a + } = r; + const n = s.ownerDocument.createElement("option"); + n.textContent = a, n.setAttribute("value", i), s.appendChild(n); + }), s.addEventListener("change", () => { + e.format(_e.blotName, s.value), this.quill.root.focus(), this.highlight(e, !0); + }), e.uiNode == null && (e.attachUI(s), e.children.head && (s.value = _e.formats(e.children.head.domNode))); + }); + } + initTimer() { + let e = null; + this.quill.on(N.events.SCROLL_OPTIMIZE, () => { + e && clearTimeout(e), e = setTimeout(() => { + this.highlight(), e = null; + }, this.options.interval); + }); + } + highlight() { + let e = arguments.length > 0 && arguments[0] !== void 0 ? arguments[0] : null, s = arguments.length > 1 && arguments[1] !== void 0 ? arguments[1] : !1; + if (this.quill.selection.composing) return; + this.quill.update(N.sources.USER); + const r = this.quill.getSelection(); + (e == null ? this.quill.scroll.descendants(Fs) : [e]).forEach((a) => { + a.highlight(this.highlightBlot, s); + }), this.quill.update(N.sources.SILENT), r != null && this.quill.setSelection(r, N.sources.SILENT); + } + highlightBlot(e) { + let s = arguments.length > 1 && arguments[1] !== void 0 ? arguments[1] : "plain"; + if (s = this.languages[s] ? s : "plain", s === "plain") + return Jn(e).split(` +`).reduce((i, a, n) => (n !== 0 && i.insert(` +`, { + [me.blotName]: s + }), i.insert(a)), new j()); + const r = this.quill.root.ownerDocument.createElement("div"); + return r.classList.add(me.className), r.innerHTML = Gw(this.options.hljs, s, e), na(this.quill.scroll, r, [(i, a) => { + const n = Bs.value(i); + return n ? a.compose(new j().retain(a.length(), { + [lt.blotName]: n + })) : a; + }], [(i, a) => i.data.split(` +`).reduce((n, o, u) => (u !== 0 && n.insert(` +`, { + [me.blotName]: s + }), n.insert(o)), a)], /* @__PURE__ */ new WeakMap()); + } +} +gl.DEFAULTS = { + hljs: window.hljs, + interval: 1e3, + languages: [{ + key: "plain", + label: "Plain" + }, { + key: "bash", + label: "Bash" + }, { + key: "cpp", + label: "C++" + }, { + key: "cs", + label: "C#" + }, { + key: "css", + label: "CSS" + }, { + key: "diff", + label: "Diff" + }, { + key: "xml", + label: "HTML/XML" + }, { + key: "java", + label: "Java" + }, { + key: "javascript", + label: "JavaScript" + }, { + key: "markdown", + label: "Markdown" + }, { + key: "php", + label: "PHP" + }, { + key: "python", + label: "Python" + }, { + key: "ruby", + label: "Ruby" + }, { + key: "sql", + label: "SQL" + }] +}; +const js = class js extends ce { + static create(e) { + const s = super.create(); + return e ? s.setAttribute("data-row", e) : s.setAttribute("data-row", ra()), s; + } + static formats(e) { + if (e.hasAttribute("data-row")) + return e.getAttribute("data-row"); + } + cellOffset() { + return this.parent ? this.parent.children.indexOf(this) : -1; + } + format(e, s) { + e === js.blotName && s ? this.domNode.setAttribute("data-row", s) : super.format(e, s); + } + row() { + return this.parent; + } + rowOffset() { + return this.row() ? this.row().rowOffset() : -1; + } + table() { + return this.row() && this.row().table(); + } +}; +B(js, "blotName", "table"), B(js, "tagName", "TD"); +let Me = js; +class ut extends Mt { + checkMerge() { + if (super.checkMerge() && this.next.children.head != null) { + const e = this.children.head.formats(), s = this.children.tail.formats(), r = this.next.children.head.formats(), i = this.next.children.tail.formats(); + return e.table === s.table && e.table === r.table && e.table === i.table; + } + return !1; + } + optimize(e) { + super.optimize(e), this.children.forEach((s) => { + if (s.next == null) return; + const r = s.formats(), i = s.next.formats(); + if (r.table !== i.table) { + const a = this.splitAfter(s); + a && a.optimize(), this.prev && this.prev.optimize(); + } + }); + } + rowOffset() { + return this.parent ? this.parent.children.indexOf(this) : -1; + } + table() { + return this.parent && this.parent.parent; + } +} +B(ut, "blotName", "table-row"), B(ut, "tagName", "TR"); +class Qe extends Mt { +} +B(Qe, "blotName", "table-body"), B(Qe, "tagName", "TBODY"); +class cs extends Mt { + balanceCells() { + const e = this.descendants(ut), s = e.reduce((r, i) => Math.max(i.children.length, r), 0); + e.forEach((r) => { + new Array(s - r.children.length).fill(0).forEach(() => { + let i; + r.children.head != null && (i = Me.formats(r.children.head.domNode)); + const a = this.scroll.create(Me.blotName, i); + r.appendChild(a), a.optimize(); + }); + }); + } + cells(e) { + return this.rows().map((s) => s.children.at(e)); + } + deleteColumn(e) { + const [s] = this.descendant(Qe); + s == null || s.children.head == null || s.children.forEach((r) => { + const i = r.children.at(e); + i != null && i.remove(); + }); + } + insertColumn(e) { + const [s] = this.descendant(Qe); + s == null || s.children.head == null || s.children.forEach((r) => { + const i = r.children.at(e), a = Me.formats(r.children.head.domNode), n = this.scroll.create(Me.blotName, a); + r.insertBefore(n, i); + }); + } + insertRow(e) { + const [s] = this.descendant(Qe); + if (s == null || s.children.head == null) return; + const r = ra(), i = this.scroll.create(ut.blotName); + s.children.head.children.forEach(() => { + const n = this.scroll.create(Me.blotName, r); + i.appendChild(n); + }); + const a = s.children.at(e); + s.insertBefore(i, a); + } + rows() { + const e = this.children.head; + return e == null ? [] : e.children.map((s) => s); + } +} +B(cs, "blotName", "table-container"), B(cs, "tagName", "TABLE"); +cs.allowedChildren = [Qe]; +Qe.requiredContainer = cs; +Qe.allowedChildren = [ut]; +ut.requiredContainer = Qe; +ut.allowedChildren = [Me]; +Me.requiredContainer = ut; +function ra() { + return `row-${Math.random().toString(36).slice(2, 6)}`; +} +class Kw extends ze { + static register() { + N.register(Me), N.register(ut), N.register(Qe), N.register(cs); + } + constructor() { + super(...arguments), this.listenBalanceCells(); + } + balanceTables() { + this.quill.scroll.descendants(cs).forEach((e) => { + e.balanceCells(); + }); + } + deleteColumn() { + const [e, , s] = this.getTable(); + s != null && (e.deleteColumn(s.cellOffset()), this.quill.update(N.sources.USER)); + } + deleteRow() { + const [, e] = this.getTable(); + e != null && (e.remove(), this.quill.update(N.sources.USER)); + } + deleteTable() { + const [e] = this.getTable(); + if (e == null) return; + const s = e.offset(); + e.remove(), this.quill.update(N.sources.USER), this.quill.setSelection(s, N.sources.SILENT); + } + getTable() { + let e = arguments.length > 0 && arguments[0] !== void 0 ? arguments[0] : this.quill.getSelection(); + if (e == null) return [null, null, null, -1]; + const [s, r] = this.quill.getLine(e.index); + if (s == null || s.statics.blotName !== Me.blotName) + return [null, null, null, -1]; + const i = s.parent; + return [i.parent.parent, i, s, r]; + } + insertColumn(e) { + const s = this.quill.getSelection(); + if (!s) return; + const [r, i, a] = this.getTable(s); + if (a == null) return; + const n = a.cellOffset(); + r.insertColumn(n + e), this.quill.update(N.sources.USER); + let o = i.rowOffset(); + e === 0 && (o += 1), this.quill.setSelection(s.index + o, s.length, N.sources.SILENT); + } + insertColumnLeft() { + this.insertColumn(0); + } + insertColumnRight() { + this.insertColumn(1); + } + insertRow(e) { + const s = this.quill.getSelection(); + if (!s) return; + const [r, i, a] = this.getTable(s); + if (a == null) return; + const n = i.rowOffset(); + r.insertRow(n + e), this.quill.update(N.sources.USER), e > 0 ? this.quill.setSelection(s, N.sources.SILENT) : this.quill.setSelection(s.index + i.children.length, s.length, N.sources.SILENT); + } + insertRowAbove() { + this.insertRow(0); + } + insertRowBelow() { + this.insertRow(1); + } + insertTable(e, s) { + const r = this.quill.getSelection(); + if (r == null) return; + const i = new Array(e).fill(0).reduce((a) => { + const n = new Array(s).fill(` +`).join(""); + return a.insert(n, { + table: ra() + }); + }, new j().retain(r.index)); + this.quill.updateContents(i, N.sources.USER), this.quill.setSelection(r.index, N.sources.SILENT), this.balanceTables(); + } + listenBalanceCells() { + this.quill.on(N.events.SCROLL_OPTIMIZE, (e) => { + e.some((s) => ["TD", "TR", "TBODY", "TABLE"].includes(s.target.tagName) ? (this.quill.once(N.events.TEXT_CHANGE, (r, i, a) => { + a === N.sources.USER && this.balanceTables(); + }), !0) : !1); + }); + } +} +const no = ct("quill:toolbar"); +class ia extends ze { + constructor(e, s) { + var r, i; + if (super(e, s), Array.isArray(this.options.container)) { + const a = document.createElement("div"); + a.setAttribute("role", "toolbar"), Ww(a, this.options.container), (i = (r = e.container) == null ? void 0 : r.parentNode) == null || i.insertBefore(a, e.container), this.container = a; + } else typeof this.options.container == "string" ? this.container = document.querySelector(this.options.container) : this.container = this.options.container; + if (!(this.container instanceof HTMLElement)) { + no.error("Container required for toolbar", this.options); + return; + } + this.container.classList.add("ql-toolbar"), this.controls = [], this.handlers = {}, this.options.handlers && Object.keys(this.options.handlers).forEach((a) => { + var o; + const n = (o = this.options.handlers) == null ? void 0 : o[a]; + n && this.addHandler(a, n); + }), Array.from(this.container.querySelectorAll("button, select")).forEach((a) => { + this.attach(a); + }), this.quill.on(N.events.EDITOR_CHANGE, () => { + const [a] = this.quill.selection.getRange(); + this.update(a); + }); + } + addHandler(e, s) { + this.handlers[e] = s; + } + attach(e) { + let s = Array.from(e.classList).find((i) => i.indexOf("ql-") === 0); + if (!s) return; + if (s = s.slice(3), e.tagName === "BUTTON" && e.setAttribute("type", "button"), this.handlers[s] == null && this.quill.scroll.query(s) == null) { + no.warn("ignoring attaching to nonexistent format", s, e); + return; + } + const r = e.tagName === "SELECT" ? "change" : "click"; + e.addEventListener(r, (i) => { + let a; + if (e.tagName === "SELECT") { + if (e.selectedIndex < 0) return; + const o = e.options[e.selectedIndex]; + o.hasAttribute("selected") ? a = !1 : a = o.value || !1; + } else + e.classList.contains("ql-active") ? a = !1 : a = e.value || !e.hasAttribute("value"), i.preventDefault(); + this.quill.focus(); + const [n] = this.quill.selection.getRange(); + if (this.handlers[s] != null) + this.handlers[s].call(this, a); + else if ( + // @ts-expect-error + this.quill.scroll.query(s).prototype instanceof Ae + ) { + if (a = prompt(`Enter ${s}`), !a) return; + this.quill.updateContents(new j().retain(n.index).delete(n.length).insert({ + [s]: a + }), N.sources.USER); + } else + this.quill.format(s, a, N.sources.USER); + this.update(n); + }), this.controls.push([s, e]); + } + update(e) { + const s = e == null ? {} : this.quill.getFormat(e); + this.controls.forEach((r) => { + const [i, a] = r; + if (a.tagName === "SELECT") { + let n = null; + if (e == null) + n = null; + else if (s[i] == null) + n = a.querySelector("option[selected]"); + else if (!Array.isArray(s[i])) { + let o = s[i]; + typeof o == "string" && (o = o.replace(/"/g, '\\"')), n = a.querySelector(`option[value="${o}"]`); + } + n == null ? (a.value = "", a.selectedIndex = -1) : n.selected = !0; + } else if (e == null) + a.classList.remove("ql-active"), a.setAttribute("aria-pressed", "false"); + else if (a.hasAttribute("value")) { + const n = s[i], o = n === a.getAttribute("value") || n != null && n.toString() === a.getAttribute("value") || n == null && !a.getAttribute("value"); + a.classList.toggle("ql-active", o), a.setAttribute("aria-pressed", o.toString()); + } else { + const n = s[i] != null; + a.classList.toggle("ql-active", n), a.setAttribute("aria-pressed", n.toString()); + } + }); + } +} +ia.DEFAULTS = {}; +function ro(t, e, s) { + const r = document.createElement("button"); + r.setAttribute("type", "button"), r.classList.add(`ql-${e}`), r.setAttribute("aria-pressed", "false"), s != null ? (r.value = s, r.setAttribute("aria-label", `${e}: ${s}`)) : r.setAttribute("aria-label", e), t.appendChild(r); +} +function Ww(t, e) { + Array.isArray(e[0]) || (e = [e]), e.forEach((s) => { + const r = document.createElement("span"); + r.classList.add("ql-formats"), s.forEach((i) => { + if (typeof i == "string") + ro(r, i); + else { + const a = Object.keys(i)[0], n = i[a]; + Array.isArray(n) ? Zw(r, a, n) : ro(r, a, n); + } + }), t.appendChild(r); + }); +} +function Zw(t, e, s) { + const r = document.createElement("select"); + r.classList.add(`ql-${e}`), s.forEach((i) => { + const a = document.createElement("option"); + i !== !1 ? a.setAttribute("value", String(i)) : a.setAttribute("selected", "selected"), r.appendChild(a); + }), t.appendChild(r); +} +ia.DEFAULTS = { + container: null, + handlers: { + clean() { + const t = this.quill.getSelection(); + if (t != null) + if (t.length === 0) { + const e = this.quill.getFormat(); + Object.keys(e).forEach((s) => { + this.quill.scroll.query(s, V.INLINE) != null && this.quill.format(s, !1, N.sources.USER); + }); + } else + this.quill.removeFormat(t.index, t.length, N.sources.USER); + }, + direction(t) { + const { + align: e + } = this.quill.getFormat(); + t === "rtl" && e == null ? this.quill.format("align", "right", N.sources.USER) : !t && e === "right" && this.quill.format("align", !1, N.sources.USER), this.quill.format("direction", t, N.sources.USER); + }, + indent(t) { + const e = this.quill.getSelection(), s = this.quill.getFormat(e), r = parseInt(s.indent || 0, 10); + if (t === "+1" || t === "-1") { + let i = t === "+1" ? 1 : -1; + s.direction === "rtl" && (i *= -1), this.quill.format("indent", r + i, N.sources.USER); + } + }, + link(t) { + t === !0 && (t = prompt("Enter link URL:")), this.quill.format("link", t, N.sources.USER); + }, + list(t) { + const e = this.quill.getSelection(), s = this.quill.getFormat(e); + t === "check" ? s.list === "checked" || s.list === "unchecked" ? this.quill.format("list", !1, N.sources.USER) : this.quill.format("list", "unchecked", N.sources.USER) : this.quill.format("list", t, N.sources.USER); + } + } +}; +const Xw = '', Yw = '', Qw = '', Jw = '', xw = '', eC = '', tC = '', sC = '', io = '', nC = '', rC = '', iC = '', aC = '', oC = '', lC = '', uC = '', dC = '', cC = '', fC = '', pC = '', hC = '', gC = '', mC = '', vC = '', bC = '', yC = '', $C = '', kC = '', wC = '', CC = '', AC = '', EC = '', SC = '', Xs = { + align: { + "": Xw, + center: Yw, + right: Qw, + justify: Jw + }, + background: xw, + blockquote: eC, + bold: tC, + clean: sC, + code: io, + "code-block": io, + color: nC, + direction: { + "": rC, + rtl: iC + }, + formula: aC, + header: { + 1: oC, + 2: lC, + 3: uC, + 4: dC, + 5: cC, + 6: fC + }, + italic: pC, + image: hC, + indent: { + "+1": gC, + "-1": mC + }, + link: vC, + list: { + bullet: bC, + check: yC, + ordered: $C + }, + script: { + sub: kC, + super: wC + }, + strike: CC, + table: AC, + underline: EC, + video: SC +}, TC = ''; +let ao = 0; +function oo(t, e) { + t.setAttribute(e, `${t.getAttribute(e) !== "true"}`); +} +class er { + constructor(e) { + this.select = e, this.container = document.createElement("span"), this.buildPicker(), this.select.style.display = "none", this.select.parentNode.insertBefore(this.container, this.select), this.label.addEventListener("mousedown", () => { + this.togglePicker(); + }), this.label.addEventListener("keydown", (s) => { + switch (s.key) { + case "Enter": + this.togglePicker(); + break; + case "Escape": + this.escape(), s.preventDefault(); + break; + } + }), this.select.addEventListener("change", this.update.bind(this)); + } + togglePicker() { + this.container.classList.toggle("ql-expanded"), oo(this.label, "aria-expanded"), oo(this.options, "aria-hidden"); + } + buildItem(e) { + const s = document.createElement("span"); + s.tabIndex = "0", s.setAttribute("role", "button"), s.classList.add("ql-picker-item"); + const r = e.getAttribute("value"); + return r && s.setAttribute("data-value", r), e.textContent && s.setAttribute("data-label", e.textContent), s.addEventListener("click", () => { + this.selectItem(s, !0); + }), s.addEventListener("keydown", (i) => { + switch (i.key) { + case "Enter": + this.selectItem(s, !0), i.preventDefault(); + break; + case "Escape": + this.escape(), i.preventDefault(); + break; + } + }), s; + } + buildLabel() { + const e = document.createElement("span"); + return e.classList.add("ql-picker-label"), e.innerHTML = TC, e.tabIndex = "0", e.setAttribute("role", "button"), e.setAttribute("aria-expanded", "false"), this.container.appendChild(e), e; + } + buildOptions() { + const e = document.createElement("span"); + e.classList.add("ql-picker-options"), e.setAttribute("aria-hidden", "true"), e.tabIndex = "-1", e.id = `ql-picker-options-${ao}`, ao += 1, this.label.setAttribute("aria-controls", e.id), this.options = e, Array.from(this.select.options).forEach((s) => { + const r = this.buildItem(s); + e.appendChild(r), s.selected === !0 && this.selectItem(r); + }), this.container.appendChild(e); + } + buildPicker() { + Array.from(this.select.attributes).forEach((e) => { + this.container.setAttribute(e.name, e.value); + }), this.container.classList.add("ql-picker"), this.label = this.buildLabel(), this.buildOptions(); + } + escape() { + this.close(), setTimeout(() => this.label.focus(), 1); + } + close() { + this.container.classList.remove("ql-expanded"), this.label.setAttribute("aria-expanded", "false"), this.options.setAttribute("aria-hidden", "true"); + } + selectItem(e) { + let s = arguments.length > 1 && arguments[1] !== void 0 ? arguments[1] : !1; + const r = this.container.querySelector(".ql-selected"); + e !== r && (r != null && r.classList.remove("ql-selected"), e != null && (e.classList.add("ql-selected"), this.select.selectedIndex = Array.from(e.parentNode.children).indexOf(e), e.hasAttribute("data-value") ? this.label.setAttribute("data-value", e.getAttribute("data-value")) : this.label.removeAttribute("data-value"), e.hasAttribute("data-label") ? this.label.setAttribute("data-label", e.getAttribute("data-label")) : this.label.removeAttribute("data-label"), s && (this.select.dispatchEvent(new Event("change")), this.close()))); + } + update() { + let e; + if (this.select.selectedIndex > -1) { + const r = ( + // @ts-expect-error Fix me later + this.container.querySelector(".ql-picker-options").children[this.select.selectedIndex] + ); + e = this.select.options[this.select.selectedIndex], this.selectItem(r); + } else + this.selectItem(null); + const s = e != null && e !== this.select.querySelector("option[selected]"); + this.label.classList.toggle("ql-active", s); + } +} +class ml extends er { + constructor(e, s) { + super(e), this.label.innerHTML = s, this.container.classList.add("ql-color-picker"), Array.from(this.container.querySelectorAll(".ql-picker-item")).slice(0, 7).forEach((r) => { + r.classList.add("ql-primary"); + }); + } + buildItem(e) { + const s = super.buildItem(e); + return s.style.backgroundColor = e.getAttribute("value") || "", s; + } + selectItem(e, s) { + super.selectItem(e, s); + const r = this.label.querySelector(".ql-color-label"), i = e && e.getAttribute("data-value") || ""; + r && (r.tagName === "line" ? r.style.stroke = i : r.style.fill = i); + } +} +class vl extends er { + constructor(e, s) { + super(e), this.container.classList.add("ql-icon-picker"), Array.from(this.container.querySelectorAll(".ql-picker-item")).forEach((r) => { + r.innerHTML = s[r.getAttribute("data-value") || ""]; + }), this.defaultItem = this.container.querySelector(".ql-selected"), this.selectItem(this.defaultItem); + } + selectItem(e, s) { + super.selectItem(e, s); + const r = e || this.defaultItem; + if (r != null) { + if (this.label.innerHTML === r.innerHTML) return; + this.label.innerHTML = r.innerHTML; + } + } +} +const _C = (t) => { + const { + overflowY: e + } = getComputedStyle(t, null); + return e !== "visible" && e !== "clip"; +}; +class bl { + constructor(e, s) { + this.quill = e, this.boundsContainer = s || document.body, this.root = e.addContainer("ql-tooltip"), this.root.innerHTML = this.constructor.TEMPLATE, _C(this.quill.root) && this.quill.root.addEventListener("scroll", () => { + this.root.style.marginTop = `${-1 * this.quill.root.scrollTop}px`; + }), this.hide(); + } + hide() { + this.root.classList.add("ql-hidden"); + } + position(e) { + const s = e.left + e.width / 2 - this.root.offsetWidth / 2, r = e.bottom + this.quill.root.scrollTop; + this.root.style.left = `${s}px`, this.root.style.top = `${r}px`, this.root.classList.remove("ql-flip"); + const i = this.boundsContainer.getBoundingClientRect(), a = this.root.getBoundingClientRect(); + let n = 0; + if (a.right > i.right && (n = i.right - a.right, this.root.style.left = `${s + n}px`), a.left < i.left && (n = i.left - a.left, this.root.style.left = `${s + n}px`), a.bottom > i.bottom) { + const o = a.bottom - a.top, u = e.bottom - e.top + o; + this.root.style.top = `${r - u}px`, this.root.classList.add("ql-flip"); + } + return n; + } + show() { + this.root.classList.remove("ql-editing"), this.root.classList.remove("ql-hidden"); + } +} +const NC = [!1, "center", "right", "justify"], IC = ["#000000", "#e60000", "#ff9900", "#ffff00", "#008a00", "#0066cc", "#9933ff", "#ffffff", "#facccc", "#ffebcc", "#ffffcc", "#cce8cc", "#cce0f5", "#ebd6ff", "#bbbbbb", "#f06666", "#ffc266", "#ffff66", "#66b966", "#66a3e0", "#c285ff", "#888888", "#a10000", "#b26b00", "#b2b200", "#006100", "#0047b2", "#6b24b2", "#444444", "#5c0000", "#663d00", "#666600", "#003700", "#002966", "#3d1466"], LC = [!1, "serif", "monospace"], qC = ["1", "2", "3", !1], OC = ["small", !1, "large", "huge"]; +class en extends ds { + constructor(e, s) { + super(e, s); + const r = (i) => { + if (!document.body.contains(e.root)) { + document.body.removeEventListener("click", r); + return; + } + this.tooltip != null && // @ts-expect-error + !this.tooltip.root.contains(i.target) && // @ts-expect-error + document.activeElement !== this.tooltip.textbox && !this.quill.hasFocus() && this.tooltip.hide(), this.pickers != null && this.pickers.forEach((a) => { + a.container.contains(i.target) || a.close(); + }); + }; + e.emitter.listenDOM("click", document.body, r); + } + addModule(e) { + const s = super.addModule(e); + return e === "toolbar" && this.extendToolbar(s), s; + } + buildButtons(e, s) { + Array.from(e).forEach((r) => { + (r.getAttribute("class") || "").split(/\s+/).forEach((a) => { + if (a.startsWith("ql-") && (a = a.slice(3), s[a] != null)) + if (a === "direction") + r.innerHTML = s[a][""] + s[a].rtl; + else if (typeof s[a] == "string") + r.innerHTML = s[a]; + else { + const n = r.value || ""; + n != null && s[a][n] && (r.innerHTML = s[a][n]); + } + }); + }); + } + buildPickers(e, s) { + this.pickers = Array.from(e).map((i) => { + if (i.classList.contains("ql-align") && (i.querySelector("option") == null && Ps(i, NC), typeof s.align == "object")) + return new vl(i, s.align); + if (i.classList.contains("ql-background") || i.classList.contains("ql-color")) { + const a = i.classList.contains("ql-background") ? "background" : "color"; + return i.querySelector("option") == null && Ps(i, IC, a === "background" ? "#ffffff" : "#000000"), new ml(i, s[a]); + } + return i.querySelector("option") == null && (i.classList.contains("ql-font") ? Ps(i, LC) : i.classList.contains("ql-header") ? Ps(i, qC) : i.classList.contains("ql-size") && Ps(i, OC)), new er(i); + }); + const r = () => { + this.pickers.forEach((i) => { + i.update(); + }); + }; + this.quill.on(F.events.EDITOR_CHANGE, r); + } +} +en.DEFAULTS = yt({}, ds.DEFAULTS, { + modules: { + toolbar: { + handlers: { + formula() { + this.quill.theme.tooltip.edit("formula"); + }, + image() { + let t = this.container.querySelector("input.ql-image[type=file]"); + t == null && (t = document.createElement("input"), t.setAttribute("type", "file"), t.setAttribute("accept", this.quill.uploader.options.mimetypes.join(", ")), t.classList.add("ql-image"), t.addEventListener("change", () => { + const e = this.quill.getSelection(!0); + this.quill.uploader.upload(e, t.files), t.value = ""; + }), this.container.appendChild(t)), t.click(); + }, + video() { + this.quill.theme.tooltip.edit("video"); + } + } + } + } +}); +class yl extends bl { + constructor(e, s) { + super(e, s), this.textbox = this.root.querySelector('input[type="text"]'), this.listen(); + } + listen() { + this.textbox.addEventListener("keydown", (e) => { + e.key === "Enter" ? (this.save(), e.preventDefault()) : e.key === "Escape" && (this.cancel(), e.preventDefault()); + }); + } + cancel() { + this.hide(), this.restoreFocus(); + } + edit() { + let e = arguments.length > 0 && arguments[0] !== void 0 ? arguments[0] : "link", s = arguments.length > 1 && arguments[1] !== void 0 ? arguments[1] : null; + if (this.root.classList.remove("ql-hidden"), this.root.classList.add("ql-editing"), this.textbox == null) return; + s != null ? this.textbox.value = s : e !== this.root.getAttribute("data-mode") && (this.textbox.value = ""); + const r = this.quill.getBounds(this.quill.selection.savedRange); + r != null && this.position(r), this.textbox.select(), this.textbox.setAttribute("placeholder", this.textbox.getAttribute(`data-${e}`) || ""), this.root.setAttribute("data-mode", e); + } + restoreFocus() { + this.quill.focus({ + preventScroll: !0 + }); + } + save() { + let { + value: e + } = this.textbox; + switch (this.root.getAttribute("data-mode")) { + case "link": { + const { + scrollTop: s + } = this.quill.root; + this.linkRange ? (this.quill.formatText(this.linkRange, "link", e, F.sources.USER), delete this.linkRange) : (this.restoreFocus(), this.quill.format("link", e, F.sources.USER)), this.quill.root.scrollTop = s; + break; + } + case "video": + e = PC(e); + case "formula": { + if (!e) break; + const s = this.quill.getSelection(!0); + if (s != null) { + const r = s.index + s.length; + this.quill.insertEmbed( + r, + // @ts-expect-error Fix me later + this.root.getAttribute("data-mode"), + e, + F.sources.USER + ), this.root.getAttribute("data-mode") === "formula" && this.quill.insertText(r + 1, " ", F.sources.USER), this.quill.setSelection(r + 2, F.sources.USER); + } + break; + } + } + this.textbox.value = "", this.hide(); + } +} +function PC(t) { + let e = t.match(/^(?:(https?):\/\/)?(?:(?:www|m)\.)?youtube\.com\/watch.*v=([a-zA-Z0-9_-]+)/) || t.match(/^(?:(https?):\/\/)?(?:(?:www|m)\.)?youtu\.be\/([a-zA-Z0-9_-]+)/); + return e ? `${e[1] || "https"}://www.youtube.com/embed/${e[2]}?showinfo=0` : (e = t.match(/^(?:(https?):\/\/)?(?:www\.)?vimeo\.com\/(\d+)/)) ? `${e[1] || "https"}://player.vimeo.com/video/${e[2]}/` : t; +} +function Ps(t, e) { + let s = arguments.length > 2 && arguments[2] !== void 0 ? arguments[2] : !1; + e.forEach((r) => { + const i = document.createElement("option"); + r === s ? i.setAttribute("selected", "selected") : i.setAttribute("value", String(r)), t.appendChild(i); + }); +} +const DC = [["bold", "italic", "link"], [{ + header: 1 +}, { + header: 2 +}, "blockquote"]]; +class $l extends yl { + constructor(e, s) { + super(e, s), this.quill.on(F.events.EDITOR_CHANGE, (r, i, a, n) => { + if (r === F.events.SELECTION_CHANGE) + if (i != null && i.length > 0 && n === F.sources.USER) { + this.show(), this.root.style.left = "0px", this.root.style.width = "", this.root.style.width = `${this.root.offsetWidth}px`; + const o = this.quill.getLines(i.index, i.length); + if (o.length === 1) { + const u = this.quill.getBounds(i); + u != null && this.position(u); + } else { + const u = o[o.length - 1], p = this.quill.getIndex(u), g = Math.min(u.length() - 1, i.index + i.length - p), k = this.quill.getBounds(new Pt(p, g)); + k != null && this.position(k); + } + } else document.activeElement !== this.textbox && this.quill.hasFocus() && this.hide(); + }); + } + listen() { + super.listen(), this.root.querySelector(".ql-close").addEventListener("click", () => { + this.root.classList.remove("ql-editing"); + }), this.quill.on(F.events.SCROLL_OPTIMIZE, () => { + setTimeout(() => { + if (this.root.classList.contains("ql-hidden")) return; + const e = this.quill.getSelection(); + if (e != null) { + const s = this.quill.getBounds(e); + s != null && this.position(s); + } + }, 1); + }); + } + cancel() { + this.show(); + } + position(e) { + const s = super.position(e), r = this.root.querySelector(".ql-tooltip-arrow"); + return r.style.marginLeft = "", s !== 0 && (r.style.marginLeft = `${-1 * s - r.offsetWidth / 2}px`), s; + } +} +B($l, "TEMPLATE", ['', '
    ', '', '', "
    "].join("")); +class kl extends en { + constructor(e, s) { + s.modules.toolbar != null && s.modules.toolbar.container == null && (s.modules.toolbar.container = DC), super(e, s), this.quill.container.classList.add("ql-bubble"); + } + extendToolbar(e) { + this.tooltip = new $l(this.quill, this.options.bounds), e.container != null && (this.tooltip.root.appendChild(e.container), this.buildButtons(e.container.querySelectorAll("button"), Xs), this.buildPickers(e.container.querySelectorAll("select"), Xs)); + } +} +kl.DEFAULTS = yt({}, en.DEFAULTS, { + modules: { + toolbar: { + handlers: { + link(t) { + t ? this.quill.theme.tooltip.edit() : this.quill.format("link", !1, N.sources.USER); + } + } + } + } +}); +const RC = [[{ + header: ["1", "2", "3", !1] +}], ["bold", "italic", "underline", "link"], [{ + list: "ordered" +}, { + list: "bullet" +}], ["clean"]]; +class wl extends yl { + constructor() { + super(...arguments); + B(this, "preview", this.root.querySelector("a.ql-preview")); + } + listen() { + super.listen(), this.root.querySelector("a.ql-action").addEventListener("click", (s) => { + this.root.classList.contains("ql-editing") ? this.save() : this.edit("link", this.preview.textContent), s.preventDefault(); + }), this.root.querySelector("a.ql-remove").addEventListener("click", (s) => { + if (this.linkRange != null) { + const r = this.linkRange; + this.restoreFocus(), this.quill.formatText(r, "link", !1, F.sources.USER), delete this.linkRange; + } + s.preventDefault(), this.hide(); + }), this.quill.on(F.events.SELECTION_CHANGE, (s, r, i) => { + if (s != null) { + if (s.length === 0 && i === F.sources.USER) { + const [a, n] = this.quill.scroll.descendant(bt, s.index); + if (a != null) { + this.linkRange = new Pt(s.index - n, a.length()); + const o = bt.formats(a.domNode); + this.preview.textContent = o, this.preview.setAttribute("href", o), this.show(); + const u = this.quill.getBounds(this.linkRange); + u != null && this.position(u); + return; + } + } else + delete this.linkRange; + this.hide(); + } + }); + } + show() { + super.show(), this.root.removeAttribute("data-mode"); + } +} +B(wl, "TEMPLATE", ['', '', '', ''].join("")); +class Cl extends en { + constructor(e, s) { + s.modules.toolbar != null && s.modules.toolbar.container == null && (s.modules.toolbar.container = RC), super(e, s), this.quill.container.classList.add("ql-snow"); + } + extendToolbar(e) { + e.container != null && (e.container.classList.add("ql-snow"), this.buildButtons(e.container.querySelectorAll("button"), Xs), this.buildPickers(e.container.querySelectorAll("select"), Xs), this.tooltip = new wl(this.quill, this.options.bounds), e.container.querySelector(".ql-link") && this.quill.keyboard.addBinding({ + key: "k", + shortKey: !0 + }, (s, r) => { + e.handlers.link.call(e, !r.format.link); + })); + } +} +Cl.DEFAULTS = yt({}, en.DEFAULTS, { + modules: { + toolbar: { + handlers: { + link(t) { + if (t) { + const e = this.quill.getSelection(); + if (e == null || e.length === 0) return; + let s = this.quill.getText(e); + /^\S+@\S+\.\S+$/.test(s) && s.indexOf("mailto:") !== 0 && (s = `mailto:${s}`); + const { + tooltip: r + } = this.quill.theme; + r.edit("link", s); + } else + this.quill.format("link", !1, N.sources.USER); + } + } + } + } +}); +N.register({ + "attributors/attribute/direction": sl, + "attributors/class/align": xo, + "attributors/class/background": xk, + "attributors/class/color": Jk, + "attributors/class/direction": nl, + "attributors/class/font": al, + "attributors/class/size": ll, + "attributors/style/align": el, + "attributors/style/background": xi, + "attributors/style/color": Ji, + "attributors/style/direction": rl, + "attributors/style/font": ol, + "attributors/style/size": ul +}, !0); +N.register({ + "formats/align": xo, + "formats/direction": nl, + "formats/indent": Hw, + "formats/background": xi, + "formats/color": Ji, + "formats/font": al, + "formats/size": ll, + "formats/blockquote": _i, + "formats/code-block": me, + "formats/header": Ni, + "formats/list": xs, + "formats/bold": Zs, + "formats/code": ea, + "formats/italic": Ii, + "formats/link": bt, + "formats/script": Li, + "formats/strike": qi, + "formats/underline": Oi, + "formats/formula": Bn, + "formats/image": Pi, + "formats/video": zw, + "modules/syntax": gl, + "modules/table": Kw, + "modules/toolbar": ia, + "themes/bubble": kl, + "themes/snow": Cl, + "ui/icons": Xs, + "ui/picker": er, + "ui/icon-picker": vl, + "ui/color-picker": ml, + "ui/tooltip": bl +}, !0); +const BC = C({ + name: "RichTextEditor", + props: { + readonly: { + type: Boolean, + default: !1 + } + }, + setup(t) { + const e = m(null); + return Ce(() => { + e.value && new N(e.value, { + theme: "snow", + readOnly: t.readonly, + modules: { + toolbar: !t.readonly + } + }); + }), { + editor: e + }; + } +}), MC = { class: "editor-container" }, FC = ["aria-readonly"]; +function UC(t, e, s, r, i, a) { + return d(), c("div", MC, [ + f("div", { + ref: "editor", + "aria-label": "Rich text editor", + "aria-readonly": t.readonly + }, null, 8, FC) + ]); +} +const X4 = /* @__PURE__ */ A(BC, [["render", UC], ["__scopeId", "data-v-a295d163"]]), jC = C({ + name: "RulerAndGuides", + setup() { + const t = m(100), e = li([]), s = (a) => { + console.log(a); + }, r = (a, n) => { + e.push({ x: a, y: n }); + }, i = (a) => { + e.splice(a, 1); + }; + return Ce(() => { + }), { + zoomLevel: t, + guides: e, + startMovingGuide: s, + addGuide: r, + removeGuide: i + }; + } +}), VC = { class: "ruler-and-guides" }, HC = { + class: "ruler", + ref: "horizontalRuler", + "aria-label": "Horizontal Ruler" +}, zC = { + class: "ruler", + ref: "verticalRuler", + "aria-label": "Vertical Ruler" +}, GC = { class: "guides" }, KC = ["onMousedown"]; +function WC(t, e, s, r, i, a) { + return d(), c("div", VC, [ + f("div", HC, null, 512), + f("div", zC, null, 512), + f("div", GC, [ + (d(!0), c(I, null, L(t.guides, (n, o) => (d(), c("div", { + key: o, + style: ne({ left: n.x + "px", top: n.y + "px" }), + class: "guide", + "aria-label": "Guide", + onMousedown: (u) => t.startMovingGuide(o) + }, null, 44, KC))), 128)) + ]) + ]); +} +const Y4 = /* @__PURE__ */ A(jC, [["render", WC], ["__scopeId", "data-v-53005f5f"]]), ZC = C({ + name: "ScheduleCRUDPanel", + props: { + feedbackMessageProp: { + type: String, + default: "" + } + }, + setup(t) { + const e = m({ + title: "", + date: "", + time: "", + location: "", + description: "", + participants: "" + }), s = m(t.feedbackMessageProp), r = () => { + s.value = `Event "${e.value.title}" created successfully.`, n(); + }, i = () => { + s.value = `Event "${e.value.title}" updated successfully.`; + }, a = () => { + s.value = `Event "${e.value.title}" deleted successfully.`, n(); + }, n = () => { + e.value = { + title: "", + date: "", + time: "", + location: "", + description: "", + participants: "" + }; + }; + return { + form: e, + feedbackMessage: s, + handleSubmit: r, + updateEvent: i, + deleteEvent: a + }; + } +}), XC = { + class: "schedule-crud-panel", + role: "region", + "aria-label": "Schedule CRUD Panel" +}, YC = { class: "form-group" }, QC = { class: "form-group" }, JC = { class: "form-group" }, xC = { class: "form-group" }, eA = { class: "form-group" }, tA = { class: "form-group" }, sA = { class: "crud-buttons" }, nA = { + key: 0, + class: "feedback" +}; +function rA(t, e, s, r, i, a) { + return d(), c("div", XC, [ + f("form", { + onSubmit: e[8] || (e[8] = de((...n) => t.handleSubmit && t.handleSubmit(...n), ["prevent"])) + }, [ + f("div", YC, [ + e[9] || (e[9] = f("label", { for: "title" }, "Event Title", -1)), + R(f("input", { + type: "text", + id: "title", + "onUpdate:modelValue": e[0] || (e[0] = (n) => t.form.title = n), + required: "" + }, null, 512), [ + [H, t.form.title] + ]) + ]), + f("div", QC, [ + e[10] || (e[10] = f("label", { for: "date" }, "Date", -1)), + R(f("input", { + type: "date", + id: "date", + "onUpdate:modelValue": e[1] || (e[1] = (n) => t.form.date = n), + required: "" + }, null, 512), [ + [H, t.form.date] + ]) + ]), + f("div", JC, [ + e[11] || (e[11] = f("label", { for: "time" }, "Time", -1)), + R(f("input", { + type: "time", + id: "time", + "onUpdate:modelValue": e[2] || (e[2] = (n) => t.form.time = n), + required: "" + }, null, 512), [ + [H, t.form.time] + ]) + ]), + f("div", xC, [ + e[12] || (e[12] = f("label", { for: "location" }, "Location", -1)), + R(f("input", { + type: "text", + id: "location", + "onUpdate:modelValue": e[3] || (e[3] = (n) => t.form.location = n) + }, null, 512), [ + [H, t.form.location] + ]) + ]), + f("div", eA, [ + e[13] || (e[13] = f("label", { for: "description" }, "Description", -1)), + R(f("textarea", { + id: "description", + "onUpdate:modelValue": e[4] || (e[4] = (n) => t.form.description = n) + }, null, 512), [ + [H, t.form.description] + ]) + ]), + f("div", tA, [ + e[14] || (e[14] = f("label", { for: "participants" }, "Participants", -1)), + R(f("input", { + type: "text", + id: "participants", + "onUpdate:modelValue": e[5] || (e[5] = (n) => t.form.participants = n) + }, null, 512), [ + [H, t.form.participants] + ]) + ]), + f("div", sA, [ + e[15] || (e[15] = f("button", { type: "submit" }, "Create Event", -1)), + f("button", { + type: "button", + onClick: e[6] || (e[6] = (...n) => t.updateEvent && t.updateEvent(...n)) + }, "Update Event"), + f("button", { + type: "button", + onClick: e[7] || (e[7] = (...n) => t.deleteEvent && t.deleteEvent(...n)) + }, "Delete Event") + ]) + ], 32), + t.feedbackMessage ? (d(), c("div", nA, w(t.feedbackMessage), 1)) : P("", !0) + ]); +} +const Q4 = /* @__PURE__ */ A(ZC, [["render", rA], ["__scopeId", "data-v-4d6e4564"]]), iA = C({ + name: "ScrollableList", + props: { + items: { + type: Array, + required: !0 + }, + disabled: { + type: Boolean, + default: !1 + } + }, + setup() { + const t = m(null), e = m(!1), s = m(null); + return { hoveredItem: t, endOfList: e, onScroll: () => { + if (s.value) { + const { scrollTop: i, scrollHeight: a, clientHeight: n } = s.value; + e.value = i + n >= a; + } + }, listRef: s }; + } +}), aA = ["aria-disabled"], oA = ["onMouseover"], lA = { + key: 0, + class: "end-of-list-message" +}; +function uA(t, e, s, r, i, a) { + return d(), c("div", { + class: "scrollable-list", + "aria-disabled": t.disabled ? "true" : "false" + }, [ + f("ul", { + class: "scrollable-list-items", + onScroll: e[1] || (e[1] = (...n) => t.onScroll && t.onScroll(...n)), + ref: "listRef" + }, [ + (d(!0), c(I, null, L(t.items, (n) => (d(), c("li", { + key: n.id, + class: q(["scrollable-list-item", { disabled: t.disabled, hover: n.id === t.hoveredItem }]), + onMouseover: (o) => t.hoveredItem = n.id, + onMouseleave: e[0] || (e[0] = (o) => t.hoveredItem = null) + }, w(n.label), 43, oA))), 128)) + ], 544), + t.endOfList ? (d(), c("div", lA, "End of List")) : P("", !0) + ], 8, aA); +} +const J4 = /* @__PURE__ */ A(iA, [["render", uA], ["__scopeId", "data-v-a0c69f47"]]), dA = C({ + name: "SearchBar", + props: { + placeholder: { + type: String, + default: "Search..." + }, + isFocused: { + type: Boolean, + default: !1 + }, + isDisabled: { + type: Boolean, + default: !1 + } + }, + setup(t) { + const e = m(t.isFocused); + return { handleFocus: () => { + e.value = !0; + }, handleBlur: () => { + e.value = !1; + } }; + } +}), cA = ["placeholder", "disabled"]; +function fA(t, e, s, r, i, a) { + return d(), c("div", { + class: q(["search-bar", { focused: t.isFocused, disabled: t.isDisabled }]) + }, [ + f("input", { + type: "text", + placeholder: t.placeholder, + disabled: t.isDisabled, + onFocus: e[0] || (e[0] = (...n) => t.handleFocus && t.handleFocus(...n)), + onBlur: e[1] || (e[1] = (...n) => t.handleBlur && t.handleBlur(...n)), + "aria-label": "Search" + }, null, 40, cA) + ], 2); +} +const x4 = /* @__PURE__ */ A(dA, [["render", fA], ["__scopeId", "data-v-dfc81841"]]), pA = C({ + name: "SearchBarWithSuggestions", + props: { + suggestions: { + type: Array, + default: () => [] + }, + filterOptions: { + type: Array, + default: () => [] + } + }, + emits: ["search"], + setup(t, { emit: e }) { + const s = m(""), r = m([]), i = m(!1), a = () => { + s.value ? (r.value = t.suggestions.filter( + (o) => o.toLowerCase().includes(s.value.toLowerCase()) + ), i.value = r.value.length > 0) : i.value = !1; + }; + return fs(s, a), { + query: s, + filteredSuggestions: r, + showSuggestions: i, + handleSearch: () => { + e("search", s.value); + }, + updateSuggestions: a + }; + } +}), hA = { class: "search-bar-with-suggestions" }, gA = { + key: 0, + class: "suggestions-list", + "aria-live": "polite" +}, mA = ["onClick"], vA = { + key: 1, + class: "no-results" +}; +function bA(t, e, s, r, i, a) { + return d(), c("div", hA, [ + R(f("input", { + type: "text", + "onUpdate:modelValue": e[0] || (e[0] = (n) => t.query = n), + onInput: e[1] || (e[1] = (...n) => t.updateSuggestions && t.updateSuggestions(...n)), + onKeyup: e[2] || (e[2] = Rs((...n) => t.handleSearch && t.handleSearch(...n), ["enter"])), + placeholder: "Search...", + "aria-label": "Search" + }, null, 544), [ + [H, t.query] + ]), + t.showSuggestions ? (d(), c("ul", gA, [ + (d(!0), c(I, null, L(t.filteredSuggestions, (n) => (d(), c("li", { + key: n, + onClick: (o) => { + t.query = n, t.handleSearch(); + } + }, w(n), 9, mA))), 128)) + ])) : P("", !0), + t.query && !t.filteredSuggestions.length ? (d(), c("div", vA, " No results found ")) : P("", !0) + ]); +} +const e3 = /* @__PURE__ */ A(pA, [["render", bA], ["__scopeId", "data-v-7d9836c1"]]), yA = C({ + name: "SearchInputWithFilterOptions", + props: { + placeholder: { + type: String, + default: "Search..." + }, + disabled: { + type: Boolean, + default: !1 + }, + filtersActive: { + type: Boolean, + default: !1 + }, + noResults: { + type: Boolean, + default: !1 + } + }, + emits: ["update:filtersActive", "input"], + setup(t, { emit: e }) { + const s = m(""); + return { + query: s, + onInput: () => { + e("input", s.value); + }, + toggleFilters: () => { + e("update:filtersActive", !t.filtersActive); + } + }; + } +}), $A = { class: "search-container" }, kA = ["placeholder", "disabled"], wA = ["aria-pressed"], CA = { + key: 0, + class: "filter-options" +}, AA = { + key: 1, + class: "no-results" +}; +function EA(t, e, s, r, i, a) { + return d(), c("div", $A, [ + R(f("input", { + type: "text", + placeholder: t.placeholder, + disabled: t.disabled, + "aria-label": "Search input", + "onUpdate:modelValue": e[0] || (e[0] = (n) => t.query = n), + onInput: e[1] || (e[1] = (...n) => t.onInput && t.onInput(...n)) + }, null, 40, kA), [ + [H, t.query] + ]), + f("button", { + "aria-pressed": t.filtersActive, + onClick: e[2] || (e[2] = (...n) => t.toggleFilters && t.toggleFilters(...n)) + }, " Filters ", 8, wA), + t.filtersActive ? (d(), c("div", CA, [ + ie(t.$slots, "filters", {}, void 0, !0) + ])) : P("", !0), + t.noResults ? (d(), c("div", AA, " No Results Found ")) : P("", !0) + ]); +} +const t3 = /* @__PURE__ */ A(yA, [["render", EA], ["__scopeId", "data-v-8be8e8b9"]]), SA = C({ + name: "SearchWithAutocomplete", + props: { + options: { + type: Array, + default: () => [] + }, + query: { + type: String, + default: "" + } + }, + setup(t) { + const e = m(t.query), s = W( + () => t.options.filter( + (i) => i.toLowerCase().includes(e.value.toLowerCase()) + ) + ); + return { query: e, filteredResults: s, onInput: () => { + } }; + } +}), TA = { class: "search-autocomplete" }, _A = { + key: 0, + class: "results-list" +}, NA = { + key: 1, + class: "no-results" +}; +function IA(t, e, s, r, i, a) { + return d(), c("div", TA, [ + R(f("input", { + type: "text", + "onUpdate:modelValue": e[0] || (e[0] = (n) => t.query = n), + onInput: e[1] || (e[1] = (...n) => t.onInput && t.onInput(...n)), + placeholder: "Search...", + "aria-label": "Search input" + }, null, 544), [ + [H, t.query] + ]), + t.query && t.filteredResults.length ? (d(), c("ul", _A, [ + (d(!0), c(I, null, L(t.filteredResults, (n, o) => (d(), c("li", { key: o }, w(n), 1))), 128)) + ])) : P("", !0), + t.query && !t.filteredResults.length ? (d(), c("p", NA, "No results found.")) : P("", !0) + ]); +} +const s3 = /* @__PURE__ */ A(SA, [["render", IA], ["__scopeId", "data-v-5ecf543f"]]), LA = C({ + name: "SelectableListWithItemDetails", + props: { + items: { + type: Array, + required: !0 + }, + disabled: { + type: Boolean, + default: !1 + } + }, + setup(t) { + const e = m(null), s = m(null); + return { selectedItem: e, openDetails: s, toggleSelection: (a) => { + t.disabled || (e.value = e.value === a ? null : a); + }, toggleDetails: (a) => { + s.value = s.value === a ? null : a; + } }; + } +}), qA = { class: "selectable-list" }, OA = { class: "selectable-list-items" }, PA = ["onClick"], DA = { class: "item-content" }, RA = ["onClick", "aria-expanded"], BA = { + key: 0, + class: "item-details" +}; +function MA(t, e, s, r, i, a) { + return d(), c("div", qA, [ + f("ul", OA, [ + (d(!0), c(I, null, L(t.items, (n) => (d(), c("li", { + key: n.id, + class: q(["selectable-list-item", { selected: n.id === t.selectedItem, disabled: t.disabled }]), + onClick: (o) => t.toggleSelection(n.id) + }, [ + f("div", DA, [ + Fe(w(n.label) + " ", 1), + f("button", { + onClick: de((o) => t.toggleDetails(n.id), ["stop"]), + class: "details-button", + "aria-expanded": n.id === t.openDetails ? "true" : "false" + }, w(n.id === t.openDetails ? "Hide Details" : "Show Details"), 9, RA) + ]), + n.id === t.openDetails ? (d(), c("div", BA, w(n.details), 1)) : P("", !0) + ], 10, PA))), 128)) + ]) + ]); +} +const n3 = /* @__PURE__ */ A(LA, [["render", MA], ["__scopeId", "data-v-213fd947"]]), FA = C({ + name: "ShapeLibrary", + setup() { + const t = m(""), e = [ + { name: "Circle", icon: "/icons/circle.svg" }, + { name: "Square", icon: "/icons/square.svg" }, + { name: "Triangle", icon: "/icons/triangle.svg" } + ], s = W( + () => e.filter((i) => i.name.toLowerCase().includes(t.value.toLowerCase())) + ); + return { + searchQuery: t, + filteredShapes: s, + onDragStart: (i) => { + console.log(i); + } + }; + } +}), UA = { class: "shape-library" }, jA = { class: "shape-list" }, VA = ["onDragstart"], HA = ["src", "alt"]; +function zA(t, e, s, r, i, a) { + return d(), c("div", UA, [ + R(f("input", { + type: "text", + "onUpdate:modelValue": e[0] || (e[0] = (n) => t.searchQuery = n), + placeholder: "Search shapes...", + "aria-label": "Search shapes" + }, null, 512), [ + [H, t.searchQuery] + ]), + f("div", jA, [ + (d(!0), c(I, null, L(t.filteredShapes, (n, o) => (d(), c("div", { + key: o, + class: "shape-item", + draggable: "true", + onDragstart: (u) => t.onDragStart(n), + "aria-label": "Shape" + }, [ + f("img", { + src: n.icon, + alt: n.name + }, null, 8, HA) + ], 40, VA))), 128)) + ]) + ]); +} +const r3 = /* @__PURE__ */ A(FA, [["render", zA], ["__scopeId", "data-v-f12478ca"]]), GA = C({ + name: "ShapeTool", + setup() { + const t = m(!1), e = m("rectangle"), s = m(50), r = m("#000000"), i = m("#ffffff"), a = m(1); + return { + isActive: t, + selectedShape: e, + size: s, + fillColor: r, + borderColor: i, + thickness: a, + toggleActive: () => { + t.value = !t.value; + } + }; + } +}), KA = ["aria-pressed"], WA = { + key: 0, + class: "shape-settings" +}; +function ZA(t, e, s, r, i, a) { + return d(), c("div", { + class: q(["shape-tool", { active: t.isActive }]) + }, [ + f("button", { + onClick: e[0] || (e[0] = (...n) => t.toggleActive && t.toggleActive(...n)), + "aria-pressed": t.isActive, + class: "shape-button" + }, "Shape Tool", 8, KA), + t.isActive ? (d(), c("div", WA, [ + e[7] || (e[7] = f("label", { for: "shape" }, "Shape", -1)), + R(f("select", { + id: "shape", + "onUpdate:modelValue": e[1] || (e[1] = (n) => t.selectedShape = n), + "aria-label": "Select Shape" + }, [...e[6] || (e[6] = [ + f("option", { value: "rectangle" }, "Rectangle", -1), + f("option", { value: "circle" }, "Circle", -1), + f("option", { value: "ellipse" }, "Ellipse", -1), + f("option", { value: "line" }, "Line", -1) + ])], 512), [ + [ye, t.selectedShape] + ]), + e[8] || (e[8] = f("label", { for: "size" }, "Size", -1)), + R(f("input", { + id: "size", + type: "range", + "onUpdate:modelValue": e[2] || (e[2] = (n) => t.size = n), + min: "1", + max: "100", + "aria-valuemin": "1", + "aria-valuemax": "100", + "aria-valuenow": "size" + }, null, 512), [ + [H, t.size] + ]), + e[9] || (e[9] = f("label", { for: "fillColor" }, "Fill Color", -1)), + R(f("input", { + id: "fillColor", + type: "color", + "onUpdate:modelValue": e[3] || (e[3] = (n) => t.fillColor = n), + "aria-label": "Fill Color" + }, null, 512), [ + [H, t.fillColor] + ]), + e[10] || (e[10] = f("label", { for: "borderColor" }, "Border Color", -1)), + R(f("input", { + id: "borderColor", + type: "color", + "onUpdate:modelValue": e[4] || (e[4] = (n) => t.borderColor = n), + "aria-label": "Border Color" + }, null, 512), [ + [H, t.borderColor] + ]), + e[11] || (e[11] = f("label", { for: "thickness" }, "Thickness", -1)), + R(f("input", { + id: "thickness", + type: "range", + "onUpdate:modelValue": e[5] || (e[5] = (n) => t.thickness = n), + min: "1", + max: "10", + "aria-valuemin": "1", + "aria-valuemax": "10", + "aria-valuenow": "thickness" + }, null, 512), [ + [H, t.thickness] + ]) + ])) : P("", !0) + ], 2); +} +const i3 = /* @__PURE__ */ A(GA, [["render", ZA], ["__scopeId", "data-v-f0914fde"]]), XA = C({ + name: "SignalStrengthIndicator", + props: { + strength: { + type: Number, + default: 0, + validator: (t) => t >= 0 && t <= 5 + } + }, + setup(t) { + const e = [1, 2, 3, 4, 5], s = W(() => { + switch (t.strength) { + case 5: + return "Full Signal"; + case 4: + case 3: + return "Good Signal"; + case 2: + case 1: + return "Weak Signal"; + default: + return "No Signal"; + } + }); + return { + levels: e, + ariaLabel: s + }; + } +}), YA = ["aria-label"]; +function QA(t, e, s, r, i, a) { + return d(), c("div", { + class: "signal-strength-indicator", + role: "status", + "aria-label": t.ariaLabel + }, [ + (d(!0), c(I, null, L(t.levels, (n) => (d(), c("div", { + key: n, + class: q(["bar", { active: n <= t.strength }]), + style: ne({ height: `${n * 20}%` }) + }, null, 6))), 128)) + ], 8, YA); +} +const a3 = /* @__PURE__ */ A(XA, [["render", QA], ["__scopeId", "data-v-7623f4d7"]]), JA = C({ + name: "SingleChoicePoll", + props: { + question: { + type: String, + required: !0 + }, + options: { + type: Array, + required: !0 + }, + isDisabled: { + type: Boolean, + default: !1 + }, + showResults: { + type: Boolean, + default: !1 + } + }, + emits: ["update:selectedOption"], + setup(t, { emit: e }) { + const s = m(null); + return { + selectedOption: s, + handleChange: () => { + e("update:selectedOption", s.value); + } + }; + } +}), xA = { + role: "group", + "aria-labelledby": "poll-question", + class: "poll" +}, eE = { id: "poll-question" }, tE = ["id", "value", "disabled", "aria-checked"], sE = ["for"]; +function nE(t, e, s, r, i, a) { + return d(), c("div", xA, [ + f("p", eE, w(t.question), 1), + (d(!0), c(I, null, L(t.options, (n) => (d(), c("div", { + key: n, + class: "option" + }, [ + R(f("input", { + type: "radio", + id: n, + value: n, + "onUpdate:modelValue": e[0] || (e[0] = (o) => t.selectedOption = o), + disabled: t.isDisabled, + onChange: e[1] || (e[1] = (...o) => t.handleChange && t.handleChange(...o)), + "aria-checked": t.selectedOption === n + }, null, 40, tE), [ + [Il, t.selectedOption] + ]), + f("label", { for: n }, w(n), 9, sE) + ]))), 128)) + ]); +} +const o3 = /* @__PURE__ */ A(JA, [["render", nE], ["__scopeId", "data-v-ff3ebf2c"]]), rE = C({ + name: "SkeletonLoading", + props: { + loading: { + type: Boolean, + default: !0 + } + } +}), iE = ["aria-busy"], aE = { + key: 0, + class: "skeleton" +}; +function oE(t, e, s, r, i, a) { + return d(), c("div", { + class: "skeleton-loading", + "aria-busy": t.loading + }, [ + t.loading ? (d(), c("div", aE)) : ie(t.$slots, "default", { key: 1 }, void 0, !0) + ], 8, iE); +} +const l3 = /* @__PURE__ */ A(rE, [["render", oE], ["__scopeId", "data-v-0ddff634"]]), lE = C({ + name: "Slider", + props: { + min: { + type: Number, + default: 0 + }, + max: { + type: Number, + default: 100 + }, + value: { + type: Number, + default: 50 + }, + step: { + type: Number, + default: 1 + }, + isDisabled: { + type: Boolean, + default: !1 + }, + showValue: { + type: Boolean, + default: !1 + } + }, + setup(t, { emit: e }) { + return { updateValue: (r) => { + const i = r.target.valueAsNumber; + e("update:value", i); + } }; + } +}), uE = ["min", "max", "value", "step", "disabled"], dE = { + key: 0, + class: "slider-value" +}; +function cE(t, e, s, r, i, a) { + return d(), c("div", { + class: q(["slider-container", { disabled: t.isDisabled }]) + }, [ + f("input", { + type: "range", + min: t.min, + max: t.max, + value: t.value, + step: t.step, + disabled: t.isDisabled, + onInput: e[0] || (e[0] = (...n) => t.updateValue && t.updateValue(...n)), + "aria-label": "Slider" + }, null, 40, uE), + t.showValue ? (d(), c("span", dE, w(t.value), 1)) : P("", !0) + ], 2); +} +const u3 = /* @__PURE__ */ A(lE, [["render", cE], ["__scopeId", "data-v-ee851f84"]]), fE = C({ + name: "SliderPoll", + props: { + question: { + type: String, + required: !0 + }, + min: { + type: Number, + default: 1 + }, + max: { + type: Number, + default: 100 + }, + initialValue: { + type: Number, + default: 50 + }, + isDisabled: { + type: Boolean, + default: !1 + }, + showResults: { + type: Boolean, + default: !1 + } + }, + setup(t) { + const e = m(t.initialValue), s = m(`slider-${Math.random().toString(36).substr(2, 9)}`); + return { + selectedValue: e, + updateValue: (i) => { + if (!t.isDisabled) { + const a = i.target; + e.value = Number(a.value); + } + }, + id: s + }; + } +}), pE = { class: "slider-poll" }, hE = ["for"], gE = ["id", "min", "max", "value", "disabled", "aria-valuenow"], mE = { + key: 0, + class: "results" +}; +function vE(t, e, s, r, i, a) { + return d(), c("div", pE, [ + f("label", { + for: t.id, + class: "slider-label" + }, w(t.question), 9, hE), + f("input", { + type: "range", + id: t.id, + min: t.min, + max: t.max, + value: t.initialValue, + disabled: t.isDisabled, + onInput: e[0] || (e[0] = (...n) => t.updateValue && t.updateValue(...n)), + class: "slider", + "aria-valuemin": "min", + "aria-valuemax": "max", + "aria-valuenow": t.selectedValue, + "aria-label": "Value slider" + }, null, 40, gE), + t.showResults ? (d(), c("div", mE, [ + f("p", null, "Selected Value: " + w(t.selectedValue), 1) + ])) : P("", !0) + ]); +} +const d3 = /* @__PURE__ */ A(fE, [["render", vE], ["__scopeId", "data-v-dd791633"]]), bE = C({ + name: "SortControl", + props: { + columns: { + type: Array, + default: () => [] + }, + disabled: { + type: Boolean, + default: !1 + } + }, + emits: ["sort"], + setup(t, { emit: e }) { + const s = m({}); + return { + sortState: s, + toggleSort: (a) => { + t.disabled || (!s.value[a] || s.value[a] === "desc" ? s.value[a] = "asc" : s.value[a] = "desc", e("sort", { column: a, order: s.value[a] })); + }, + getSortIcon: (a) => s.value[a] === "asc" ? "↑" : s.value[a] === "desc" ? "↓" : "" + }; + } +}), yE = { class: "sort-control" }, $E = ["onClick", "aria-disabled"]; +function kE(t, e, s, r, i, a) { + return d(), c("div", yE, [ + (d(!0), c(I, null, L(t.columns, (n) => (d(), c("div", { + key: n, + class: q(["sort-button", { disabled: t.disabled }]), + onClick: (o) => t.toggleSort(n), + "aria-disabled": t.disabled, + role: "button" + }, [ + Fe(w(n) + " ", 1), + f("span", null, w(t.getSortIcon(n)), 1) + ], 10, $E))), 128)) + ]); +} +const c3 = /* @__PURE__ */ A(bE, [["render", kE], ["__scopeId", "data-v-07bb3c30"]]), wE = C({ + name: "SortableList", + props: { + items: { + type: Array, + required: !0 + }, + disabled: { + type: Boolean, + default: !1 + } + }, + setup(t) { + const e = m(null); + return { onDragStart: (i) => { + t.disabled || (e.value = i); + }, onDrop: (i) => { + if (e.value !== null && e.value !== i && !t.disabled) { + const a = t.items[e.value]; + t.items.splice(e.value, 1), t.items.splice(i, 0, a), e.value = null; + } + }, draggingIndex: e }; + } +}), CE = ["aria-disabled"], AE = ["draggable", "onDragstart", "onDrop"]; +function EE(t, e, s, r, i, a) { + return d(), c("ul", { + class: "sortable-list", + "aria-disabled": t.disabled + }, [ + (d(!0), c(I, null, L(t.items, (n, o) => (d(), c("li", { + key: n.id, + draggable: !t.disabled, + onDragstart: (u) => t.onDragStart(o), + onDragover: e[0] || (e[0] = de(() => { + }, ["prevent"])), + onDrop: (u) => t.onDrop(o), + class: q({ dragging: t.draggingIndex === o }) + }, w(n.label), 43, AE))), 128)) + ], 8, CE); +} +const f3 = /* @__PURE__ */ A(wE, [["render", EE], ["__scopeId", "data-v-2f34b2ff"]]), SE = C({ + name: "SortableTable", + props: { + columns: { + type: Array, + required: !0 + }, + data: { + type: Array, + required: !0 + } + }, + setup(t) { + const e = m(null), s = m(1), r = m(""), i = m(null), a = (p) => { + e.value === p ? s.value = -s.value : (e.value = p, s.value = 1); + }, n = (p) => { + i.value = i.value === p ? null : p; + }, o = (p) => e.value === p ? s.value === 1 ? "asc" : "desc" : "", u = W(() => { + let p = t.data.filter( + (g) => Object.values(g).some((k) => k.toString().toLowerCase().includes(r.value.toLowerCase())) + ); + return e.value && p.sort((g, k) => { + const y = g[e.value], b = k[e.value]; + return y < b ? -1 * s.value : y > b ? 1 * s.value : 0; + }), p; + }); + return { sortBy: a, selectRow: n, getSortDirection: o, filteredAndSortedData: u, filterText: r, selectedRow: i }; + } +}), TE = { class: "sortable-table" }, _E = ["onClick"], NE = ["onClick"]; +function IE(t, e, s, r, i, a) { + return d(), c("div", TE, [ + R(f("input", { + type: "text", + "onUpdate:modelValue": e[0] || (e[0] = (n) => t.filterText = n), + class: "filter-input", + placeholder: "Filter table...", + "aria-label": "Filter table" + }, null, 512), [ + [H, t.filterText] + ]), + f("table", null, [ + f("thead", null, [ + f("tr", null, [ + (d(!0), c(I, null, L(t.columns, (n) => (d(), c("th", { + key: n.key, + onClick: (o) => t.sortBy(n.key) + }, [ + Fe(w(n.label) + " ", 1), + f("span", { + class: q(t.getSortDirection(n.key)) + }, null, 2) + ], 8, _E))), 128)) + ]) + ]), + f("tbody", null, [ + (d(!0), c(I, null, L(t.filteredAndSortedData, (n) => (d(), c("tr", { + key: n.id, + class: q({ selected: n.id === t.selectedRow }), + onClick: (o) => t.selectRow(n.id) + }, [ + (d(!0), c(I, null, L(t.columns, (o) => (d(), c("td", { + key: o.key + }, w(n[o.key]), 1))), 128)) + ], 10, NE))), 128)) + ]) + ]) + ]); +} +const p3 = /* @__PURE__ */ A(SE, [["render", IE], ["__scopeId", "data-v-339e4c57"]]), LE = C({ + name: "StarRatingPoll", + props: { + question: { + type: String, + required: !0 + }, + totalStars: { + type: Number, + default: 5 + }, + initialRating: { + type: Number, + default: 0 + }, + isDisabled: { + type: Boolean, + default: !1 + }, + showResults: { + type: Boolean, + default: !1 + } + }, + setup(t) { + const e = m(t.initialRating); + return { + rating: e, + rate: (r) => { + t.isDisabled || (e.value = r); + } + }; + } +}), qE = { + class: "star-rating-poll", + role: "radiogroup", + "aria-labelledby": "poll-question" +}, OE = { id: "poll-question" }, PE = { class: "stars" }, DE = ["aria-checked", "disabled", "onClick"]; +function RE(t, e, s, r, i, a) { + return d(), c("div", qE, [ + f("p", OE, w(t.question), 1), + f("div", PE, [ + (d(!0), c(I, null, L(t.totalStars, (n) => (d(), c("button", { + key: n, + "aria-checked": n <= t.rating, + disabled: t.isDisabled, + onClick: (o) => t.rate(n), + class: q(["star", { filled: n <= t.rating }]), + role: "radio" + }, " ★ ", 10, DE))), 128)) + ]) + ]); +} +const h3 = /* @__PURE__ */ A(LE, [["render", RE], ["__scopeId", "data-v-f5091b77"]]), BE = C({ + name: "StatusDots", + props: { + status: { + type: String, + default: "offline", + validator: (t) => ["online", "offline", "busy", "idle"].includes(t) + } + }, + setup(t) { + const e = W(() => { + switch (t.status) { + case "online": + return "var(--status-online-color, #4caf50)"; + case "offline": + return "var(--status-offline-color, #f44336)"; + case "busy": + return "var(--status-busy-color, #ff9800)"; + case "idle": + return "var(--status-idle-color, #ffeb3b)"; + default: + return "var(--status-offline-color, #f44336)"; + } + }), s = W(() => { + switch (t.status) { + case "online": + return "User is online"; + case "offline": + return "User is offline"; + case "busy": + return "User is busy"; + case "idle": + return "User is idle"; + default: + return "User status unknown"; + } + }); + return { + statusColor: e, + ariaLabel: s + }; + } +}), ME = ["aria-label"]; +function FE(t, e, s, r, i, a) { + return d(), c("div", { + class: "status-dots", + role: "status", + "aria-label": t.ariaLabel + }, [ + f("span", { + class: q(["dot", t.status]), + style: ne({ backgroundColor: t.statusColor }) + }, null, 6) + ], 8, ME); +} +const g3 = /* @__PURE__ */ A(BE, [["render", FE], ["__scopeId", "data-v-2e464e2a"]]), UE = C({ + name: "Stepper", + props: { + steps: { + type: Array, + required: !0 + } + } +}), jE = { + class: "stepper", + "aria-label": "Progress steps" +}, VE = ["aria-current"], HE = { class: "step-label" }; +function zE(t, e, s, r, i, a) { + return d(), c("nav", jE, [ + f("ol", null, [ + (d(!0), c(I, null, L(t.steps, (n, o) => (d(), c("li", { + key: o, + class: q(["step", n.status]), + "aria-current": n.status === "active" ? "step" : void 0 + }, [ + f("span", HE, w(n.label), 1) + ], 10, VE))), 128)) + ]) + ]); +} +const m3 = /* @__PURE__ */ A(UE, [["render", zE], ["__scopeId", "data-v-f866a8b6"]]), GE = C({ + name: "SystemAlertGlobalNotificationBar", + props: { + type: { + type: String, + required: !0, + validator: (t) => ["success", "error", "warning", "info"].includes(t) + }, + message: { + type: String, + required: !0 + } + } +}), KE = ["aria-live"], WE = { class: "notification-message" }; +function ZE(t, e, s, r, i, a) { + return d(), c("div", { + class: q(["notification-bar", t.type]), + role: "alert", + "aria-live": t.type === "error" ? "assertive" : "polite" + }, [ + f("span", WE, w(t.message), 1) + ], 10, KE); +} +const v3 = /* @__PURE__ */ A(GE, [["render", ZE], ["__scopeId", "data-v-d2e84c71"]]), XE = C({ + name: "Tabs", + props: { + tabs: { + type: Array, + required: !0 + }, + initialActiveIndex: { + type: Number, + default: 0 + } + }, + setup(t) { + const e = m(t.initialActiveIndex); + return { activeIndex: e, selectTab: (r) => { + t.tabs[r].disabled || (e.value = r); + } }; + } +}), YE = { + class: "tabs", + role: "tablist" +}, QE = ["aria-selected", "disabled", "onClick"]; +function JE(t, e, s, r, i, a) { + return d(), c("div", YE, [ + (d(!0), c(I, null, L(t.tabs, (n, o) => (d(), c("button", { + key: n.id, + role: "tab", + "aria-selected": t.activeIndex === o, + disabled: n.disabled, + class: q({ + active: t.activeIndex === o, + disabled: n.disabled + }), + onClick: (u) => t.selectTab(o) + }, w(n.label), 11, QE))), 128)) + ]); +} +const b3 = /* @__PURE__ */ A(XE, [["render", JE], ["__scopeId", "data-v-71783afe"]]), xE = C({ + name: "TaskCompletionCheckList", + props: { + tasks: { + type: Array, + required: !0 + } + } +}), eS = { class: "checklist" }, tS = ["checked", "indeterminate", "aria-checked"], sS = { class: "task-label" }; +function nS(t, e, s, r, i, a) { + return d(), c("ul", eS, [ + (d(!0), c(I, null, L(t.tasks, (n, o) => (d(), c("li", { + key: o, + class: q({ + checked: n.status === "checked", + unchecked: n.status === "unchecked", + partiallyComplete: n.status === "partiallyComplete" + }) + }, [ + f("input", { + type: "checkbox", + checked: n.status === "checked", + indeterminate: n.status === "partiallyComplete", + "aria-checked": n.status === "checked" ? "true" : n.status === "partiallyComplete" ? "mixed" : "false", + disabled: "" + }, null, 8, tS), + f("span", sS, w(n.label), 1) + ], 2))), 128)) + ]); +} +const y3 = /* @__PURE__ */ A(xE, [["render", nS], ["__scopeId", "data-v-1aebe86c"]]), rS = C({ + name: "TextTool", + setup() { + const t = m(!1), e = m("Arial"), s = m(16), r = m("#000000"), i = m("left"); + return { + isActive: t, + fontStyle: e, + fontSize: s, + fontColor: r, + alignment: i, + fontStyles: ["Arial", "Verdana", "Times New Roman"], + alignments: ["left", "center", "right"], + toggleActive: () => { + t.value = !t.value; + } + }; + } +}), iS = { class: "text-tool" }, aS = { + key: 0, + class: "text-options" +}, oS = ["value"], lS = ["value"]; +function uS(t, e, s, r, i, a) { + return d(), c("div", iS, [ + f("button", { + onClick: e[0] || (e[0] = (...n) => t.toggleActive && t.toggleActive(...n)), + class: q({ active: t.isActive }), + "aria-label": "Text Tool" + }, "Text Tool", 2), + t.isActive ? (d(), c("div", aS, [ + e[5] || (e[5] = f("label", { for: "font-style" }, "Font Style:", -1)), + R(f("select", { + id: "font-style", + "onUpdate:modelValue": e[1] || (e[1] = (n) => t.fontStyle = n) + }, [ + (d(!0), c(I, null, L(t.fontStyles, (n) => (d(), c("option", { + key: n, + value: n + }, w(n), 9, oS))), 128)) + ], 512), [ + [ye, t.fontStyle] + ]), + e[6] || (e[6] = f("label", { for: "font-size" }, "Font Size:", -1)), + R(f("input", { + id: "font-size", + type: "number", + "onUpdate:modelValue": e[2] || (e[2] = (n) => t.fontSize = n) + }, null, 512), [ + [H, t.fontSize] + ]), + e[7] || (e[7] = f("label", { for: "font-color" }, "Color:", -1)), + R(f("input", { + id: "font-color", + type: "color", + "onUpdate:modelValue": e[3] || (e[3] = (n) => t.fontColor = n) + }, null, 512), [ + [H, t.fontColor] + ]), + e[8] || (e[8] = f("label", { for: "alignment" }, "Alignment:", -1)), + R(f("select", { + id: "alignment", + "onUpdate:modelValue": e[4] || (e[4] = (n) => t.alignment = n) + }, [ + (d(!0), c(I, null, L(t.alignments, (n) => (d(), c("option", { + key: n, + value: n + }, w(n), 9, lS))), 128)) + ], 512), [ + [ye, t.alignment] + ]) + ])) : P("", !0) + ]); +} +const $3 = /* @__PURE__ */ A(rS, [["render", uS], ["__scopeId", "data-v-a7cdacd4"]]), dS = C({ + name: "Textarea", + props: { + placeholder: { + type: String, + default: "Enter text..." + }, + disabled: { + type: Boolean, + default: !1 + } + }, + emits: ["input"], + setup(t, { emit: e }) { + const s = m(""); + return { + text: s, + onInput: () => { + e("input", s.value); + } + }; + } +}), cS = { class: "textarea-container" }, fS = ["placeholder", "disabled"]; +function pS(t, e, s, r, i, a) { + return d(), c("div", cS, [ + R(f("textarea", { + placeholder: t.placeholder, + disabled: t.disabled, + "aria-label": "Textarea input", + "onUpdate:modelValue": e[0] || (e[0] = (n) => t.text = n), + onInput: e[1] || (e[1] = (...n) => t.onInput && t.onInput(...n)) + }, null, 40, fS), [ + [H, t.text] + ]) + ]); +} +const k3 = /* @__PURE__ */ A(dS, [["render", pS], ["__scopeId", "data-v-b0ae56b0"]]), hS = C({ + name: "ThumbsUpThumbsDownPoll", + props: { + question: { + type: String, + required: !0 + }, + initialSelection: { + type: String, + default: null + }, + isDisabled: { + type: Boolean, + default: !1 + }, + showResults: { + type: Boolean, + default: !1 + } + }, + setup(t) { + const e = m(t.initialSelection); + return { + selectedThumb: e, + selectThumb: (r) => { + t.isDisabled || (e.value = r); + } + }; + } +}), gS = { + class: "thumbs-poll", + role: "radiogroup", + "aria-labelledby": "poll-question" +}, mS = { id: "poll-question" }, vS = { class: "thumbs" }, bS = ["aria-checked", "disabled"], yS = ["aria-checked", "disabled"]; +function $S(t, e, s, r, i, a) { + return d(), c("div", gS, [ + f("p", mS, w(t.question), 1), + f("div", vS, [ + f("button", { + "aria-checked": t.selectedThumb === "up", + disabled: t.isDisabled, + onClick: e[0] || (e[0] = (n) => t.selectThumb("up")), + class: q(["thumb", { selected: t.selectedThumb === "up" }]), + role: "radio" + }, " 👍 ", 10, bS), + f("button", { + "aria-checked": t.selectedThumb === "down", + disabled: t.isDisabled, + onClick: e[1] || (e[1] = (n) => t.selectThumb("down")), + class: q(["thumb", { selected: t.selectedThumb === "down" }]), + role: "radio" + }, " 👎 ", 10, yS) + ]) + ]); +} +const w3 = /* @__PURE__ */ A(hS, [["render", $S], ["__scopeId", "data-v-64adc4b2"]]), kS = C({ + name: "TimelineAdjuster", + props: { + feedbackMessage: { + type: String, + default: "" + } + }, + setup(t) { + const e = m(1), s = m(0), r = m(t.feedbackMessage), i = m(!1), a = m(0), n = Array.from({ length: 24 }, (E, S) => S); + return { + zoomLevel: e, + timelineOffset: s, + feedbackMessage: r, + zoomIn: () => { + e.value < 3 && (e.value += 1, r.value = `Zoom level: ${e.value}`); + }, + zoomOut: () => { + e.value > 1 && (e.value -= 1, r.value = `Zoom level: ${e.value}`); + }, + startDrag: (E) => { + i.value = !0, a.value = E.clientX; + }, + stopDrag: () => { + i.value = !1; + }, + drag: (E) => { + i.value && (s.value += E.clientX - a.value, a.value = E.clientX); + }, + goToToday: () => { + r.value = "Navigated to today"; + }, + goToNextDay: () => { + r.value = "Navigated to next day"; + }, + goToPreviousDay: () => { + r.value = "Navigated to previous day"; + }, + hours: n + }; + } +}), wS = { + class: "timeline-adjuster", + role: "region", + "aria-label": "Timeline Adjuster" +}, CS = { class: "zoom-controls" }, AS = { class: "navigation" }, ES = { + key: 0, + class: "feedback" +}; +function SS(t, e, s, r, i, a) { + return d(), c("div", wS, [ + f("div", CS, [ + f("button", { + onClick: e[0] || (e[0] = (...n) => t.zoomIn && t.zoomIn(...n)), + "aria-label": "Zoom in" + }, "+"), + f("button", { + onClick: e[1] || (e[1] = (...n) => t.zoomOut && t.zoomOut(...n)), + "aria-label": "Zoom out" + }, "-") + ]), + f("div", { + class: "timeline-container", + onMousedown: e[2] || (e[2] = (...n) => t.startDrag && t.startDrag(...n)), + onMouseup: e[3] || (e[3] = (...n) => t.stopDrag && t.stopDrag(...n)), + onMouseleave: e[4] || (e[4] = (...n) => t.stopDrag && t.stopDrag(...n)), + onMousemove: e[5] || (e[5] = (...n) => t.drag && t.drag(...n)) + }, [ + f("div", { + class: "timeline", + style: ne({ transform: `translateX(${t.timelineOffset}px)` }) + }, [ + (d(!0), c(I, null, L(t.hours, (n) => (d(), c("div", { + key: n, + class: "time-slot" + }, w(n) + ":00", 1))), 128)) + ], 4) + ], 32), + f("div", AS, [ + f("button", { + onClick: e[6] || (e[6] = (...n) => t.goToToday && t.goToToday(...n)), + "aria-label": "Go to today" + }, "Today"), + f("button", { + onClick: e[7] || (e[7] = (...n) => t.goToNextDay && t.goToNextDay(...n)), + "aria-label": "Go to next day" + }, "Next Day"), + f("button", { + onClick: e[8] || (e[8] = (...n) => t.goToPreviousDay && t.goToPreviousDay(...n)), + "aria-label": "Go to previous day" + }, "Previous Day") + ]), + t.feedbackMessage ? (d(), c("div", ES, w(t.feedbackMessage), 1)) : P("", !0) + ]); +} +const C3 = /* @__PURE__ */ A(kS, [["render", SS], ["__scopeId", "data-v-e10b0b13"]]), TS = C({ + name: "TimelineList", + props: { + items: { + type: Array, + required: !0 + }, + activeIndex: { + type: Number, + default: 0 + } + } +}), _S = { + class: "timeline-list", + role: "list" +}, NS = { class: "timeline-content" }, IS = { class: "timeline-label" }; +function LS(t, e, s, r, i, a) { + return d(), c("ul", _S, [ + (d(!0), c(I, null, L(t.items, (n, o) => (d(), c("li", { + key: n.id, + class: q({ + active: o === t.activeIndex, + completed: n.completed, + inactive: !n.completed && o !== t.activeIndex + }), + role: "listitem" + }, [ + f("div", NS, [ + f("span", IS, w(n.label), 1) + ]) + ], 2))), 128)) + ]); +} +const A3 = /* @__PURE__ */ A(TS, [["render", LS], ["__scopeId", "data-v-553e4b1b"]]), qS = C({ + name: "Toast", + props: { + message: { + type: String, + required: !0 + }, + type: { + type: String, + required: !0 + } + }, + setup() { + const t = m(!0); + return { dismiss: () => { + t.value = !1; + }, isVisible: t }; + } +}); +function OS(t, e, s, r, i, a) { + return d(), c("div", { + class: q(["toast", t.type]), + role: "alert", + "aria-live": "assertive", + "aria-atomic": "true" + }, [ + f("span", null, w(t.message), 1), + f("button", { + class: "close-btn", + onClick: e[0] || (e[0] = (...n) => t.dismiss && t.dismiss(...n)), + "aria-label": "Dismiss" + }, " × ") + ], 2); +} +const E3 = /* @__PURE__ */ A(qS, [["render", OS], ["__scopeId", "data-v-761055db"]]), PS = C({ + name: "ToggleSwitch", + props: { + checked: { + type: Boolean, + default: !1 + }, + disabled: { + type: Boolean, + default: !1 + } + }, + emits: ["change"], + setup(t, { emit: e }) { + return { + onToggle: (r) => { + e("change", r.target.checked); + } + }; + } +}), DS = { class: "toggle-switch" }, RS = ["checked", "disabled", "aria-checked"]; +function BS(t, e, s, r, i, a) { + return d(), c("div", DS, [ + f("input", { + type: "checkbox", + checked: t.checked, + disabled: t.disabled, + onChange: e[0] || (e[0] = (...n) => t.onToggle && t.onToggle(...n)), + "aria-checked": t.checked, + role: "switch" + }, null, 40, RS), + e[1] || (e[1] = f("span", { class: "slider" }, null, -1)) + ]); +} +const S3 = /* @__PURE__ */ A(PS, [["render", BS], ["__scopeId", "data-v-ad0fe00a"]]), MS = C({ + name: "TreeviewList", + props: { + nodes: { + type: Array, + required: !0 + } + }, + methods: { + toggleNode(t) { + t.expanded = !t.expanded; + } + } +}), FS = { + class: "treeview-list", + role: "tree" +}, US = ["aria-expanded"], jS = ["onClick"], VS = { class: "treeview-label" }; +function HS(t, e, s, r, i, a) { + const n = Ll("TreeviewList", !0); + return d(), c("ul", FS, [ + (d(!0), c(I, null, L(t.nodes, (o) => (d(), c("li", { + key: o.id, + class: q({ + expanded: o.expanded, + selected: o.selected + }), + role: "treeitem", + "aria-expanded": o.expanded + }, [ + f("div", { + class: "treeview-node", + onClick: (u) => t.toggleNode(o) + }, [ + f("span", VS, w(o.label), 1) + ], 8, jS), + o.children && o.expanded ? (d(), uo(n, { + key: 0, + nodes: o.children + }, null, 8, ["nodes"])) : P("", !0) + ], 10, US))), 128)) + ]); +} +const T3 = /* @__PURE__ */ A(MS, [["render", HS], ["__scopeId", "data-v-140b42d6"]]), zS = C({ + name: "UndoRedoButtons", + setup() { + const t = m(!1), e = m(!1); + return { + canUndo: t, + canRedo: e, + undo: () => { + t.value && (e.value = !0, t.value = !1); + }, + redo: () => { + e.value && (t.value = !0, e.value = !1); + } + }; + } +}), GS = { class: "undo-redo-buttons" }, KS = ["disabled"], WS = ["disabled"]; +function ZS(t, e, s, r, i, a) { + return d(), c("div", GS, [ + f("button", { + disabled: !t.canUndo, + onClick: e[0] || (e[0] = (...n) => t.undo && t.undo(...n)), + "aria-label": "Undo", + class: q({ active: t.canUndo }) + }, "Undo", 10, KS), + f("button", { + disabled: !t.canRedo, + onClick: e[1] || (e[1] = (...n) => t.redo && t.redo(...n)), + "aria-label": "Redo", + class: q({ active: t.canRedo }) + }, "Redo", 10, WS) + ]); +} +const _3 = /* @__PURE__ */ A(zS, [["render", ZS], ["__scopeId", "data-v-a47f68b7"]]), XS = C({ + name: "Upload", + props: { + message: { + type: String, + required: !0 + }, + status: { + type: String, + required: !0 + }, + progress: { + type: Number, + default: 0 + } + }, + setup(t) { + const e = m(t.status === "uploading" || t.status === "downloading"), s = m(t.status === "uploading"); + return { progressVisible: e, showCancelButton: s, cancelUpload: () => { + console.log("Upload cancelled."); + } }; + } +}), YS = ["value"]; +function QS(t, e, s, r, i, a) { + return d(), c("div", { + class: q(["upload", t.status]) + }, [ + f("span", null, w(t.message), 1), + t.progressVisible ? (d(), c("progress", { + key: 0, + value: t.progress, + max: "100" + }, null, 8, YS)) : P("", !0), + t.showCancelButton ? (d(), c("button", { + key: 1, + onClick: e[0] || (e[0] = (...n) => t.cancelUpload && t.cancelUpload(...n)), + class: "cancel-btn", + "aria-label": "Cancel Upload" + }, " Cancel ")) : P("", !0) + ], 2); +} +const N3 = /* @__PURE__ */ A(XS, [["render", QS], ["__scopeId", "data-v-97cfacf5"]]), JS = C({ + name: "ValidationMessages", + props: { + type: { + type: String, + default: "success", + validator: (t) => ["success", "error", "warning"].includes(t) + } + } +}); +function xS(t, e, s, r, i, a) { + return d(), c("div", { + class: q(["validation-message", t.type]), + role: "alert", + "aria-live": "assertive" + }, [ + ie(t.$slots, "default", {}, void 0, !0) + ], 2); +} +const I3 = /* @__PURE__ */ A(JS, [["render", xS], ["__scopeId", "data-v-3cbe2019"]]), e5 = C({ + name: "Video", + props: { + videoSrc: { + type: String, + required: !0 + }, + initialState: { + type: String, + default: "paused" + } + }, + setup(t) { + const e = m(t.initialState === "uploading"), s = m(t.initialState === "paused"), r = m(t.initialState === "completed"), i = m(t.initialState === "error"), a = m(null); + return { + isUploading: e, + isPaused: s, + isCompleted: r, + isError: i, + videoElement: a, + onPlay: () => { + s.value = !1; + }, + onPause: () => { + s.value = !0; + } + }; + } +}), t5 = { + class: "video-container", + role: "region", + "aria-label": "Video Player" +}, s5 = ["src"], n5 = { + class: "status", + "aria-live": "polite" +}, r5 = { + key: 0, + class: "status-text" +}, i5 = { + key: 1, + class: "status-text" +}, a5 = { + key: 2, + class: "status-text" +}, o5 = { + key: 3, + class: "status-text" +}; +function l5(t, e, s, r, i, a) { + return d(), c("div", t5, [ + f("video", { + ref: "videoElement", + src: t.videoSrc, + onPlay: e[0] || (e[0] = (...n) => t.onPlay && t.onPlay(...n)), + onPause: e[1] || (e[1] = (...n) => t.onPause && t.onPause(...n)), + controls: "", + class: "video-element" + }, null, 40, s5), + f("div", n5, [ + t.isUploading ? (d(), c("span", r5, "Uploading...")) : P("", !0), + t.isPaused ? (d(), c("span", i5, "Paused")) : P("", !0), + t.isCompleted ? (d(), c("span", a5, "Completed")) : P("", !0), + t.isError ? (d(), c("span", o5, "Error")) : P("", !0) + ]) + ]); +} +const L3 = /* @__PURE__ */ A(e5, [["render", l5], ["__scopeId", "data-v-02494477"]]), u5 = C({ + name: "VideoPlayer", + props: { + videoSrc: { + type: String, + required: !0 + } + }, + setup() { + const t = m(!1), e = m(!1), s = m(null); + return Ce(() => { + s.value && (s.value.controls = !1); + }), { + isPlaying: t, + isFullscreen: e, + videoElement: s, + togglePlayPause: () => { + s.value && (s.value.paused ? s.value.play() : s.value.pause()); + }, + toggleFullscreen: () => { + s.value && (document.fullscreenElement ? document.exitFullscreen() : s.value.requestFullscreen()); + }, + onPlay: () => { + t.value = !0; + }, + onPause: () => { + t.value = !1; + }, + onBuffering: () => { + t.value = !1; + }, + onFullscreenChange: () => { + e.value = !!document.fullscreenElement; + } + }; + } +}), d5 = { + class: "video-player", + role: "region", + "aria-label": "Video Player" +}, c5 = ["src"], f5 = { class: "controls" }; +function p5(t, e, s, r, i, a) { + return d(), c("div", d5, [ + f("video", { + ref: "videoElement", + src: t.videoSrc, + onPlay: e[0] || (e[0] = (...n) => t.onPlay && t.onPlay(...n)), + onPause: e[1] || (e[1] = (...n) => t.onPause && t.onPause(...n)), + onWaiting: e[2] || (e[2] = (...n) => t.onBuffering && t.onBuffering(...n)), + onFullscreenchange: e[3] || (e[3] = (...n) => t.onFullscreenChange && t.onFullscreenChange(...n)), + controls: "", + class: "video-element" + }, null, 40, c5), + f("div", f5, [ + f("button", { + onClick: e[4] || (e[4] = (...n) => t.togglePlayPause && t.togglePlayPause(...n)), + "aria-label": "Play/Pause", + class: "control-btn" + }, w(t.isPlaying ? "Pause" : "Play"), 1), + f("button", { + onClick: e[5] || (e[5] = (...n) => t.toggleFullscreen && t.toggleFullscreen(...n)), + "aria-label": "Fullscreen", + class: "control-btn" + }, w(t.isFullscreen ? "Exit Fullscreen" : "Fullscreen"), 1) + ]) + ]); +} +const q3 = /* @__PURE__ */ A(u5, [["render", p5], ["__scopeId", "data-v-2d5e89b2"]]), h5 = C({ + name: "VirtualizedList", + props: { + items: { + type: Array, + required: !0 + }, + itemHeight: { + type: Number, + default: 50 + } + }, + setup(t) { + const e = m(400), s = W(() => Math.ceil(e.value / t.itemHeight)), r = m(0), i = m(!1), a = m(!1), n = W(() => t.items.slice(r.value, r.value + s.value)), o = (u) => { + const p = u.target; + p.scrollTop + p.clientHeight >= p.scrollHeight && (r.value + s.value < t.items.length ? (i.value = !0, setTimeout(() => { + r.value += s.value, i.value = !1; + }, 1e3)) : a.value = !0); + }; + return Ce(() => { + var u; + e.value = ((u = document.querySelector(".virtualized-list")) == null ? void 0 : u.clientHeight) || 400; + }), { visibleItems: n, onScroll: o, loading: i, endOfList: a }; + } +}), g5 = { + key: 0, + class: "loading-indicator" +}, m5 = { + key: 1, + class: "end-of-list" +}; +function v5(t, e, s, r, i, a) { + return d(), c("div", { + class: "virtualized-list", + role: "list", + onScroll: e[0] || (e[0] = (...n) => t.onScroll && t.onScroll(...n)) + }, [ + (d(!0), c(I, null, L(t.visibleItems, (n) => (d(), c("div", { + key: n.id, + class: "list-item", + role: "listitem" + }, w(n.label), 1))), 128)), + t.loading ? (d(), c("div", g5, "Loading more items...")) : t.endOfList ? (d(), c("div", m5, "End of list")) : P("", !0) + ], 32); +} +const O3 = /* @__PURE__ */ A(h5, [["render", v5], ["__scopeId", "data-v-a52d90b7"]]), b5 = C({ + name: "VisualCueForAccessibilityFocusIndicator", + props: { + label: { + type: String, + required: !0 + }, + isFocused: { + type: Boolean, + default: !1 + } + }, + setup(t) { + const e = m(t.isFocused); + return { handleFocus: () => { + e.value = !0; + }, handleBlur: () => { + e.value = !1; + } }; + } +}); +function y5(t, e, s, r, i, a) { + return d(), c("div", { + class: q(["focus-indicator", { focused: t.isFocused }]), + tabindex: "0", + onFocus: e[0] || (e[0] = (...n) => t.handleFocus && t.handleFocus(...n)), + onBlur: e[1] || (e[1] = (...n) => t.handleBlur && t.handleBlur(...n)) + }, [ + f("span", null, w(t.label), 1) + ], 34); +} +const P3 = /* @__PURE__ */ A(b5, [["render", y5], ["__scopeId", "data-v-3d5d03e6"]]), $5 = C({ + name: "WinningHandDisplay", + props: { + communityCards: { + type: Array, + default: () => [] + }, + playerCards: { + type: Array, + default: () => [] + }, + winningCards: { + type: Array, + default: () => [] + }, + isHidden: { + type: Boolean, + default: !1 + } + } +}), k5 = { class: "cards" }; +function w5(t, e, s, r, i, a) { + return d(), c("div", { + class: q(["winning-hand-display", { hidden: t.isHidden }]), + "aria-label": "Winning Hand Display" + }, [ + f("div", k5, [ + (d(!0), c(I, null, L(t.communityCards, (n, o) => (d(), c("div", { + key: "community-" + o, + class: "card community-card" + }, w(n), 1))), 128)), + (d(!0), c(I, null, L(t.playerCards, (n, o) => (d(), c("div", { + key: "player-" + o, + class: q(["card player-card", { winner: t.winningCards.includes(n) }]) + }, w(n), 3))), 128)) + ]) + ], 2); +} +const D3 = /* @__PURE__ */ A($5, [["render", w5], ["__scopeId", "data-v-65bb899c"]]), C5 = C({ + name: "YesNoPoll", + props: { + question: { + type: String, + required: !0 + }, + initialSelection: { + type: String, + default: null + }, + isDisabled: { + type: Boolean, + default: !1 + }, + showResults: { + type: Boolean, + default: !1 + } + }, + setup(t) { + const e = m(t.initialSelection); + return { + selectedOption: e, + selectOption: (r) => { + t.isDisabled || (e.value = r); + } + }; + } +}), A5 = { + class: "yes-no-poll", + role: "radiogroup", + "aria-labelledby": "poll-question" +}, E5 = { id: "poll-question" }, S5 = { class: "options" }, T5 = ["aria-checked", "disabled"], _5 = ["aria-checked", "disabled"]; +function N5(t, e, s, r, i, a) { + return d(), c("div", A5, [ + f("p", E5, w(t.question), 1), + f("div", S5, [ + f("button", { + "aria-checked": t.selectedOption === "yes", + disabled: t.isDisabled, + onClick: e[0] || (e[0] = (n) => t.selectOption("yes")), + class: q(["option", { selected: t.selectedOption === "yes" }]), + role: "radio" + }, " Yes ", 10, T5), + f("button", { + "aria-checked": t.selectedOption === "no", + disabled: t.isDisabled, + onClick: e[1] || (e[1] = (n) => t.selectOption("no")), + class: q(["option", { selected: t.selectedOption === "no" }]), + role: "radio" + }, " No ", 10, _5) + ]) + ]); +} +const R3 = /* @__PURE__ */ A(C5, [["render", N5], ["__scopeId", "data-v-5d57e636"]]), I5 = C({ + name: "ZoomTool", + setup() { + const t = m(100); + return { + zoomLevel: t, + zoomIn: () => { + t.value < 500 && (t.value += 10); + }, + zoomOut: () => { + t.value > 10 && (t.value -= 10); + }, + fitToScreen: () => { + t.value = 100; + }, + resetZoom: () => { + t.value = 100; + } + }; + } +}), L5 = { class: "zoom-tool" }, q5 = { class: "zoom-controls" }, O5 = { class: "zoom-level" }; +function P5(t, e, s, r, i, a) { + return d(), c("div", L5, [ + f("div", q5, [ + f("button", { + onClick: e[0] || (e[0] = (...n) => t.zoomIn && t.zoomIn(...n)), + "aria-label": "Zoom In" + }, "+"), + f("span", O5, w(t.zoomLevel) + "%", 1), + f("button", { + onClick: e[1] || (e[1] = (...n) => t.zoomOut && t.zoomOut(...n)), + "aria-label": "Zoom Out" + }, "-") + ]), + f("button", { + onClick: e[2] || (e[2] = (...n) => t.fitToScreen && t.fitToScreen(...n)), + "aria-label": "Fit to Screen" + }, "Fit to Screen"), + f("button", { + onClick: e[3] || (e[3] = (...n) => t.resetZoom && t.resetZoom(...n)), + "aria-label": "Reset Zoom" + }, "Reset") + ]); +} +const B3 = /* @__PURE__ */ A(I5, [["render", P5], ["__scopeId", "data-v-e6c5dde0"]]); +export { + M5 as Accordion, + F5 as ActionableList, + U5 as ActivityIndicators, + j5 as AdminViewScheduler, + V5 as AudioPlayer, + H5 as AudioPlayerAdvanced, + z5 as AudioWaveformDisplay, + G5 as Avatar, + K5 as Badge, + W5 as BadgeWithCounts, + Z5 as BatteryLevelIndicator, + X5 as BetSlider, + Y5 as BottomNavigationBar, + Q5 as BreadcrumbWithDropdowns, + J5 as Breadcrumbs, + x5 as BrushTool, + eT as Button, + tT as CalendarView, + sT as CallButton, + nT as Canvas, + rT as Captcha, + iT as CardActions, + aT as CardBadge, + oT as CardBody, + lT as CardFooter, + uT as CardHeader, + dT as CardImage, + cT as CardbasedList, + fT as Carousel, + pT as ChatBubble, + hT as CheckList, + gT as Checkbox, + mT as Chips, + vT as CollapsibleMenuList, + bT as ColorPicker, + yT as ColumnVisibilityToggle, + $T as CommandPalette, + kT as CommunityCards, + wT as ContextualList, + CT as ContextualNavigation, + AT as CountdownTimer, + ET as DarkModeToggle, + ST as DataExportButton, + TT as DataFilterPanel, + _T as DataGrid, + NT as DataImportDialog, + IT as DataSummary, + LT as DataTable, + qT as DateAndTimePicker, + OT as DatePicker, + PT as DealerButton, + DT as DeckOfCards, + RT as DiscardPile, + BT as DragAndDropScheduler, + MT as DropdownMenu, + FT as EditableDataTable, + UT as EmbeddedMediaIframe, + jT as EmojiReactionPoll, + VT as EraserTool, + HT as EventDetailsDialog, + zT as EventFilterBar, + GT as EventReminderSystem, + KT as ExpandableList, + WT as FavoritesList, + ZT as FieldEditableDataTable, + XT as FileInputWithPreview, + YT as FileUpload, + QT as FillTool, + JT as FilterableList, + xT as FlipCard, + e4 as FloatingActionButton, + t4 as FoldButton, + s4 as GroupedList, + n4 as HandOfCards, + r4 as IconButton, + i4 as ImageChoicePoll, + a4 as ImageSlider, + o4 as InteractiveMediaMap, + l4 as InteractivePollResults, + u4 as LayerPanel, + d4 as LiveResultsPoll, + c4 as LiveStreamPlayer, + f4 as LoadMoreButtonInList, + p4 as LoadingBarsWithSteps, + h4 as LoadingSpinner, + g4 as MediaGallery, + m4 as MultipleChoicePoll, + v4 as MultiselectList, + b4 as Notification, + y4 as NotificationBellIcon, + $4 as NumberInputWithIncrement, + k4 as NumberedList, + w4 as OpenEndedPoll, + C4 as Pagination, + A4 as PaginationControl, + E4 as PasswordConfirmationField, + S4 as PinnedList, + T4 as PlayingCard, + _4 as PodcastPlayer, + N4 as PokerChips, + I4 as PokerHand, + L4 as PokerTable, + q4 as PokerTimer, + O4 as Pot, + P4 as ProgressBar, + D4 as ProgressCircle, + R4 as PublicViewCalendar, + B4 as RadioButton, + M4 as RaiseButton, + F4 as RangeSlider, + U4 as RankingPoll, + j4 as RatingStars, + V4 as RecurringEventScheduler, + X4 as RichTextEditor, + Y4 as RulerAndGuides, + Q4 as ScheduleCRUDPanel, + J4 as ScrollableList, + x4 as SearchBar, + e3 as SearchBarWithSuggestions, + t3 as SearchInputWithFilterOptions, + s3 as SearchWithAutocomplete, + n3 as SelectableListWithItemDetails, + r3 as ShapeLibrary, + i3 as ShapeTool, + a3 as SignalStrengthIndicator, + o3 as SingleChoicePoll, + l3 as SkeletonLoading, + u3 as Slider, + d3 as SliderPoll, + c3 as SortControl, + f3 as SortableList, + p3 as SortableTable, + h3 as StarRatingPoll, + g3 as StatusDots, + m3 as Stepper, + v3 as SystemAlertGlobalNotificationBar, + b3 as Tabs, + y3 as TaskCompletionCheckList, + $3 as TextTool, + k3 as Textarea, + B5 as ThreeSixtyDegreeImageViewer, + w3 as ThumbsUpThumbsDownPoll, + C3 as TimelineAdjuster, + A3 as TimelineList, + E3 as Toast, + S3 as ToggleSwitch, + T3 as TreeviewList, + _3 as UndoRedoButtons, + N3 as Upload, + I3 as ValidationMessages, + L3 as Video, + q3 as VideoPlayer, + O3 as VirtualizedList, + P3 as VisualCueForAccessibilityFocusIndicator, + D3 as WinningHandDisplay, + R3 as YesNoPoll, + B3 as ZoomTool +}; diff --git a/pkgs/experimental/layout_engine_atoms/src/layout_engine_atoms/runtime/vue/realtime.py b/pkgs/experimental/layout_engine_atoms/src/layout_engine_atoms/runtime/vue/realtime.py new file mode 100644 index 0000000000..73e1d61536 --- /dev/null +++ b/pkgs/experimental/layout_engine_atoms/src/layout_engine_atoms/runtime/vue/realtime.py @@ -0,0 +1,163 @@ +from __future__ import annotations + +import asyncio +from dataclasses import dataclass, field +from typing import Any, Awaitable, Callable, Mapping, MutableMapping, Sequence + +from fastapi import FastAPI, WebSocket, WebSocketDisconnect + + +RealtimePublisher = Callable[["WebsocketMuxHub"], Awaitable[None]] + + +@dataclass(slots=True) +class RealtimeChannel: + """Declarative manifest channel description.""" + + id: str + scope: str = "site" + topic: str | None = None + description: str | None = None + meta: Mapping[str, Any] | None = None + + def as_manifest(self) -> dict[str, Any]: + """Return a manifest-ready payload.""" + + payload: dict[str, Any] = { + "id": self.id, + "scope": self.scope, + } + if self.topic: + payload["topic"] = self.topic + if self.description: + payload["description"] = self.description + if self.meta: + payload["meta"] = dict(self.meta) + return payload + + +@dataclass(slots=True) +class RealtimeOptions: + """Configuration for websocket mux routing.""" + + path: str = "/ws/events" + channels: Sequence[RealtimeChannel] = () + publishers: Sequence[RealtimePublisher] = () + auto_subscribe: bool = True + bindings: Sequence["RealtimeBinding"] = () + + +class WebsocketMuxHub: + """Minimal mux-compatible hub that manages websocket subscribers.""" + + def __init__(self, *, path: str) -> None: + self.path = path + self._subscriptions: dict[WebSocket, set[str]] = {} + self._lock = asyncio.Lock() + self._publisher_tasks: list[asyncio.Task[Any]] = [] + + async def connect(self, websocket: WebSocket) -> None: + await websocket.accept() + async with self._lock: + self._subscriptions[websocket] = set() + + async def disconnect(self, websocket: WebSocket) -> None: + async with self._lock: + self._subscriptions.pop(websocket, None) + + async def disconnect_all(self) -> None: + async with self._lock: + websockets = list(self._subscriptions.keys()) + for websocket in websockets: + await self.disconnect(websocket) + + async def subscribe(self, websocket: WebSocket, channel: str) -> None: + async with self._lock: + if websocket not in self._subscriptions: + self._subscriptions[websocket] = {channel} + else: + self._subscriptions[websocket].add(channel) + + async def unsubscribe(self, websocket: WebSocket, channel: str) -> None: + async with self._lock: + channels = self._subscriptions.get(websocket) + if not channels: + return + channels.discard(channel) + if not channels: + self._subscriptions.pop(websocket, None) + + async def broadcast(self, channel: str, payload: Mapping[str, Any]) -> None: + message = {"channel": channel, "payload": dict(payload)} + async with self._lock: + targets = [ + websocket + for websocket, channels in self._subscriptions.items() + if channel in channels + ] + stale: list[WebSocket] = [] + for websocket in targets: + try: + await websocket.send_json(message) + except Exception: + stale.append(websocket) + for websocket in stale: + await self.disconnect(websocket) + + async def start_publishers(self, publishers: Sequence[RealtimePublisher]) -> None: + """Launch background publisher coroutines.""" + + if not publishers: + return + loop = asyncio.get_running_loop() + for publisher in publishers: + task = loop.create_task(publisher(self)) + self._publisher_tasks.append(task) + + async def stop_publishers(self) -> None: + for task in self._publisher_tasks: + task.cancel() + try: + await task + except asyncio.CancelledError: + pass + self._publisher_tasks.clear() + + def mount(self, app: FastAPI, route_path: str) -> None: + """Register the websocket route on the FastAPI app.""" + + async def _endpoint(websocket: WebSocket) -> None: + await self.connect(websocket) + try: + while True: + message = await websocket.receive_json() + action = message.get("action") + channel = message.get("channel") + if action == "subscribe" and isinstance(channel, str): + await self.subscribe(websocket, channel) + elif action == "unsubscribe" and isinstance(channel, str): + await self.unsubscribe(websocket, channel) + elif action == "publish" and isinstance(channel, str): + payload = message.get("payload") or {} + if isinstance(payload, Mapping): + await self.broadcast(channel, payload) + except WebSocketDisconnect: + await self.disconnect(websocket) + + app.add_api_websocket_route(route_path, _endpoint) + + +@dataclass(slots=True) +class RealtimeBinding: + """Declarative mapping between a channel payload and a tile's props.""" + + channel: str + tile_id: str + fields: Mapping[str, str] = field(default_factory=dict) + + def as_payload(self) -> MutableMapping[str, Any]: + return { + "channel": self.channel, + "tileId": self.tile_id, + "fields": dict(self.fields), + } diff --git a/pkgs/experimental/layout_engine_atoms/src/layout_engine_atoms/runtime/vue/template.py b/pkgs/experimental/layout_engine_atoms/src/layout_engine_atoms/runtime/vue/template.py new file mode 100644 index 0000000000..0c661f583d --- /dev/null +++ b/pkgs/experimental/layout_engine_atoms/src/layout_engine_atoms/runtime/vue/template.py @@ -0,0 +1,85 @@ +from __future__ import annotations + +import json +from importlib.resources import files +from typing import Iterable + +TEMPLATE_NAME = "mpa_shell.html" + + +def _load_template() -> str: + template_path = files("layout_engine_atoms.runtime.vue.templates") / TEMPLATE_NAME + return template_path.read_text(encoding="utf-8") + + +def _indent(content: str, spaces: int) -> str: + prefix = " " * spaces + return "\n".join(f"{prefix}{line}" if line else "" for line in content.splitlines()) + + +def _build_extra_styles(extra_styles: Iterable[str] | None) -> str: + if not extra_styles: + return "" + + tags: list[str] = [] + for entry in extra_styles: + stripped = entry.strip() + if not stripped: + continue + if stripped.startswith("<"): + tags.append(stripped) + else: + tags.append(f'') + if not tags: + return "" + return "\n" + _indent("\n".join(tags), 4) + + +def _build_pre_boot_scripts(scripts: Iterable[str] | None) -> str: + if not scripts: + return "" + rendered = "\n".join(scripts) + if not rendered: + return "" + return _indent(rendered, 4) + "\n" + + +def _build_css_variables(palette: dict[str, str]) -> str: + if not palette: + return " --le-accent: rgba(56, 189, 248, 0.75);" + lines = [ + f"--le-{key.replace('_', '-')}: {value};" for key, value in palette.items() + ] + return _indent("\n".join(lines), 8) + + +_TEMPLATE_CACHE: str | None = None + + +def render_shell( + *, + title: str, + import_map: dict[str, str], + config_payload: dict, + palette: dict[str, str], + bootstrap_module: str, + extra_styles: Iterable[str] | None = None, + pre_boot_scripts: Iterable[str] | None = None, +) -> str: + global _TEMPLATE_CACHE + if _TEMPLATE_CACHE is None: + _TEMPLATE_CACHE = _load_template() + + import_map_json = json.dumps({"imports": import_map}, indent=8) + config_json = json.dumps(config_payload, indent=2) + + rendered = _TEMPLATE_CACHE.format( + title=title, + extra_style_tags=_build_extra_styles(extra_styles), + css_variables=_build_css_variables(palette), + import_map_json=import_map_json, + pre_boot_scripts=_build_pre_boot_scripts(pre_boot_scripts), + config_json=_indent(config_json, 4), + bootstrap_module=bootstrap_module, + ) + return rendered diff --git a/pkgs/experimental/layout_engine_atoms/src/layout_engine_atoms/runtime/vue/templates/mpa_shell.html b/pkgs/experimental/layout_engine_atoms/src/layout_engine_atoms/runtime/vue/templates/mpa_shell.html new file mode 100644 index 0000000000..aee8d6f4ec --- /dev/null +++ b/pkgs/experimental/layout_engine_atoms/src/layout_engine_atoms/runtime/vue/templates/mpa_shell.html @@ -0,0 +1,53 @@ + + + + + + {title} + + +{extra_style_tags} + + +{pre_boot_scripts} + + +
    +
    +
    +
    +
    + + + + diff --git a/pkgs/experimental/layout_engine_atoms/src/layout_engine_atoms/spec.py b/pkgs/experimental/layout_engine_atoms/src/layout_engine_atoms/spec.py index 929c2702dc..7ebf1c38fa 100644 --- a/pkgs/experimental/layout_engine_atoms/src/layout_engine_atoms/spec.py +++ b/pkgs/experimental/layout_engine_atoms/src/layout_engine_atoms/spec.py @@ -22,6 +22,10 @@ class AtomPreset(BaseModel): defaults: Mapping[str, Any] = Field(default_factory=dict) family: str | None = None description: str | None = None + framework: str | None = None + package: str | None = None + tokens: Mapping[str, Any] = Field(default_factory=dict) + registry: Mapping[str, Any] = Field(default_factory=dict) def to_spec(self) -> AtomSpec: """Convert the preset into a concrete :class:`AtomSpec`.""" @@ -31,4 +35,9 @@ def to_spec(self) -> AtomSpec: export=self.export, version=self.version, defaults=dict(self.defaults), + family=self.family, + framework=self.framework, + package=self.package, + tokens=dict(self.tokens), + registry=dict(self.registry), ) diff --git a/pkgs/experimental/layout_engine_atoms/src/layout_engine_atoms/swarma/__init__.py b/pkgs/experimental/layout_engine_atoms/src/layout_engine_atoms/swarma/__init__.py new file mode 100644 index 0000000000..c3fe566276 --- /dev/null +++ b/pkgs/experimental/layout_engine_atoms/src/layout_engine_atoms/swarma/__init__.py @@ -0,0 +1,5 @@ +"""SwarmaKit atom helpers.""" + +from .base import AtomProps, SwarmaAtomCatalog + +__all__ = ["AtomProps", "SwarmaAtomCatalog"] diff --git a/pkgs/experimental/layout_engine_atoms/src/layout_engine_atoms/swarma/base.py b/pkgs/experimental/layout_engine_atoms/src/layout_engine_atoms/swarma/base.py new file mode 100644 index 0000000000..c1a2bebb08 --- /dev/null +++ b/pkgs/experimental/layout_engine_atoms/src/layout_engine_atoms/swarma/base.py @@ -0,0 +1,100 @@ +from __future__ import annotations + +from typing import Any, Iterable, Mapping, Type + +from pydantic import BaseModel +from layout_engine import AtomSpec + +from ..default import AtomPresetCatalog +from ..spec import AtomPreset + + +class AtomProps(BaseModel): + """Loose schema for SwarmaKit atom props (override per framework atom).""" + + model_config = {"extra": "allow"} + + +class SwarmaAtomCatalog(AtomPresetCatalog): + """Catalog adapter that adds Swarma-specific helpers on top of presets.""" + + def __init__( + self, + presets: Mapping[str, AtomPreset | AtomSpec] | Iterable[AtomPreset | AtomSpec], + *, + props_schema: Type[AtomProps] = AtomProps, + ): + self._props_schema = props_schema + normalized = self._normalize_presets(presets) + super().__init__(normalized) + + @property + def props_schema(self) -> Type[AtomProps]: + return self._props_schema + + def get_spec(self, role: str) -> AtomSpec: + return self.get(role).to_spec() + + def merge_props( + self, role: str, overrides: Mapping[str, Any] | None = None + ) -> dict[str, Any]: + preset = self.get(role) + merged = {**dict(preset.defaults), **(overrides or {})} + return self._props_schema(**merged).model_dump() + + def with_overrides(self, role: str, **patch: Any) -> "SwarmaAtomCatalog": + """Return a new catalog with the given role patched (e.g., defaults/version).""" + + if role not in self._presets: + raise KeyError(f"Unknown Swarma atom role: {role}") + + updated = self._presets[role].model_copy(update=patch) + presets = dict(self._presets) + presets[role] = updated + return self.__class__(presets, props_schema=self._props_schema) + + def with_extra_presets( + self, + extra_presets: Mapping[str, AtomPreset | AtomSpec] + | Iterable[AtomPreset | AtomSpec], + ) -> "SwarmaAtomCatalog": + presets = dict(self._presets) + updates = self._normalize_presets(extra_presets) + presets.update(updates) + return self.__class__(presets, props_schema=self._props_schema) + + @staticmethod + def _ensure_preset(value: AtomPreset | AtomSpec) -> AtomPreset: + if isinstance(value, AtomPreset): + return value + if isinstance(value, AtomSpec): + return AtomPreset( + role=value.role, + module=value.module, + export=value.export, + version=value.version, + defaults=dict(value.defaults), + family=getattr(value, "family", None), + framework=getattr(value, "framework", None), + package=getattr(value, "package", None), + tokens=dict(getattr(value, "tokens", {}) or {}), + registry=dict(getattr(value, "registry", {}) or {}), + ) + raise TypeError(f"Unsupported preset payload type: {type(value)!r}") + + @classmethod + def _normalize_presets( + cls, + data: Mapping[str, AtomPreset | AtomSpec] | Iterable[AtomPreset | AtomSpec], + ) -> dict[str, AtomPreset]: + if isinstance(data, Mapping): + normalized: dict[str, AtomPreset] = {} + for candidate in data.values(): + preset = cls._ensure_preset(candidate) + normalized[preset.role] = preset + return normalized + normalized: dict[str, AtomPreset] = {} + for candidate in data: + preset = cls._ensure_preset(candidate) + normalized[preset.role] = preset + return normalized diff --git a/pkgs/experimental/layout_engine_atoms/tests/test_atom_catalogs.py b/pkgs/experimental/layout_engine_atoms/tests/test_atom_catalogs.py new file mode 100644 index 0000000000..f60eeaefba --- /dev/null +++ b/pkgs/experimental/layout_engine_atoms/tests/test_atom_catalogs.py @@ -0,0 +1,140 @@ +from __future__ import annotations + +import pytest +from layout_engine import AtomSpec + +from layout_engine_atoms.default import AtomPresetCatalog +from layout_engine_atoms.spec import AtomPreset +from layout_engine_atoms.swarma import AtomProps, SwarmaAtomCatalog + + +def make_preset( + role: str, + *, + module: str = "pkg", + export: str = "Component", + version: str = "1.0.0", + defaults: dict[str, object] | None = None, + framework: str | None = None, + family: str | None = None, + registry: dict[str, object] | None = None, +) -> AtomPreset: + return AtomPreset( + role=role, + module=module, + export=export, + version=version, + defaults=defaults or {}, + framework=framework, + family=family, + registry=registry or {}, + ) + + +def make_spec( + role: str, *, module: str = "pkg", export: str = "Component", version: str = "1.0.0" +) -> AtomSpec: + return AtomSpec( + role=role, + module=module, + export=export, + version=version, + defaults={"foo": "bar"}, + ) + + +def test_atom_preset_catalog_from_specs_and_registry_roundtrip() -> None: + specs = [ + AtomSpec( + role="test:a", + module="pkg", + export="Alpha", + version="1.0", + defaults={"flag": True}, + ), + AtomSpec( + role="test:b", + module="pkg", + export="Beta", + version="1.1", + defaults={"count": 3}, + ), + ] + + catalog = AtomPresetCatalog.from_specs(specs) + + assert sorted(p.role for p in catalog.presets()) == ["test:a", "test:b"] + as_specs = catalog.as_specs() + assert set(as_specs) == {"test:a", "test:b"} + assert as_specs["test:a"].export == "Alpha" + + registry = catalog.build_registry() + stored = registry.get("test:b") + assert stored.export == "Beta" + assert stored.defaults["count"] == 3 + + +def test_atom_preset_catalog_get_unknown_role_raises() -> None: + catalog = AtomPresetCatalog({"test:a": make_preset("test:a")}) + + with pytest.raises(KeyError): + catalog.get("missing") + + +class ButtonProps(AtomProps): + size: str = "md" + tone: str | None = None + + +def test_swarma_atom_catalog_merge_props_and_get_spec() -> None: + preset = make_preset( + "swarmakit:vue:button", + defaults={"tone": "primary"}, + framework="vue", + family="swarmakit", + registry={"name": "swarmakit"}, + ) + catalog = SwarmaAtomCatalog([preset], props_schema=ButtonProps) + + spec = catalog.get_spec("swarmakit:vue:button") + assert spec.module == "pkg" + assert getattr(spec, "framework", None) in (None, "vue") + + merged = catalog.merge_props("swarmakit:vue:button", {"size": "lg"}) + assert merged == {"size": "lg", "tone": "primary"} + + +def test_swarma_atom_catalog_with_overrides_returns_new_instance() -> None: + preset = make_preset("swarmakit:vue:badge", defaults={"tone": "info"}) + catalog = SwarmaAtomCatalog([preset], props_schema=ButtonProps) + + updated = catalog.with_overrides("swarmakit:vue:badge", defaults={"tone": "warn"}) + + assert updated is not catalog + assert updated.merge_props("swarmakit:vue:badge")["tone"] == "warn" + assert catalog.merge_props("swarmakit:vue:badge")["tone"] == "info" + + +def test_swarma_atom_catalog_with_extra_presets_accepts_specs() -> None: + preset = make_preset("swarmakit:vue:badge") + extra_spec = make_spec("swarmakit:vue:chip", module="pkg2") + + catalog = SwarmaAtomCatalog([preset], props_schema=ButtonProps) + extended = catalog.with_extra_presets({"alias": extra_spec}) + + assert "swarmakit:vue:badge" in extended.as_specs() + assert "swarmakit:vue:chip" in extended.as_specs() + assert extended.get_spec("swarmakit:vue:chip").module == "pkg2" + + +def test_swarma_atom_catalog_with_overrides_unknown_role() -> None: + catalog = SwarmaAtomCatalog([make_preset("swarmakit:vue:button")]) + with pytest.raises(KeyError): + catalog.with_overrides("swarmakit:vue:missing", defaults={}) + + +def test_swarma_atom_catalog_rejects_invalid_payload() -> None: + catalog = SwarmaAtomCatalog([make_preset("swarmakit:vue:button")]) + + with pytest.raises(TypeError): + catalog.with_extra_presets({"bad": object()}) # type: ignore[arg-type] diff --git a/pkgs/experimental/layout_engine_atoms/tests/test_catalog.py b/pkgs/experimental/layout_engine_atoms/tests/test_catalog.py index 067b95ed38..d893e11070 100644 --- a/pkgs/experimental/layout_engine_atoms/tests/test_catalog.py +++ b/pkgs/experimental/layout_engine_atoms/tests/test_catalog.py @@ -6,27 +6,54 @@ AtomPreset, build_default_registry, ) +from layout_engine_atoms.catalog import swarma_svelte, swarma_vue def test_default_presets_are_registered() -> None: assert DEFAULT_PRESETS, "expected non-empty preset catalog" - assert "ui:button:primary" in DEFAULT_PRESETS - assert isinstance(DEFAULT_PRESETS["ui:button:primary"], AtomPreset) + assert "swarmakit:vue:button" in DEFAULT_PRESETS + button_preset = DEFAULT_PRESETS["swarmakit:vue:button"] + assert isinstance(button_preset, AtomPreset) + assert button_preset.module == "@swarmakit/vue" + assert button_preset.framework == "vue" + assert button_preset.registry.get("name") == "swarmakit" def test_default_atoms_match_presets() -> None: assert set(DEFAULT_ATOMS) == set(DEFAULT_PRESETS) - spec = DEFAULT_ATOMS["viz:metric:kpi"] - assert spec.module == "@swarmauri/atoms/Metrics" - assert spec.export == "KpiCard" + spec = DEFAULT_ATOMS["swarmakit:vue:button"] + assert spec.module == "@swarmakit/vue" + assert spec.export == "Button" + assert getattr(spec, "framework", None) in (None, "vue") + registry_meta = dict(getattr(spec, "registry", {})) + if registry_meta: + assert registry_meta.get("name") == "swarmakit" def test_build_default_registry() -> None: registry = build_default_registry() assert isinstance(registry, AtomRegistry) - spec = registry.get("viz:metric:kpi") - assert spec.defaults["format"] == "compact" + spec = registry.get("swarmakit:vue:button") + assert spec.module == "@swarmakit/vue" + assert getattr(spec, "framework", None) in (None, "vue") - merged = registry.resolve_props("input:date-range", {"presets": ["today"]}) - assert merged["presets"] == ["today"] - assert "presets" in merged + merged = registry.resolve_props("swarmakit:vue:button", {"size": "lg"}) + assert merged["size"] == "lg" + + +def test_swarma_vue_presets_cover_swarmakit_components() -> None: + assert len(swarma_vue.DEFAULT_PRESETS) == 157 + assert "swarmakit:vue:button" in swarma_vue.DEFAULT_PRESETS + registry = swarma_vue.build_registry() + assert isinstance(registry, AtomRegistry) + button_spec = registry.get("swarmakit:vue:button") + assert button_spec.module == "@swarmakit/vue" + + +def test_swarma_svelte_presets_cover_swarmakit_components() -> None: + assert len(swarma_svelte.DEFAULT_PRESETS) == 71 + assert "swarmakit:svelte:button" in swarma_svelte.DEFAULT_PRESETS + registry = swarma_svelte.build_registry() + assert isinstance(registry, AtomRegistry) + button_spec = registry.get("swarmakit:svelte:button") + assert button_spec.module == "@swarmakit/svelte" diff --git a/pkgs/experimental/layout_engine_atoms/tests/test_manifest_helpers.py b/pkgs/experimental/layout_engine_atoms/tests/test_manifest_helpers.py new file mode 100644 index 0000000000..e377a47b14 --- /dev/null +++ b/pkgs/experimental/layout_engine_atoms/tests/test_manifest_helpers.py @@ -0,0 +1,64 @@ +from __future__ import annotations + +from layout_engine.structure import block, col, row, table + +from layout_engine_atoms.manifest import ( + create_registry, + quick_manifest, + quick_manifest_from_table, + tile, +) + + +def test_quick_manifest_builds_manifest() -> None: + registry = create_registry() + + manifest = quick_manifest( + [ + tile( + "hero", "swarmakit:vue:cardbased-list", span="full", props={"cards": []} + ), + tile( + "summary", + "swarmakit:vue:data-summary", + span="half", + props={"data": [1, 2, 3]}, + ), + tile( + "activity", + "swarmakit:vue:activity-indicators", + span="half", + props={"type": "success"}, + ), + ], + registry=registry, + columns=2, + ) + + assert manifest.kind == "layout_manifest" + ids = [t["id"] if isinstance(t, dict) else t.id for t in manifest.tiles] + assert ids == ["hero", "summary", "activity"] + + +def test_quick_manifest_from_table_builds_manifest() -> None: + registry = create_registry() + + layout = table( + row(col(block("hero")), col(block("activity"))), + row(col(block("summary")), col(block("extra"))), + ) + + manifest = quick_manifest_from_table( + layout, + [ + tile("hero", "swarmakit:vue:cardbased-list"), + tile("summary", "swarmakit:vue:data-summary"), + tile("activity", "swarmakit:vue:activity-indicators"), + tile("extra", "swarmakit:vue:progress-bar"), + ], + registry=registry, + ) + + assert manifest.kind == "layout_manifest" + ids = [t["id"] if isinstance(t, dict) else t.id for t in manifest.tiles] + assert set(ids) == {"hero", "summary", "activity", "extra"} diff --git a/pkgs/experimental/layout_engine_atoms/uv.lock b/pkgs/experimental/layout_engine_atoms/uv.lock index a92061789d..a64b7da468 100644 --- a/pkgs/experimental/layout_engine_atoms/uv.lock +++ b/pkgs/experimental/layout_engine_atoms/uv.lock @@ -2,6 +2,15 @@ version = 1 revision = 2 requires-python = ">=3.10, <3.13" +[[package]] +name = "annotated-doc" +version = "0.0.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d7/a6/dc46877b911e40c00d395771ea710d5e77b6de7bacd5fdcd78d70cc5a48f/annotated_doc-0.0.3.tar.gz", hash = "sha256:e18370014c70187422c33e945053ff4c286f453a984eba84d0dbfa0c935adeda", size = 5535, upload-time = "2025-10-24T14:57:10.718Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/02/b7/cf592cb5de5cb3bade3357f8d2cf42bf103bbe39f459824b4939fd212911/annotated_doc-0.0.3-py3-none-any.whl", hash = "sha256:348ec6664a76f1fd3be81f43dffbee4c7e8ce931ba71ec67cc7f4ade7fbbb580", size = 5488, upload-time = "2025-10-24T14:57:09.462Z" }, +] + [[package]] name = "annotated-types" version = "0.7.0" @@ -11,6 +20,42 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643, upload-time = "2024-05-20T21:33:24.1Z" }, ] +[[package]] +name = "anyio" +version = "4.11.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "exceptiongroup", marker = "python_full_version < '3.11'" }, + { name = "idna" }, + { name = "sniffio" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c6/78/7d432127c41b50bccba979505f272c16cbcadcc33645d5fa3a738110ae75/anyio-4.11.0.tar.gz", hash = "sha256:82a8d0b81e318cc5ce71a5f1f8b5c4e63619620b63141ef8c995fa0db95a57c4", size = 219094, upload-time = "2025-09-23T09:19:12.58Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/15/b3/9b1a8074496371342ec1e796a96f99c82c945a339cd81a8e73de28b4cf9e/anyio-4.11.0-py3-none-any.whl", hash = "sha256:0287e96f4d26d4149305414d4e3bc32f0dcd0862365a4bddea19d7a1ec38c4fc", size = 109097, upload-time = "2025-09-23T09:19:10.601Z" }, +] + +[[package]] +name = "backports-asyncio-runner" +version = "1.2.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/8e/ff/70dca7d7cb1cbc0edb2c6cc0c38b65cba36cccc491eca64cabd5fe7f8670/backports_asyncio_runner-1.2.0.tar.gz", hash = "sha256:a5aa7b2b7d8f8bfcaa2b57313f70792df84e32a2a746f585213373f900b42162", size = 69893, upload-time = "2025-07-02T02:27:15.685Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a0/59/76ab57e3fe74484f48a53f8e337171b4a2349e506eabe136d7e01d059086/backports_asyncio_runner-1.2.0-py3-none-any.whl", hash = "sha256:0da0a936a8aeb554eccb426dc55af3ba63bcdc69fa1a600b5bb305413a4477b5", size = 12313, upload-time = "2025-07-02T02:27:14.263Z" }, +] + +[[package]] +name = "click" +version = "8.3.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/46/61/de6cd827efad202d7057d93e0fed9294b96952e188f7384832791c7b2254/click-8.3.0.tar.gz", hash = "sha256:e7b8232224eba16f4ebe410c25ced9f7875cb5f3263ffc93cc3e8da705e229c4", size = 276943, upload-time = "2025-09-18T17:32:23.696Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/db/d3/9dcc0f5797f070ec8edf30fbadfb200e71d9db6b84d211e3b2085a7589a0/click-8.3.0-py3-none-any.whl", hash = "sha256:9b9f285302c6e3064f4330c05f05b81945b2a39544279343e6e7c5f27a9baddc", size = 107295, upload-time = "2025-09-18T17:32:22.42Z" }, +] + [[package]] name = "colorama" version = "0.4.6" @@ -32,6 +77,68 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/36/f4/c6e662dade71f56cd2f3735141b265c3c79293c109549c1e6933b0651ffc/exceptiongroup-1.3.0-py3-none-any.whl", hash = "sha256:4d111e6e0c13d0644cad6ddaa7ed0261a0b36971f6d23e7ec9b4b9097da78a10", size = 16674, upload-time = "2025-05-10T17:42:49.33Z" }, ] +[[package]] +name = "fastapi" +version = "0.120.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "annotated-doc" }, + { name = "pydantic" }, + { name = "starlette" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/40/cc/28aff6e246ee85bd571b26e4a793b84d42700e3bdc3008c3d747eda7b06d/fastapi-0.120.1.tar.gz", hash = "sha256:b5c6217e9ddca6dfcf54c97986180d4a1955e10c693d74943fc5327700178bff", size = 337616, upload-time = "2025-10-27T17:53:42.954Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7e/bb/1a74dbe87e9a595bf63052c886dfef965dc5b91d149456a8301eb3d41ce2/fastapi-0.120.1-py3-none-any.whl", hash = "sha256:0e8a2c328e96c117272d8c794d3a97d205f753cc2e69dd7ee387b7488a75601f", size = 108254, upload-time = "2025-10-27T17:53:40.076Z" }, +] + +[[package]] +name = "h11" +version = "0.16.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/01/ee/02a2c011bdab74c6fb3c75474d40b3052059d95df7e73351460c8588d963/h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1", size = 101250, upload-time = "2025-04-24T03:35:25.427Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515, upload-time = "2025-04-24T03:35:24.344Z" }, +] + +[[package]] +name = "httptools" +version = "0.7.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b5/46/120a669232c7bdedb9d52d4aeae7e6c7dfe151e99dc70802e2fc7a5e1993/httptools-0.7.1.tar.gz", hash = "sha256:abd72556974f8e7c74a259655924a717a2365b236c882c3f6f8a45fe94703ac9", size = 258961, upload-time = "2025-10-10T03:55:08.559Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c7/e5/c07e0bcf4ec8db8164e9f6738c048b2e66aabf30e7506f440c4cc6953f60/httptools-0.7.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:11d01b0ff1fe02c4c32d60af61a4d613b74fad069e47e06e9067758c01e9ac78", size = 204531, upload-time = "2025-10-10T03:54:20.887Z" }, + { url = "https://files.pythonhosted.org/packages/7e/4f/35e3a63f863a659f92ffd92bef131f3e81cf849af26e6435b49bd9f6f751/httptools-0.7.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:84d86c1e5afdc479a6fdabf570be0d3eb791df0ae727e8dbc0259ed1249998d4", size = 109408, upload-time = "2025-10-10T03:54:22.455Z" }, + { url = "https://files.pythonhosted.org/packages/f5/71/b0a9193641d9e2471ac541d3b1b869538a5fb6419d52fd2669fa9c79e4b8/httptools-0.7.1-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:c8c751014e13d88d2be5f5f14fc8b89612fcfa92a9cc480f2bc1598357a23a05", size = 440889, upload-time = "2025-10-10T03:54:23.753Z" }, + { url = "https://files.pythonhosted.org/packages/eb/d9/2e34811397b76718750fea44658cb0205b84566e895192115252e008b152/httptools-0.7.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:654968cb6b6c77e37b832a9be3d3ecabb243bbe7a0b8f65fbc5b6b04c8fcabed", size = 440460, upload-time = "2025-10-10T03:54:25.313Z" }, + { url = "https://files.pythonhosted.org/packages/01/3f/a04626ebeacc489866bb4d82362c0657b2262bef381d68310134be7f40bb/httptools-0.7.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:b580968316348b474b020edf3988eecd5d6eec4634ee6561e72ae3a2a0e00a8a", size = 425267, upload-time = "2025-10-10T03:54:26.81Z" }, + { url = "https://files.pythonhosted.org/packages/a5/99/adcd4f66614db627b587627c8ad6f4c55f18881549bab10ecf180562e7b9/httptools-0.7.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:d496e2f5245319da9d764296e86c5bb6fcf0cf7a8806d3d000717a889c8c0b7b", size = 424429, upload-time = "2025-10-10T03:54:28.174Z" }, + { url = "https://files.pythonhosted.org/packages/d5/72/ec8fc904a8fd30ba022dfa85f3bbc64c3c7cd75b669e24242c0658e22f3c/httptools-0.7.1-cp310-cp310-win_amd64.whl", hash = "sha256:cbf8317bfccf0fed3b5680c559d3459cccf1abe9039bfa159e62e391c7270568", size = 86173, upload-time = "2025-10-10T03:54:29.5Z" }, + { url = "https://files.pythonhosted.org/packages/9c/08/17e07e8d89ab8f343c134616d72eebfe03798835058e2ab579dcc8353c06/httptools-0.7.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:474d3b7ab469fefcca3697a10d11a32ee2b9573250206ba1e50d5980910da657", size = 206521, upload-time = "2025-10-10T03:54:31.002Z" }, + { url = "https://files.pythonhosted.org/packages/aa/06/c9c1b41ff52f16aee526fd10fbda99fa4787938aa776858ddc4a1ea825ec/httptools-0.7.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a3c3b7366bb6c7b96bd72d0dbe7f7d5eead261361f013be5f6d9590465ea1c70", size = 110375, upload-time = "2025-10-10T03:54:31.941Z" }, + { url = "https://files.pythonhosted.org/packages/cc/cc/10935db22fda0ee34c76f047590ca0a8bd9de531406a3ccb10a90e12ea21/httptools-0.7.1-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:379b479408b8747f47f3b253326183d7c009a3936518cdb70db58cffd369d9df", size = 456621, upload-time = "2025-10-10T03:54:33.176Z" }, + { url = "https://files.pythonhosted.org/packages/0e/84/875382b10d271b0c11aa5d414b44f92f8dd53e9b658aec338a79164fa548/httptools-0.7.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cad6b591a682dcc6cf1397c3900527f9affef1e55a06c4547264796bbd17cf5e", size = 454954, upload-time = "2025-10-10T03:54:34.226Z" }, + { url = "https://files.pythonhosted.org/packages/30/e1/44f89b280f7e46c0b1b2ccee5737d46b3bb13136383958f20b580a821ca0/httptools-0.7.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:eb844698d11433d2139bbeeb56499102143beb582bd6c194e3ba69c22f25c274", size = 440175, upload-time = "2025-10-10T03:54:35.942Z" }, + { url = "https://files.pythonhosted.org/packages/6f/7e/b9287763159e700e335028bc1824359dc736fa9b829dacedace91a39b37e/httptools-0.7.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f65744d7a8bdb4bda5e1fa23e4ba16832860606fcc09d674d56e425e991539ec", size = 440310, upload-time = "2025-10-10T03:54:37.1Z" }, + { url = "https://files.pythonhosted.org/packages/b3/07/5b614f592868e07f5c94b1f301b5e14a21df4e8076215a3bccb830a687d8/httptools-0.7.1-cp311-cp311-win_amd64.whl", hash = "sha256:135fbe974b3718eada677229312e97f3b31f8a9c8ffa3ae6f565bf808d5b6bcb", size = 86875, upload-time = "2025-10-10T03:54:38.421Z" }, + { url = "https://files.pythonhosted.org/packages/53/7f/403e5d787dc4942316e515e949b0c8a013d84078a915910e9f391ba9b3ed/httptools-0.7.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:38e0c83a2ea9746ebbd643bdfb521b9aa4a91703e2cd705c20443405d2fd16a5", size = 206280, upload-time = "2025-10-10T03:54:39.274Z" }, + { url = "https://files.pythonhosted.org/packages/2a/0d/7f3fd28e2ce311ccc998c388dd1c53b18120fda3b70ebb022b135dc9839b/httptools-0.7.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f25bbaf1235e27704f1a7b86cd3304eabc04f569c828101d94a0e605ef7205a5", size = 110004, upload-time = "2025-10-10T03:54:40.403Z" }, + { url = "https://files.pythonhosted.org/packages/84/a6/b3965e1e146ef5762870bbe76117876ceba51a201e18cc31f5703e454596/httptools-0.7.1-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:2c15f37ef679ab9ecc06bfc4e6e8628c32a8e4b305459de7cf6785acd57e4d03", size = 517655, upload-time = "2025-10-10T03:54:41.347Z" }, + { url = "https://files.pythonhosted.org/packages/11/7d/71fee6f1844e6fa378f2eddde6c3e41ce3a1fb4b2d81118dd544e3441ec0/httptools-0.7.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7fe6e96090df46b36ccfaf746f03034e5ab723162bc51b0a4cf58305324036f2", size = 511440, upload-time = "2025-10-10T03:54:42.452Z" }, + { url = "https://files.pythonhosted.org/packages/22/a5/079d216712a4f3ffa24af4a0381b108aa9c45b7a5cc6eb141f81726b1823/httptools-0.7.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:f72fdbae2dbc6e68b8239defb48e6a5937b12218e6ffc2c7846cc37befa84362", size = 495186, upload-time = "2025-10-10T03:54:43.937Z" }, + { url = "https://files.pythonhosted.org/packages/e9/9e/025ad7b65278745dee3bd0ebf9314934c4592560878308a6121f7f812084/httptools-0.7.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e99c7b90a29fd82fea9ef57943d501a16f3404d7b9ee81799d41639bdaae412c", size = 499192, upload-time = "2025-10-10T03:54:45.003Z" }, + { url = "https://files.pythonhosted.org/packages/6d/de/40a8f202b987d43afc4d54689600ff03ce65680ede2f31df348d7f368b8f/httptools-0.7.1-cp312-cp312-win_amd64.whl", hash = "sha256:3e14f530fefa7499334a79b0cf7e7cd2992870eb893526fb097d51b4f2d0f321", size = 86694, upload-time = "2025-10-10T03:54:45.923Z" }, +] + +[[package]] +name = "idna" +version = "3.11" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6f/6d/0703ccc57f3a7233505399edb88de3cbd678da106337b9fcde432b65ed60/idna-3.11.tar.gz", hash = "sha256:795dafcc9c04ed0c1fb032c2aa73654d8e8c5023a7df64a53f39190ada629902", size = 194582, upload-time = "2025-10-12T14:55:20.501Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl", hash = "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea", size = 71008, upload-time = "2025-10-12T14:55:18.883Z" }, +] + [[package]] name = "iniconfig" version = "2.1.0" @@ -55,7 +162,7 @@ wheels = [ [[package]] name = "layout-engine" -version = "0.1.1" +version = "0.1.1.dev1" source = { directory = "../layout_engine" } dependencies = [ { name = "jinja2" }, @@ -84,24 +191,32 @@ dev = [ [[package]] name = "layout-engine-atoms" -version = "0.1.0" +version = "0.1.0.dev1" source = { editable = "." } dependencies = [ + { name = "fastapi" }, { name = "layout-engine" }, + { name = "uvicorn", extra = ["standard"] }, ] [package.dev-dependencies] dev = [ { name = "pytest" }, + { name = "pytest-asyncio" }, { name = "ruff" }, ] [package.metadata] -requires-dist = [{ name = "layout-engine", directory = "../layout_engine" }] +requires-dist = [ + { name = "fastapi" }, + { name = "layout-engine", directory = "../layout_engine" }, + { name = "uvicorn", extras = ["standard"] }, +] [package.metadata.requires-dev] dev = [ { name = "pytest", specifier = ">=8.2" }, + { name = "pytest-asyncio", specifier = ">=0.23" }, { name = "ruff", specifier = ">=0.6" }, ] @@ -282,6 +397,65 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/a8/a4/20da314d277121d6534b3a980b29035dcd51e6744bd79075a6ce8fa4eb8d/pytest-8.4.2-py3-none-any.whl", hash = "sha256:872f880de3fc3a5bdc88a11b39c9710c3497a547cfa9320bc3c5e62fbf272e79", size = 365750, upload-time = "2025-09-04T14:34:20.226Z" }, ] +[[package]] +name = "pytest-asyncio" +version = "1.2.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "backports-asyncio-runner", marker = "python_full_version < '3.11'" }, + { name = "pytest" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/42/86/9e3c5f48f7b7b638b216e4b9e645f54d199d7abbbab7a64a13b4e12ba10f/pytest_asyncio-1.2.0.tar.gz", hash = "sha256:c609a64a2a8768462d0c99811ddb8bd2583c33fd33cf7f21af1c142e824ffb57", size = 50119, upload-time = "2025-09-12T07:33:53.816Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/93/2fa34714b7a4ae72f2f8dad66ba17dd9a2c793220719e736dda28b7aec27/pytest_asyncio-1.2.0-py3-none-any.whl", hash = "sha256:8e17ae5e46d8e7efe51ab6494dd2010f4ca8dae51652aa3c8d55acf50bfb2e99", size = 15095, upload-time = "2025-09-12T07:33:52.639Z" }, +] + +[[package]] +name = "python-dotenv" +version = "1.2.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f0/26/19cadc79a718c5edbec86fd4919a6b6d3f681039a2f6d66d14be94e75fb9/python_dotenv-1.2.1.tar.gz", hash = "sha256:42667e897e16ab0d66954af0e60a9caa94f0fd4ecf3aaf6d2d260eec1aa36ad6", size = 44221, upload-time = "2025-10-26T15:12:10.434Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/14/1b/a298b06749107c305e1fe0f814c6c74aea7b2f1e10989cb30f544a1b3253/python_dotenv-1.2.1-py3-none-any.whl", hash = "sha256:b81ee9561e9ca4004139c6cbba3a238c32b03e4894671e181b671e8cb8425d61", size = 21230, upload-time = "2025-10-26T15:12:09.109Z" }, +] + +[[package]] +name = "pyyaml" +version = "6.0.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/05/8e/961c0007c59b8dd7729d542c61a4d537767a59645b82a0b521206e1e25c2/pyyaml-6.0.3.tar.gz", hash = "sha256:d76623373421df22fb4cf8817020cbb7ef15c725b9d5e45f17e189bfc384190f", size = 130960, upload-time = "2025-09-25T21:33:16.546Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f4/a0/39350dd17dd6d6c6507025c0e53aef67a9293a6d37d3511f23ea510d5800/pyyaml-6.0.3-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:214ed4befebe12df36bcc8bc2b64b396ca31be9304b8f59e25c11cf94a4c033b", size = 184227, upload-time = "2025-09-25T21:31:46.04Z" }, + { url = "https://files.pythonhosted.org/packages/05/14/52d505b5c59ce73244f59c7a50ecf47093ce4765f116cdb98286a71eeca2/pyyaml-6.0.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:02ea2dfa234451bbb8772601d7b8e426c2bfa197136796224e50e35a78777956", size = 174019, upload-time = "2025-09-25T21:31:47.706Z" }, + { url = "https://files.pythonhosted.org/packages/43/f7/0e6a5ae5599c838c696adb4e6330a59f463265bfa1e116cfd1fbb0abaaae/pyyaml-6.0.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b30236e45cf30d2b8e7b3e85881719e98507abed1011bf463a8fa23e9c3e98a8", size = 740646, upload-time = "2025-09-25T21:31:49.21Z" }, + { url = "https://files.pythonhosted.org/packages/2f/3a/61b9db1d28f00f8fd0ae760459a5c4bf1b941baf714e207b6eb0657d2578/pyyaml-6.0.3-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:66291b10affd76d76f54fad28e22e51719ef9ba22b29e1d7d03d6777a9174198", size = 840793, upload-time = "2025-09-25T21:31:50.735Z" }, + { url = "https://files.pythonhosted.org/packages/7a/1e/7acc4f0e74c4b3d9531e24739e0ab832a5edf40e64fbae1a9c01941cabd7/pyyaml-6.0.3-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9c7708761fccb9397fe64bbc0395abcae8c4bf7b0eac081e12b809bf47700d0b", size = 770293, upload-time = "2025-09-25T21:31:51.828Z" }, + { url = "https://files.pythonhosted.org/packages/8b/ef/abd085f06853af0cd59fa5f913d61a8eab65d7639ff2a658d18a25d6a89d/pyyaml-6.0.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:418cf3f2111bc80e0933b2cd8cd04f286338bb88bdc7bc8e6dd775ebde60b5e0", size = 732872, upload-time = "2025-09-25T21:31:53.282Z" }, + { url = "https://files.pythonhosted.org/packages/1f/15/2bc9c8faf6450a8b3c9fc5448ed869c599c0a74ba2669772b1f3a0040180/pyyaml-6.0.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:5e0b74767e5f8c593e8c9b5912019159ed0533c70051e9cce3e8b6aa699fcd69", size = 758828, upload-time = "2025-09-25T21:31:54.807Z" }, + { url = "https://files.pythonhosted.org/packages/a3/00/531e92e88c00f4333ce359e50c19b8d1de9fe8d581b1534e35ccfbc5f393/pyyaml-6.0.3-cp310-cp310-win32.whl", hash = "sha256:28c8d926f98f432f88adc23edf2e6d4921ac26fb084b028c733d01868d19007e", size = 142415, upload-time = "2025-09-25T21:31:55.885Z" }, + { url = "https://files.pythonhosted.org/packages/2a/fa/926c003379b19fca39dd4634818b00dec6c62d87faf628d1394e137354d4/pyyaml-6.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:bdb2c67c6c1390b63c6ff89f210c8fd09d9a1217a465701eac7316313c915e4c", size = 158561, upload-time = "2025-09-25T21:31:57.406Z" }, + { url = "https://files.pythonhosted.org/packages/6d/16/a95b6757765b7b031c9374925bb718d55e0a9ba8a1b6a12d25962ea44347/pyyaml-6.0.3-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:44edc647873928551a01e7a563d7452ccdebee747728c1080d881d68af7b997e", size = 185826, upload-time = "2025-09-25T21:31:58.655Z" }, + { url = "https://files.pythonhosted.org/packages/16/19/13de8e4377ed53079ee996e1ab0a9c33ec2faf808a4647b7b4c0d46dd239/pyyaml-6.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:652cb6edd41e718550aad172851962662ff2681490a8a711af6a4d288dd96824", size = 175577, upload-time = "2025-09-25T21:32:00.088Z" }, + { url = "https://files.pythonhosted.org/packages/0c/62/d2eb46264d4b157dae1275b573017abec435397aa59cbcdab6fc978a8af4/pyyaml-6.0.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:10892704fc220243f5305762e276552a0395f7beb4dbf9b14ec8fd43b57f126c", size = 775556, upload-time = "2025-09-25T21:32:01.31Z" }, + { url = "https://files.pythonhosted.org/packages/10/cb/16c3f2cf3266edd25aaa00d6c4350381c8b012ed6f5276675b9eba8d9ff4/pyyaml-6.0.3-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:850774a7879607d3a6f50d36d04f00ee69e7fc816450e5f7e58d7f17f1ae5c00", size = 882114, upload-time = "2025-09-25T21:32:03.376Z" }, + { url = "https://files.pythonhosted.org/packages/71/60/917329f640924b18ff085ab889a11c763e0b573da888e8404ff486657602/pyyaml-6.0.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b8bb0864c5a28024fac8a632c443c87c5aa6f215c0b126c449ae1a150412f31d", size = 806638, upload-time = "2025-09-25T21:32:04.553Z" }, + { url = "https://files.pythonhosted.org/packages/dd/6f/529b0f316a9fd167281a6c3826b5583e6192dba792dd55e3203d3f8e655a/pyyaml-6.0.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1d37d57ad971609cf3c53ba6a7e365e40660e3be0e5175fa9f2365a379d6095a", size = 767463, upload-time = "2025-09-25T21:32:06.152Z" }, + { url = "https://files.pythonhosted.org/packages/f2/6a/b627b4e0c1dd03718543519ffb2f1deea4a1e6d42fbab8021936a4d22589/pyyaml-6.0.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:37503bfbfc9d2c40b344d06b2199cf0e96e97957ab1c1b546fd4f87e53e5d3e4", size = 794986, upload-time = "2025-09-25T21:32:07.367Z" }, + { url = "https://files.pythonhosted.org/packages/45/91/47a6e1c42d9ee337c4839208f30d9f09caa9f720ec7582917b264defc875/pyyaml-6.0.3-cp311-cp311-win32.whl", hash = "sha256:8098f252adfa6c80ab48096053f512f2321f0b998f98150cea9bd23d83e1467b", size = 142543, upload-time = "2025-09-25T21:32:08.95Z" }, + { url = "https://files.pythonhosted.org/packages/da/e3/ea007450a105ae919a72393cb06f122f288ef60bba2dc64b26e2646fa315/pyyaml-6.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:9f3bfb4965eb874431221a3ff3fdcddc7e74e3b07799e0e84ca4a0f867d449bf", size = 158763, upload-time = "2025-09-25T21:32:09.96Z" }, + { url = "https://files.pythonhosted.org/packages/d1/33/422b98d2195232ca1826284a76852ad5a86fe23e31b009c9886b2d0fb8b2/pyyaml-6.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7f047e29dcae44602496db43be01ad42fc6f1cc0d8cd6c83d342306c32270196", size = 182063, upload-time = "2025-09-25T21:32:11.445Z" }, + { url = "https://files.pythonhosted.org/packages/89/a0/6cf41a19a1f2f3feab0e9c0b74134aa2ce6849093d5517a0c550fe37a648/pyyaml-6.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fc09d0aa354569bc501d4e787133afc08552722d3ab34836a80547331bb5d4a0", size = 173973, upload-time = "2025-09-25T21:32:12.492Z" }, + { url = "https://files.pythonhosted.org/packages/ed/23/7a778b6bd0b9a8039df8b1b1d80e2e2ad78aa04171592c8a5c43a56a6af4/pyyaml-6.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9149cad251584d5fb4981be1ecde53a1ca46c891a79788c0df828d2f166bda28", size = 775116, upload-time = "2025-09-25T21:32:13.652Z" }, + { url = "https://files.pythonhosted.org/packages/65/30/d7353c338e12baef4ecc1b09e877c1970bd3382789c159b4f89d6a70dc09/pyyaml-6.0.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5fdec68f91a0c6739b380c83b951e2c72ac0197ace422360e6d5a959d8d97b2c", size = 844011, upload-time = "2025-09-25T21:32:15.21Z" }, + { url = "https://files.pythonhosted.org/packages/8b/9d/b3589d3877982d4f2329302ef98a8026e7f4443c765c46cfecc8858c6b4b/pyyaml-6.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ba1cc08a7ccde2d2ec775841541641e4548226580ab850948cbfda66a1befcdc", size = 807870, upload-time = "2025-09-25T21:32:16.431Z" }, + { url = "https://files.pythonhosted.org/packages/05/c0/b3be26a015601b822b97d9149ff8cb5ead58c66f981e04fedf4e762f4bd4/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8dc52c23056b9ddd46818a57b78404882310fb473d63f17b07d5c40421e47f8e", size = 761089, upload-time = "2025-09-25T21:32:17.56Z" }, + { url = "https://files.pythonhosted.org/packages/be/8e/98435a21d1d4b46590d5459a22d88128103f8da4c2d4cb8f14f2a96504e1/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:41715c910c881bc081f1e8872880d3c650acf13dfa8214bad49ed4cede7c34ea", size = 790181, upload-time = "2025-09-25T21:32:18.834Z" }, + { url = "https://files.pythonhosted.org/packages/74/93/7baea19427dcfbe1e5a372d81473250b379f04b1bd3c4c5ff825e2327202/pyyaml-6.0.3-cp312-cp312-win32.whl", hash = "sha256:96b533f0e99f6579b3d4d4995707cf36df9100d67e0c8303a0c55b27b5f99bc5", size = 137658, upload-time = "2025-09-25T21:32:20.209Z" }, + { url = "https://files.pythonhosted.org/packages/86/bf/899e81e4cce32febab4fb42bb97dcdf66bc135272882d1987881a4b519e9/pyyaml-6.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:5fcd34e47f6e0b794d17de1b4ff496c00986e1c83f7ab2fb8fcfe9616ff7477b", size = 154003, upload-time = "2025-09-25T21:32:21.167Z" }, + { url = "https://files.pythonhosted.org/packages/1a/08/67bd04656199bbb51dbed1439b7f27601dfb576fb864099c7ef0c3e55531/pyyaml-6.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:64386e5e707d03a7e172c0701abfb7e10f0fb753ee1d773128192742712a98fd", size = 140344, upload-time = "2025-09-25T21:32:22.617Z" }, +] + [[package]] name = "ruff" version = "0.14.0" @@ -308,6 +482,28 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/c6/2a/65880dfd0e13f7f13a775998f34703674a4554906167dce02daf7865b954/ruff-0.14.0-py3-none-win_arm64.whl", hash = "sha256:f42c9495f5c13ff841b1da4cb3c2a42075409592825dada7c5885c2c844ac730", size = 12565142, upload-time = "2025-10-07T18:21:53.577Z" }, ] +[[package]] +name = "sniffio" +version = "1.3.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a2/87/a6771e1546d97e7e041b6ae58d80074f81b7d5121207425c964ddf5cfdbd/sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc", size = 20372, upload-time = "2024-02-25T23:20:04.057Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235, upload-time = "2024-02-25T23:20:01.196Z" }, +] + +[[package]] +name = "starlette" +version = "0.48.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a7/a5/d6f429d43394057b67a6b5bbe6eae2f77a6bf7459d961fdb224bf206eee6/starlette-0.48.0.tar.gz", hash = "sha256:7e8cee469a8ab2352911528110ce9088fdc6a37d9876926e73da7ce4aa4c7a46", size = 2652949, upload-time = "2025-09-13T08:41:05.699Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/be/72/2db2f49247d0a18b4f1bb9a5a39a0162869acf235f3a96418363947b3d46/starlette-0.48.0-py3-none-any.whl", hash = "sha256:0764ca97b097582558ecb498132ed0c7d942f233f365b86ba37770e026510659", size = 73736, upload-time = "2025-09-13T08:41:03.869Z" }, +] + [[package]] name = "tomli" version = "2.3.0" @@ -353,3 +549,159 @@ sdist = { url = "https://files.pythonhosted.org/packages/55/e3/70399cb7dd41c10ac wheels = [ { url = "https://files.pythonhosted.org/packages/dc/9b/47798a6c91d8bdb567fe2698fe81e0c6b7cb7ef4d13da4114b41d239f65d/typing_inspection-0.4.2-py3-none-any.whl", hash = "sha256:4ed1cacbdc298c220f1bd249ed5287caa16f34d44ef4e9c3d0cbad5b521545e7", size = 14611, upload-time = "2025-10-01T02:14:40.154Z" }, ] + +[[package]] +name = "uvicorn" +version = "0.38.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, + { name = "h11" }, + { name = "typing-extensions", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/cb/ce/f06b84e2697fef4688ca63bdb2fdf113ca0a3be33f94488f2cadb690b0cf/uvicorn-0.38.0.tar.gz", hash = "sha256:fd97093bdd120a2609fc0d3afe931d4d4ad688b6e75f0f929fde1bc36fe0e91d", size = 80605, upload-time = "2025-10-18T13:46:44.63Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ee/d9/d88e73ca598f4f6ff671fb5fde8a32925c2e08a637303a1d12883c7305fa/uvicorn-0.38.0-py3-none-any.whl", hash = "sha256:48c0afd214ceb59340075b4a052ea1ee91c16fbc2a9b1469cca0e54566977b02", size = 68109, upload-time = "2025-10-18T13:46:42.958Z" }, +] + +[package.optional-dependencies] +standard = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "httptools" }, + { name = "python-dotenv" }, + { name = "pyyaml" }, + { name = "uvloop", marker = "platform_python_implementation != 'PyPy' and sys_platform != 'cygwin' and sys_platform != 'win32'" }, + { name = "watchfiles" }, + { name = "websockets" }, +] + +[[package]] +name = "uvloop" +version = "0.22.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/06/f0/18d39dbd1971d6d62c4629cc7fa67f74821b0dc1f5a77af43719de7936a7/uvloop-0.22.1.tar.gz", hash = "sha256:6c84bae345b9147082b17371e3dd5d42775bddce91f885499017f4607fdaf39f", size = 2443250, upload-time = "2025-10-16T22:17:19.342Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/eb/14/ecceb239b65adaaf7fde510aa8bd534075695d1e5f8dadfa32b5723d9cfb/uvloop-0.22.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:ef6f0d4cc8a9fa1f6a910230cd53545d9a14479311e87e3cb225495952eb672c", size = 1343335, upload-time = "2025-10-16T22:16:11.43Z" }, + { url = "https://files.pythonhosted.org/packages/ba/ae/6f6f9af7f590b319c94532b9567409ba11f4fa71af1148cab1bf48a07048/uvloop-0.22.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:7cd375a12b71d33d46af85a3343b35d98e8116134ba404bd657b3b1d15988792", size = 742903, upload-time = "2025-10-16T22:16:12.979Z" }, + { url = "https://files.pythonhosted.org/packages/09/bd/3667151ad0702282a1f4d5d29288fce8a13c8b6858bf0978c219cd52b231/uvloop-0.22.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ac33ed96229b7790eb729702751c0e93ac5bc3bcf52ae9eccbff30da09194b86", size = 3648499, upload-time = "2025-10-16T22:16:14.451Z" }, + { url = "https://files.pythonhosted.org/packages/b3/f6/21657bb3beb5f8c57ce8be3b83f653dd7933c2fd00545ed1b092d464799a/uvloop-0.22.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:481c990a7abe2c6f4fc3d98781cc9426ebd7f03a9aaa7eb03d3bfc68ac2a46bd", size = 3700133, upload-time = "2025-10-16T22:16:16.272Z" }, + { url = "https://files.pythonhosted.org/packages/09/e0/604f61d004ded805f24974c87ddd8374ef675644f476f01f1df90e4cdf72/uvloop-0.22.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:a592b043a47ad17911add5fbd087c76716d7c9ccc1d64ec9249ceafd735f03c2", size = 3512681, upload-time = "2025-10-16T22:16:18.07Z" }, + { url = "https://files.pythonhosted.org/packages/bb/ce/8491fd370b0230deb5eac69c7aae35b3be527e25a911c0acdffb922dc1cd/uvloop-0.22.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:1489cf791aa7b6e8c8be1c5a080bae3a672791fcb4e9e12249b05862a2ca9cec", size = 3615261, upload-time = "2025-10-16T22:16:19.596Z" }, + { url = "https://files.pythonhosted.org/packages/c7/d5/69900f7883235562f1f50d8184bb7dd84a2fb61e9ec63f3782546fdbd057/uvloop-0.22.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:c60ebcd36f7b240b30788554b6f0782454826a0ed765d8430652621b5de674b9", size = 1352420, upload-time = "2025-10-16T22:16:21.187Z" }, + { url = "https://files.pythonhosted.org/packages/a8/73/c4e271b3bce59724e291465cc936c37758886a4868787da0278b3b56b905/uvloop-0.22.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3b7f102bf3cb1995cfeaee9321105e8f5da76fdb104cdad8986f85461a1b7b77", size = 748677, upload-time = "2025-10-16T22:16:22.558Z" }, + { url = "https://files.pythonhosted.org/packages/86/94/9fb7fad2f824d25f8ecac0d70b94d0d48107ad5ece03769a9c543444f78a/uvloop-0.22.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:53c85520781d84a4b8b230e24a5af5b0778efdb39142b424990ff1ef7c48ba21", size = 3753819, upload-time = "2025-10-16T22:16:23.903Z" }, + { url = "https://files.pythonhosted.org/packages/74/4f/256aca690709e9b008b7108bc85fba619a2bc37c6d80743d18abad16ee09/uvloop-0.22.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:56a2d1fae65fd82197cb8c53c367310b3eabe1bbb9fb5a04d28e3e3520e4f702", size = 3804529, upload-time = "2025-10-16T22:16:25.246Z" }, + { url = "https://files.pythonhosted.org/packages/7f/74/03c05ae4737e871923d21a76fe28b6aad57f5c03b6e6bfcfa5ad616013e4/uvloop-0.22.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:40631b049d5972c6755b06d0bfe8233b1bd9a8a6392d9d1c45c10b6f9e9b2733", size = 3621267, upload-time = "2025-10-16T22:16:26.819Z" }, + { url = "https://files.pythonhosted.org/packages/75/be/f8e590fe61d18b4a92070905497aec4c0e64ae1761498cad09023f3f4b3e/uvloop-0.22.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:535cc37b3a04f6cd2c1ef65fa1d370c9a35b6695df735fcff5427323f2cd5473", size = 3723105, upload-time = "2025-10-16T22:16:28.252Z" }, + { url = "https://files.pythonhosted.org/packages/3d/ff/7f72e8170be527b4977b033239a83a68d5c881cc4775fca255c677f7ac5d/uvloop-0.22.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:fe94b4564e865d968414598eea1a6de60adba0c040ba4ed05ac1300de402cd42", size = 1359936, upload-time = "2025-10-16T22:16:29.436Z" }, + { url = "https://files.pythonhosted.org/packages/c3/c6/e5d433f88fd54d81ef4be58b2b7b0cea13c442454a1db703a1eea0db1a59/uvloop-0.22.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:51eb9bd88391483410daad430813d982010f9c9c89512321f5b60e2cddbdddd6", size = 752769, upload-time = "2025-10-16T22:16:30.493Z" }, + { url = "https://files.pythonhosted.org/packages/24/68/a6ac446820273e71aa762fa21cdcc09861edd3536ff47c5cd3b7afb10eeb/uvloop-0.22.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:700e674a166ca5778255e0e1dc4e9d79ab2acc57b9171b79e65feba7184b3370", size = 4317413, upload-time = "2025-10-16T22:16:31.644Z" }, + { url = "https://files.pythonhosted.org/packages/5f/6f/e62b4dfc7ad6518e7eff2516f680d02a0f6eb62c0c212e152ca708a0085e/uvloop-0.22.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7b5b1ac819a3f946d3b2ee07f09149578ae76066d70b44df3fa990add49a82e4", size = 4426307, upload-time = "2025-10-16T22:16:32.917Z" }, + { url = "https://files.pythonhosted.org/packages/90/60/97362554ac21e20e81bcef1150cb2a7e4ffdaf8ea1e5b2e8bf7a053caa18/uvloop-0.22.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e047cc068570bac9866237739607d1313b9253c3051ad84738cbb095be0537b2", size = 4131970, upload-time = "2025-10-16T22:16:34.015Z" }, + { url = "https://files.pythonhosted.org/packages/99/39/6b3f7d234ba3964c428a6e40006340f53ba37993f46ed6e111c6e9141d18/uvloop-0.22.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:512fec6815e2dd45161054592441ef76c830eddaad55c8aa30952e6fe1ed07c0", size = 4296343, upload-time = "2025-10-16T22:16:35.149Z" }, +] + +[[package]] +name = "watchfiles" +version = "1.1.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c2/c9/8869df9b2a2d6c59d79220a4db37679e74f807c559ffe5265e08b227a210/watchfiles-1.1.1.tar.gz", hash = "sha256:a173cb5c16c4f40ab19cecf48a534c409f7ea983ab8fed0741304a1c0a31b3f2", size = 94440, upload-time = "2025-10-14T15:06:21.08Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a7/1a/206e8cf2dd86fddf939165a57b4df61607a1e0add2785f170a3f616b7d9f/watchfiles-1.1.1-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:eef58232d32daf2ac67f42dea51a2c80f0d03379075d44a587051e63cc2e368c", size = 407318, upload-time = "2025-10-14T15:04:18.753Z" }, + { url = "https://files.pythonhosted.org/packages/b3/0f/abaf5262b9c496b5dad4ed3c0e799cbecb1f8ea512ecb6ddd46646a9fca3/watchfiles-1.1.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:03fa0f5237118a0c5e496185cafa92878568b652a2e9a9382a5151b1a0380a43", size = 394478, upload-time = "2025-10-14T15:04:20.297Z" }, + { url = "https://files.pythonhosted.org/packages/b1/04/9cc0ba88697b34b755371f5ace8d3a4d9a15719c07bdc7bd13d7d8c6a341/watchfiles-1.1.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8ca65483439f9c791897f7db49202301deb6e15fe9f8fe2fed555bf986d10c31", size = 449894, upload-time = "2025-10-14T15:04:21.527Z" }, + { url = "https://files.pythonhosted.org/packages/d2/9c/eda4615863cd8621e89aed4df680d8c3ec3da6a4cf1da113c17decd87c7f/watchfiles-1.1.1-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f0ab1c1af0cb38e3f598244c17919fb1a84d1629cc08355b0074b6d7f53138ac", size = 459065, upload-time = "2025-10-14T15:04:22.795Z" }, + { url = "https://files.pythonhosted.org/packages/84/13/f28b3f340157d03cbc8197629bc109d1098764abe1e60874622a0be5c112/watchfiles-1.1.1-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3bc570d6c01c206c46deb6e935a260be44f186a2f05179f52f7fcd2be086a94d", size = 488377, upload-time = "2025-10-14T15:04:24.138Z" }, + { url = "https://files.pythonhosted.org/packages/86/93/cfa597fa9389e122488f7ffdbd6db505b3b915ca7435ecd7542e855898c2/watchfiles-1.1.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e84087b432b6ac94778de547e08611266f1f8ffad28c0ee4c82e028b0fc5966d", size = 595837, upload-time = "2025-10-14T15:04:25.057Z" }, + { url = "https://files.pythonhosted.org/packages/57/1e/68c1ed5652b48d89fc24d6af905d88ee4f82fa8bc491e2666004e307ded1/watchfiles-1.1.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:620bae625f4cb18427b1bb1a2d9426dc0dd5a5ba74c7c2cdb9de405f7b129863", size = 473456, upload-time = "2025-10-14T15:04:26.497Z" }, + { url = "https://files.pythonhosted.org/packages/d5/dc/1a680b7458ffa3b14bb64878112aefc8f2e4f73c5af763cbf0bd43100658/watchfiles-1.1.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:544364b2b51a9b0c7000a4b4b02f90e9423d97fbbf7e06689236443ebcad81ab", size = 455614, upload-time = "2025-10-14T15:04:27.539Z" }, + { url = "https://files.pythonhosted.org/packages/61/a5/3d782a666512e01eaa6541a72ebac1d3aae191ff4a31274a66b8dd85760c/watchfiles-1.1.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:bbe1ef33d45bc71cf21364df962af171f96ecaeca06bd9e3d0b583efb12aec82", size = 630690, upload-time = "2025-10-14T15:04:28.495Z" }, + { url = "https://files.pythonhosted.org/packages/9b/73/bb5f38590e34687b2a9c47a244aa4dd50c56a825969c92c9c5fc7387cea1/watchfiles-1.1.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:1a0bb430adb19ef49389e1ad368450193a90038b5b752f4ac089ec6942c4dff4", size = 622459, upload-time = "2025-10-14T15:04:29.491Z" }, + { url = "https://files.pythonhosted.org/packages/f1/ac/c9bb0ec696e07a20bd58af5399aeadaef195fb2c73d26baf55180fe4a942/watchfiles-1.1.1-cp310-cp310-win32.whl", hash = "sha256:3f6d37644155fb5beca5378feb8c1708d5783145f2a0f1c4d5a061a210254844", size = 272663, upload-time = "2025-10-14T15:04:30.435Z" }, + { url = "https://files.pythonhosted.org/packages/11/a0/a60c5a7c2ec59fa062d9a9c61d02e3b6abd94d32aac2d8344c4bdd033326/watchfiles-1.1.1-cp310-cp310-win_amd64.whl", hash = "sha256:a36d8efe0f290835fd0f33da35042a1bb5dc0e83cbc092dcf69bce442579e88e", size = 287453, upload-time = "2025-10-14T15:04:31.53Z" }, + { url = "https://files.pythonhosted.org/packages/1f/f8/2c5f479fb531ce2f0564eda479faecf253d886b1ab3630a39b7bf7362d46/watchfiles-1.1.1-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:f57b396167a2565a4e8b5e56a5a1c537571733992b226f4f1197d79e94cf0ae5", size = 406529, upload-time = "2025-10-14T15:04:32.899Z" }, + { url = "https://files.pythonhosted.org/packages/fe/cd/f515660b1f32f65df671ddf6f85bfaca621aee177712874dc30a97397977/watchfiles-1.1.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:421e29339983e1bebc281fab40d812742268ad057db4aee8c4d2bce0af43b741", size = 394384, upload-time = "2025-10-14T15:04:33.761Z" }, + { url = "https://files.pythonhosted.org/packages/7b/c3/28b7dc99733eab43fca2d10f55c86e03bd6ab11ca31b802abac26b23d161/watchfiles-1.1.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6e43d39a741e972bab5d8100b5cdacf69db64e34eb19b6e9af162bccf63c5cc6", size = 448789, upload-time = "2025-10-14T15:04:34.679Z" }, + { url = "https://files.pythonhosted.org/packages/4a/24/33e71113b320030011c8e4316ccca04194bf0cbbaeee207f00cbc7d6b9f5/watchfiles-1.1.1-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f537afb3276d12814082a2e9b242bdcf416c2e8fd9f799a737990a1dbe906e5b", size = 460521, upload-time = "2025-10-14T15:04:35.963Z" }, + { url = "https://files.pythonhosted.org/packages/f4/c3/3c9a55f255aa57b91579ae9e98c88704955fa9dac3e5614fb378291155df/watchfiles-1.1.1-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b2cd9e04277e756a2e2d2543d65d1e2166d6fd4c9b183f8808634fda23f17b14", size = 488722, upload-time = "2025-10-14T15:04:37.091Z" }, + { url = "https://files.pythonhosted.org/packages/49/36/506447b73eb46c120169dc1717fe2eff07c234bb3232a7200b5f5bd816e9/watchfiles-1.1.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5f3f58818dc0b07f7d9aa7fe9eb1037aecb9700e63e1f6acfed13e9fef648f5d", size = 596088, upload-time = "2025-10-14T15:04:38.39Z" }, + { url = "https://files.pythonhosted.org/packages/82/ab/5f39e752a9838ec4d52e9b87c1e80f1ee3ccdbe92e183c15b6577ab9de16/watchfiles-1.1.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9bb9f66367023ae783551042d31b1d7fd422e8289eedd91f26754a66f44d5cff", size = 472923, upload-time = "2025-10-14T15:04:39.666Z" }, + { url = "https://files.pythonhosted.org/packages/af/b9/a419292f05e302dea372fa7e6fda5178a92998411f8581b9830d28fb9edb/watchfiles-1.1.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aebfd0861a83e6c3d1110b78ad54704486555246e542be3e2bb94195eabb2606", size = 456080, upload-time = "2025-10-14T15:04:40.643Z" }, + { url = "https://files.pythonhosted.org/packages/b0/c3/d5932fd62bde1a30c36e10c409dc5d54506726f08cb3e1d8d0ba5e2bc8db/watchfiles-1.1.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:5fac835b4ab3c6487b5dbad78c4b3724e26bcc468e886f8ba8cc4306f68f6701", size = 629432, upload-time = "2025-10-14T15:04:41.789Z" }, + { url = "https://files.pythonhosted.org/packages/f7/77/16bddd9779fafb795f1a94319dc965209c5641db5bf1edbbccace6d1b3c0/watchfiles-1.1.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:399600947b170270e80134ac854e21b3ccdefa11a9529a3decc1327088180f10", size = 623046, upload-time = "2025-10-14T15:04:42.718Z" }, + { url = "https://files.pythonhosted.org/packages/46/ef/f2ecb9a0f342b4bfad13a2787155c6ee7ce792140eac63a34676a2feeef2/watchfiles-1.1.1-cp311-cp311-win32.whl", hash = "sha256:de6da501c883f58ad50db3a32ad397b09ad29865b5f26f64c24d3e3281685849", size = 271473, upload-time = "2025-10-14T15:04:43.624Z" }, + { url = "https://files.pythonhosted.org/packages/94/bc/f42d71125f19731ea435c3948cad148d31a64fccde3867e5ba4edee901f9/watchfiles-1.1.1-cp311-cp311-win_amd64.whl", hash = "sha256:35c53bd62a0b885bf653ebf6b700d1bf05debb78ad9292cf2a942b23513dc4c4", size = 287598, upload-time = "2025-10-14T15:04:44.516Z" }, + { url = "https://files.pythonhosted.org/packages/57/c9/a30f897351f95bbbfb6abcadafbaca711ce1162f4db95fc908c98a9165f3/watchfiles-1.1.1-cp311-cp311-win_arm64.whl", hash = "sha256:57ca5281a8b5e27593cb7d82c2ac927ad88a96ed406aa446f6344e4328208e9e", size = 277210, upload-time = "2025-10-14T15:04:45.883Z" }, + { url = "https://files.pythonhosted.org/packages/74/d5/f039e7e3c639d9b1d09b07ea412a6806d38123f0508e5f9b48a87b0a76cc/watchfiles-1.1.1-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:8c89f9f2f740a6b7dcc753140dd5e1ab9215966f7a3530d0c0705c83b401bd7d", size = 404745, upload-time = "2025-10-14T15:04:46.731Z" }, + { url = "https://files.pythonhosted.org/packages/a5/96/a881a13aa1349827490dab2d363c8039527060cfcc2c92cc6d13d1b1049e/watchfiles-1.1.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:bd404be08018c37350f0d6e34676bd1e2889990117a2b90070b3007f172d0610", size = 391769, upload-time = "2025-10-14T15:04:48.003Z" }, + { url = "https://files.pythonhosted.org/packages/4b/5b/d3b460364aeb8da471c1989238ea0e56bec24b6042a68046adf3d9ddb01c/watchfiles-1.1.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8526e8f916bb5b9a0a777c8317c23ce65de259422bba5b31325a6fa6029d33af", size = 449374, upload-time = "2025-10-14T15:04:49.179Z" }, + { url = "https://files.pythonhosted.org/packages/b9/44/5769cb62d4ed055cb17417c0a109a92f007114a4e07f30812a73a4efdb11/watchfiles-1.1.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2edc3553362b1c38d9f06242416a5d8e9fe235c204a4072e988ce2e5bb1f69f6", size = 459485, upload-time = "2025-10-14T15:04:50.155Z" }, + { url = "https://files.pythonhosted.org/packages/19/0c/286b6301ded2eccd4ffd0041a1b726afda999926cf720aab63adb68a1e36/watchfiles-1.1.1-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:30f7da3fb3f2844259cba4720c3fc7138eb0f7b659c38f3bfa65084c7fc7abce", size = 488813, upload-time = "2025-10-14T15:04:51.059Z" }, + { url = "https://files.pythonhosted.org/packages/c7/2b/8530ed41112dd4a22f4dcfdb5ccf6a1baad1ff6eed8dc5a5f09e7e8c41c7/watchfiles-1.1.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f8979280bdafff686ba5e4d8f97840f929a87ed9cdf133cbbd42f7766774d2aa", size = 594816, upload-time = "2025-10-14T15:04:52.031Z" }, + { url = "https://files.pythonhosted.org/packages/ce/d2/f5f9fb49489f184f18470d4f99f4e862a4b3e9ac2865688eb2099e3d837a/watchfiles-1.1.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dcc5c24523771db3a294c77d94771abcfcb82a0e0ee8efd910c37c59ec1b31bb", size = 475186, upload-time = "2025-10-14T15:04:53.064Z" }, + { url = "https://files.pythonhosted.org/packages/cf/68/5707da262a119fb06fbe214d82dd1fe4a6f4af32d2d14de368d0349eb52a/watchfiles-1.1.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1db5d7ae38ff20153d542460752ff397fcf5c96090c1230803713cf3147a6803", size = 456812, upload-time = "2025-10-14T15:04:55.174Z" }, + { url = "https://files.pythonhosted.org/packages/66/ab/3cbb8756323e8f9b6f9acb9ef4ec26d42b2109bce830cc1f3468df20511d/watchfiles-1.1.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:28475ddbde92df1874b6c5c8aaeb24ad5be47a11f87cde5a28ef3835932e3e94", size = 630196, upload-time = "2025-10-14T15:04:56.22Z" }, + { url = "https://files.pythonhosted.org/packages/78/46/7152ec29b8335f80167928944a94955015a345440f524d2dfe63fc2f437b/watchfiles-1.1.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:36193ed342f5b9842edd3532729a2ad55c4160ffcfa3700e0d54be496b70dd43", size = 622657, upload-time = "2025-10-14T15:04:57.521Z" }, + { url = "https://files.pythonhosted.org/packages/0a/bf/95895e78dd75efe9a7f31733607f384b42eb5feb54bd2eb6ed57cc2e94f4/watchfiles-1.1.1-cp312-cp312-win32.whl", hash = "sha256:859e43a1951717cc8de7f4c77674a6d389b106361585951d9e69572823f311d9", size = 272042, upload-time = "2025-10-14T15:04:59.046Z" }, + { url = "https://files.pythonhosted.org/packages/87/0a/90eb755f568de2688cb220171c4191df932232c20946966c27a59c400850/watchfiles-1.1.1-cp312-cp312-win_amd64.whl", hash = "sha256:91d4c9a823a8c987cce8fa2690923b069966dabb196dd8d137ea2cede885fde9", size = 288410, upload-time = "2025-10-14T15:05:00.081Z" }, + { url = "https://files.pythonhosted.org/packages/36/76/f322701530586922fbd6723c4f91ace21364924822a8772c549483abed13/watchfiles-1.1.1-cp312-cp312-win_arm64.whl", hash = "sha256:a625815d4a2bdca61953dbba5a39d60164451ef34c88d751f6c368c3ea73d404", size = 278209, upload-time = "2025-10-14T15:05:01.168Z" }, + { url = "https://files.pythonhosted.org/packages/ba/4c/a888c91e2e326872fa4705095d64acd8aa2fb9c1f7b9bd0588f33850516c/watchfiles-1.1.1-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:17ef139237dfced9da49fb7f2232c86ca9421f666d78c264c7ffca6601d154c3", size = 409611, upload-time = "2025-10-14T15:06:05.809Z" }, + { url = "https://files.pythonhosted.org/packages/1e/c7/5420d1943c8e3ce1a21c0a9330bcf7edafb6aa65d26b21dbb3267c9e8112/watchfiles-1.1.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:672b8adf25b1a0d35c96b5888b7b18699d27d4194bac8beeae75be4b7a3fc9b2", size = 396889, upload-time = "2025-10-14T15:06:07.035Z" }, + { url = "https://files.pythonhosted.org/packages/0c/e5/0072cef3804ce8d3aaddbfe7788aadff6b3d3f98a286fdbee9fd74ca59a7/watchfiles-1.1.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:77a13aea58bc2b90173bc69f2a90de8e282648939a00a602e1dc4ee23e26b66d", size = 451616, upload-time = "2025-10-14T15:06:08.072Z" }, + { url = "https://files.pythonhosted.org/packages/83/4e/b87b71cbdfad81ad7e83358b3e447fedd281b880a03d64a760fe0a11fc2e/watchfiles-1.1.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0b495de0bb386df6a12b18335a0285dda90260f51bdb505503c02bcd1ce27a8b", size = 458413, upload-time = "2025-10-14T15:06:09.209Z" }, + { url = "https://files.pythonhosted.org/packages/d3/8e/e500f8b0b77be4ff753ac94dc06b33d8f0d839377fee1b78e8c8d8f031bf/watchfiles-1.1.1-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:db476ab59b6765134de1d4fe96a1a9c96ddf091683599be0f26147ea1b2e4b88", size = 408250, upload-time = "2025-10-14T15:06:10.264Z" }, + { url = "https://files.pythonhosted.org/packages/bd/95/615e72cd27b85b61eec764a5ca51bd94d40b5adea5ff47567d9ebc4d275a/watchfiles-1.1.1-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:89eef07eee5e9d1fda06e38822ad167a044153457e6fd997f8a858ab7564a336", size = 396117, upload-time = "2025-10-14T15:06:11.28Z" }, + { url = "https://files.pythonhosted.org/packages/c9/81/e7fe958ce8a7fb5c73cc9fb07f5aeaf755e6aa72498c57d760af760c91f8/watchfiles-1.1.1-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ce19e06cbda693e9e7686358af9cd6f5d61312ab8b00488bc36f5aabbaf77e24", size = 450493, upload-time = "2025-10-14T15:06:12.321Z" }, + { url = "https://files.pythonhosted.org/packages/6e/d4/ed38dd3b1767193de971e694aa544356e63353c33a85d948166b5ff58b9e/watchfiles-1.1.1-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3e6f39af2eab0118338902798b5aa6664f46ff66bc0280de76fca67a7f262a49", size = 457546, upload-time = "2025-10-14T15:06:13.372Z" }, +] + +[[package]] +name = "websockets" +version = "15.0.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/21/e6/26d09fab466b7ca9c7737474c52be4f76a40301b08362eb2dbc19dcc16c1/websockets-15.0.1.tar.gz", hash = "sha256:82544de02076bafba038ce055ee6412d68da13ab47f0c60cab827346de828dee", size = 177016, upload-time = "2025-03-05T20:03:41.606Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1e/da/6462a9f510c0c49837bbc9345aca92d767a56c1fb2939e1579df1e1cdcf7/websockets-15.0.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d63efaa0cd96cf0c5fe4d581521d9fa87744540d4bc999ae6e08595a1014b45b", size = 175423, upload-time = "2025-03-05T20:01:35.363Z" }, + { url = "https://files.pythonhosted.org/packages/1c/9f/9d11c1a4eb046a9e106483b9ff69bce7ac880443f00e5ce64261b47b07e7/websockets-15.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ac60e3b188ec7574cb761b08d50fcedf9d77f1530352db4eef1707fe9dee7205", size = 173080, upload-time = "2025-03-05T20:01:37.304Z" }, + { url = "https://files.pythonhosted.org/packages/d5/4f/b462242432d93ea45f297b6179c7333dd0402b855a912a04e7fc61c0d71f/websockets-15.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5756779642579d902eed757b21b0164cd6fe338506a8083eb58af5c372e39d9a", size = 173329, upload-time = "2025-03-05T20:01:39.668Z" }, + { url = "https://files.pythonhosted.org/packages/6e/0c/6afa1f4644d7ed50284ac59cc70ef8abd44ccf7d45850d989ea7310538d0/websockets-15.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0fdfe3e2a29e4db3659dbd5bbf04560cea53dd9610273917799f1cde46aa725e", size = 182312, upload-time = "2025-03-05T20:01:41.815Z" }, + { url = "https://files.pythonhosted.org/packages/dd/d4/ffc8bd1350b229ca7a4db2a3e1c482cf87cea1baccd0ef3e72bc720caeec/websockets-15.0.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4c2529b320eb9e35af0fa3016c187dffb84a3ecc572bcee7c3ce302bfeba52bf", size = 181319, upload-time = "2025-03-05T20:01:43.967Z" }, + { url = "https://files.pythonhosted.org/packages/97/3a/5323a6bb94917af13bbb34009fac01e55c51dfde354f63692bf2533ffbc2/websockets-15.0.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ac1e5c9054fe23226fb11e05a6e630837f074174c4c2f0fe442996112a6de4fb", size = 181631, upload-time = "2025-03-05T20:01:46.104Z" }, + { url = "https://files.pythonhosted.org/packages/a6/cc/1aeb0f7cee59ef065724041bb7ed667b6ab1eeffe5141696cccec2687b66/websockets-15.0.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:5df592cd503496351d6dc14f7cdad49f268d8e618f80dce0cd5a36b93c3fc08d", size = 182016, upload-time = "2025-03-05T20:01:47.603Z" }, + { url = "https://files.pythonhosted.org/packages/79/f9/c86f8f7af208e4161a7f7e02774e9d0a81c632ae76db2ff22549e1718a51/websockets-15.0.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:0a34631031a8f05657e8e90903e656959234f3a04552259458aac0b0f9ae6fd9", size = 181426, upload-time = "2025-03-05T20:01:48.949Z" }, + { url = "https://files.pythonhosted.org/packages/c7/b9/828b0bc6753db905b91df6ae477c0b14a141090df64fb17f8a9d7e3516cf/websockets-15.0.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:3d00075aa65772e7ce9e990cab3ff1de702aa09be3940d1dc88d5abf1ab8a09c", size = 181360, upload-time = "2025-03-05T20:01:50.938Z" }, + { url = "https://files.pythonhosted.org/packages/89/fb/250f5533ec468ba6327055b7d98b9df056fb1ce623b8b6aaafb30b55d02e/websockets-15.0.1-cp310-cp310-win32.whl", hash = "sha256:1234d4ef35db82f5446dca8e35a7da7964d02c127b095e172e54397fb6a6c256", size = 176388, upload-time = "2025-03-05T20:01:52.213Z" }, + { url = "https://files.pythonhosted.org/packages/1c/46/aca7082012768bb98e5608f01658ff3ac8437e563eca41cf068bd5849a5e/websockets-15.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:39c1fec2c11dc8d89bba6b2bf1556af381611a173ac2b511cf7231622058af41", size = 176830, upload-time = "2025-03-05T20:01:53.922Z" }, + { url = "https://files.pythonhosted.org/packages/9f/32/18fcd5919c293a398db67443acd33fde142f283853076049824fc58e6f75/websockets-15.0.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:823c248b690b2fd9303ba00c4f66cd5e2d8c3ba4aa968b2779be9532a4dad431", size = 175423, upload-time = "2025-03-05T20:01:56.276Z" }, + { url = "https://files.pythonhosted.org/packages/76/70/ba1ad96b07869275ef42e2ce21f07a5b0148936688c2baf7e4a1f60d5058/websockets-15.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678999709e68425ae2593acf2e3ebcbcf2e69885a5ee78f9eb80e6e371f1bf57", size = 173082, upload-time = "2025-03-05T20:01:57.563Z" }, + { url = "https://files.pythonhosted.org/packages/86/f2/10b55821dd40eb696ce4704a87d57774696f9451108cff0d2824c97e0f97/websockets-15.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d50fd1ee42388dcfb2b3676132c78116490976f1300da28eb629272d5d93e905", size = 173330, upload-time = "2025-03-05T20:01:59.063Z" }, + { url = "https://files.pythonhosted.org/packages/a5/90/1c37ae8b8a113d3daf1065222b6af61cc44102da95388ac0018fcb7d93d9/websockets-15.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d99e5546bf73dbad5bf3547174cd6cb8ba7273062a23808ffea025ecb1cf8562", size = 182878, upload-time = "2025-03-05T20:02:00.305Z" }, + { url = "https://files.pythonhosted.org/packages/8e/8d/96e8e288b2a41dffafb78e8904ea7367ee4f891dafc2ab8d87e2124cb3d3/websockets-15.0.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:66dd88c918e3287efc22409d426c8f729688d89a0c587c88971a0faa2c2f3792", size = 181883, upload-time = "2025-03-05T20:02:03.148Z" }, + { url = "https://files.pythonhosted.org/packages/93/1f/5d6dbf551766308f6f50f8baf8e9860be6182911e8106da7a7f73785f4c4/websockets-15.0.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8dd8327c795b3e3f219760fa603dcae1dcc148172290a8ab15158cf85a953413", size = 182252, upload-time = "2025-03-05T20:02:05.29Z" }, + { url = "https://files.pythonhosted.org/packages/d4/78/2d4fed9123e6620cbf1706c0de8a1632e1a28e7774d94346d7de1bba2ca3/websockets-15.0.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8fdc51055e6ff4adeb88d58a11042ec9a5eae317a0a53d12c062c8a8865909e8", size = 182521, upload-time = "2025-03-05T20:02:07.458Z" }, + { url = "https://files.pythonhosted.org/packages/e7/3b/66d4c1b444dd1a9823c4a81f50231b921bab54eee2f69e70319b4e21f1ca/websockets-15.0.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:693f0192126df6c2327cce3baa7c06f2a117575e32ab2308f7f8216c29d9e2e3", size = 181958, upload-time = "2025-03-05T20:02:09.842Z" }, + { url = "https://files.pythonhosted.org/packages/08/ff/e9eed2ee5fed6f76fdd6032ca5cd38c57ca9661430bb3d5fb2872dc8703c/websockets-15.0.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:54479983bd5fb469c38f2f5c7e3a24f9a4e70594cd68cd1fa6b9340dadaff7cf", size = 181918, upload-time = "2025-03-05T20:02:11.968Z" }, + { url = "https://files.pythonhosted.org/packages/d8/75/994634a49b7e12532be6a42103597b71098fd25900f7437d6055ed39930a/websockets-15.0.1-cp311-cp311-win32.whl", hash = "sha256:16b6c1b3e57799b9d38427dda63edcbe4926352c47cf88588c0be4ace18dac85", size = 176388, upload-time = "2025-03-05T20:02:13.32Z" }, + { url = "https://files.pythonhosted.org/packages/98/93/e36c73f78400a65f5e236cd376713c34182e6663f6889cd45a4a04d8f203/websockets-15.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:27ccee0071a0e75d22cb35849b1db43f2ecd3e161041ac1ee9d2352ddf72f065", size = 176828, upload-time = "2025-03-05T20:02:14.585Z" }, + { url = "https://files.pythonhosted.org/packages/51/6b/4545a0d843594f5d0771e86463606a3988b5a09ca5123136f8a76580dd63/websockets-15.0.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:3e90baa811a5d73f3ca0bcbf32064d663ed81318ab225ee4f427ad4e26e5aff3", size = 175437, upload-time = "2025-03-05T20:02:16.706Z" }, + { url = "https://files.pythonhosted.org/packages/f4/71/809a0f5f6a06522af902e0f2ea2757f71ead94610010cf570ab5c98e99ed/websockets-15.0.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:592f1a9fe869c778694f0aa806ba0374e97648ab57936f092fd9d87f8bc03665", size = 173096, upload-time = "2025-03-05T20:02:18.832Z" }, + { url = "https://files.pythonhosted.org/packages/3d/69/1a681dd6f02180916f116894181eab8b2e25b31e484c5d0eae637ec01f7c/websockets-15.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0701bc3cfcb9164d04a14b149fd74be7347a530ad3bbf15ab2c678a2cd3dd9a2", size = 173332, upload-time = "2025-03-05T20:02:20.187Z" }, + { url = "https://files.pythonhosted.org/packages/a6/02/0073b3952f5bce97eafbb35757f8d0d54812b6174ed8dd952aa08429bcc3/websockets-15.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e8b56bdcdb4505c8078cb6c7157d9811a85790f2f2b3632c7d1462ab5783d215", size = 183152, upload-time = "2025-03-05T20:02:22.286Z" }, + { url = "https://files.pythonhosted.org/packages/74/45/c205c8480eafd114b428284840da0b1be9ffd0e4f87338dc95dc6ff961a1/websockets-15.0.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0af68c55afbd5f07986df82831c7bff04846928ea8d1fd7f30052638788bc9b5", size = 182096, upload-time = "2025-03-05T20:02:24.368Z" }, + { url = "https://files.pythonhosted.org/packages/14/8f/aa61f528fba38578ec553c145857a181384c72b98156f858ca5c8e82d9d3/websockets-15.0.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:64dee438fed052b52e4f98f76c5790513235efaa1ef7f3f2192c392cd7c91b65", size = 182523, upload-time = "2025-03-05T20:02:25.669Z" }, + { url = "https://files.pythonhosted.org/packages/ec/6d/0267396610add5bc0d0d3e77f546d4cd287200804fe02323797de77dbce9/websockets-15.0.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:d5f6b181bb38171a8ad1d6aa58a67a6aa9d4b38d0f8c5f496b9e42561dfc62fe", size = 182790, upload-time = "2025-03-05T20:02:26.99Z" }, + { url = "https://files.pythonhosted.org/packages/02/05/c68c5adbf679cf610ae2f74a9b871ae84564462955d991178f95a1ddb7dd/websockets-15.0.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:5d54b09eba2bada6011aea5375542a157637b91029687eb4fdb2dab11059c1b4", size = 182165, upload-time = "2025-03-05T20:02:30.291Z" }, + { url = "https://files.pythonhosted.org/packages/29/93/bb672df7b2f5faac89761cb5fa34f5cec45a4026c383a4b5761c6cea5c16/websockets-15.0.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3be571a8b5afed347da347bfcf27ba12b069d9d7f42cb8c7028b5e98bbb12597", size = 182160, upload-time = "2025-03-05T20:02:31.634Z" }, + { url = "https://files.pythonhosted.org/packages/ff/83/de1f7709376dc3ca9b7eeb4b9a07b4526b14876b6d372a4dc62312bebee0/websockets-15.0.1-cp312-cp312-win32.whl", hash = "sha256:c338ffa0520bdb12fbc527265235639fb76e7bc7faafbb93f6ba80d9c06578a9", size = 176395, upload-time = "2025-03-05T20:02:33.017Z" }, + { url = "https://files.pythonhosted.org/packages/7d/71/abf2ebc3bbfa40f391ce1428c7168fb20582d0ff57019b69ea20fa698043/websockets-15.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:fcd5cf9e305d7b8338754470cf69cf81f420459dbae8a3b40cee57417f4614a7", size = 176841, upload-time = "2025-03-05T20:02:34.498Z" }, + { url = "https://files.pythonhosted.org/packages/02/9e/d40f779fa16f74d3468357197af8d6ad07e7c5a27ea1ca74ceb38986f77a/websockets-15.0.1-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:0c9e74d766f2818bb95f84c25be4dea09841ac0f734d1966f415e4edfc4ef1c3", size = 173109, upload-time = "2025-03-05T20:03:17.769Z" }, + { url = "https://files.pythonhosted.org/packages/bc/cd/5b887b8585a593073fd92f7c23ecd3985cd2c3175025a91b0d69b0551372/websockets-15.0.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:1009ee0c7739c08a0cd59de430d6de452a55e42d6b522de7aa15e6f67db0b8e1", size = 173343, upload-time = "2025-03-05T20:03:19.094Z" }, + { url = "https://files.pythonhosted.org/packages/fe/ae/d34f7556890341e900a95acf4886833646306269f899d58ad62f588bf410/websockets-15.0.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:76d1f20b1c7a2fa82367e04982e708723ba0e7b8d43aa643d3dcd404d74f1475", size = 174599, upload-time = "2025-03-05T20:03:21.1Z" }, + { url = "https://files.pythonhosted.org/packages/71/e6/5fd43993a87db364ec60fc1d608273a1a465c0caba69176dd160e197ce42/websockets-15.0.1-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f29d80eb9a9263b8d109135351caf568cc3f80b9928bccde535c235de55c22d9", size = 174207, upload-time = "2025-03-05T20:03:23.221Z" }, + { url = "https://files.pythonhosted.org/packages/2b/fb/c492d6daa5ec067c2988ac80c61359ace5c4c674c532985ac5a123436cec/websockets-15.0.1-pp310-pypy310_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b359ed09954d7c18bbc1680f380c7301f92c60bf924171629c5db97febb12f04", size = 174155, upload-time = "2025-03-05T20:03:25.321Z" }, + { url = "https://files.pythonhosted.org/packages/68/a1/dcb68430b1d00b698ae7a7e0194433bce4f07ded185f0ee5fb21e2a2e91e/websockets-15.0.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:cad21560da69f4ce7658ca2cb83138fb4cf695a2ba3e475e0559e05991aa8122", size = 176884, upload-time = "2025-03-05T20:03:27.934Z" }, + { url = "https://files.pythonhosted.org/packages/fa/a8/5b41e0da817d64113292ab1f8247140aac61cbf6cfd085d6a0fa77f4984f/websockets-15.0.1-py3-none-any.whl", hash = "sha256:f7a866fbc1e97b5c617ee4116daaa09b722101d4a3c170c787450ba409f9736f", size = 169743, upload-time = "2025-03-05T20:03:39.41Z" }, +] diff --git a/pkgs/experimental/swarmakit/.gitignore b/pkgs/experimental/swarmakit/.gitignore new file mode 100644 index 0000000000..e870aa35cb --- /dev/null +++ b/pkgs/experimental/swarmakit/.gitignore @@ -0,0 +1,133 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +lerna-debug.log* +.pnpm-debug.log* + +# Diagnostic reports (https://nodejs.org/api/report.html) +report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage +*.lcov + +# nyc test coverage +.nyc_output + +# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# Bower dependency directory (https://bower.io/) +bower_components + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (https://nodejs.org/api/addons.html) +build/Release + +# Dependency directories +node_modules/ +jspm_packages/ + +# Snowpack dependency directory (https://snowpack.dev/) +web_modules/ + +# TypeScript cache +*.tsbuildinfo + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Optional stylelint cache +.stylelintcache + +# Microbundle cache +.rpt2_cache/ +.rts2_cache_cjs/ +.rts2_cache_es/ +.rts2_cache_umd/ + +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity + +# dotenv environment variable files +.env +.env.development.local +.env.test.local +.env.production.local +.env.local + +# parcel-bundler cache (https://parceljs.org/) +.cache +.parcel-cache + +# Next.js build output +.next +out + +# Nuxt.js build / generate output +.nuxt +dist + +# Gatsby files +.cache/ +# Comment in the public line in if your project uses Gatsby and not Next.js +# https://nextjs.org/blog/next-9-1#public-directory-support +# public + +# vuepress build output +.vuepress/dist + +# vuepress v2.x temp and cache directory +.temp +.cache + +# Docusaurus cache and generated files +.docusaurus + +# Serverless directories +.serverless/ + +# FuseBox cache +.fusebox/ + +# DynamoDB Local files +.dynamodb/ + +# TernJS port file +.tern-port + +# Stores VSCode versions used for testing VSCode extensions +.vscode-test + +# yarn v2 +.yarn/cache +.yarn/unplugged +.yarn/build-state.yml +.yarn/install-state.gz +.pnp.* +/app/storybook-static + +*storybook.log \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/.npmignore b/pkgs/experimental/swarmakit/.npmignore new file mode 100644 index 0000000000..04c01ba7ba --- /dev/null +++ b/pkgs/experimental/swarmakit/.npmignore @@ -0,0 +1,2 @@ +node_modules/ +dist/ \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/CONTRIBUTING.md b/pkgs/experimental/swarmakit/CONTRIBUTING.md new file mode 100644 index 0000000000..8ce3f9d639 --- /dev/null +++ b/pkgs/experimental/swarmakit/CONTRIBUTING.md @@ -0,0 +1,180 @@ +# Contributing Guidelines + +We welcome contributions to improve Swarmakit. This guide outlines the steps to follow when contributing, adhering to the feature-based branching principle, which helps keep the codebase clean and organized. + +## Reporting Bugs + +If you found an issue and you want to report it, please follow these steps: + +1. **Search Existing Issues**: Before reporting a bug, check the Issues to see if the problem has already been reported or is being addressed. + +2. **Create a new Bug report:** If no issue matches, open a new issue using the provided bug report template. Include: + - Detailed steps to reproduce the bug. + - The expected and actual behavior. + - Screenshots, logs, or other helpful information. + +## Suggesting New Features + +If you have an idea for a new feature, please: + +1. **Search Existing Features:** Review existing issues to see if the feature has already been requested. + +2. **Submit a New Feature Request:** If not, create a new issue using the feature request template. Provide: + - A clear description of the feature. + - Its potential use cases and benefits to the project. + +## Suggesting Enhancements + +To suggest improvements to existing features: + +1. **Search Existing Issues:** Make sure the enhancement hasn’t already been proposed. + +2. **Create an Enhancement Request:** If not, submit an issue with the enhancement request template. Describe: + - The current functionality. + - The proposed improvements and how they enhance the project. + +## How to Contribute + +1. **Fork The Repository:** + + Start by creating your own copy of the Swarmakit repository: + + - Navigate to the [Swarmakit repository](https://github.com/swarmauri/swarmakit) + - Click the Fork button in the upper right corner to create a personal copy under your GitHub account. + +2. **Clone Your Forked Repository:** + + Once you've forked the repository, clone it to your local machine: + + ```bash + git clone https://github.com//swarmakit.git + cd swarmakit + ``` + + Replace `` with your GitHub username. + +3. **Set Up Your Development Environment:** + + - Ensure you have Node.js and npm installed. + - Install dependencies + + ```bash + npm install + ``` + +4. **Navigate to Your Desired Library:** + + - Change into the directory you want to work on. For example: + + ```bash + cd libs/vue + ``` + + - Replace `vue` with `react` or `svelte` as needed. + +5. **Create a New Feature Branch:** + + - Before making any changes to the codebase, create a new branch of your feature: + + ```bash + git checkout -b feature/ + ``` + + - Replace `` with a descriptive name for the feature you’re implementing. This naming convention helps identify the purpose of the branch. + +6. **Pull the Latest Changes:** + + - Before making any modifications, ensure your local repository is up to date with the latest changes from GitHub: + + ```bash + git pull + ``` + + - This command ensures you have the latest code before starting your work. + +7. **Make Your Changes:** + + - With your new branch created and the library selected, implement your changes: + + - Ensure your code follows existing coding standards. + - Write clear, concise commit messages describing your changes. + - Consider adding tests or documentation if applicable. + +8. **Test Your Changes:** + + - Run the build command to check for any errors: + + ``` bash + npm run build + ``` + + - If you’ve added new features, ensure they work correctly by testing manually where necessary. + +9. **Commit Your Changes:** + + - Once you are satisfied with your changes, commit them to your feature branch: + + ```bash + git commit -m "feat: add new component to Vue library for improved user interface" + ``` + + - Follow the conventional commit format for commit messages, starting with feat: for new features, fix: for bug fixes, and other relevant prefixes. + +10. **Push Your Changes:** + + - After committing, push your changes to your forked repository: + + ```bash + git push origin feature/ + ``` + +11. **Write Tests:** + + - Ensure each new feature has an associated test file. + - Tests should cover: + 1. **Component Type:** Verify the component is of the expected type. + 2. **Resource Handling:** Validate inputs/outputs and dependencies. + 3. **Serialization:** Ensure data is properly serialized and deserialized. + 4. **Access Method:** Test component accessibility within the system. + 5. **Functionality:** Confirm the feature meets the project requirements. + +12. **Open a Pull Request:** + + - Navigate back to the original Swarmakit repository on GitHub: + + - Click the Pull Requests tab. + - Click the New Pull Request button. + - Select your feature branch from the dropdown menu and provide a clear title and description for your pull request, explaining the changes made and their purpose. + - Click Create Pull Request. + +13. **Review and Address Feedback:** + + - After submitting your pull request, maintainers will review your changes. Be open to feedback: + - + - Make any requested changes by committing them to your feature branch. + - Push the updates, and the pull request will automatically update. + +14. **Merge and Celebrate:** + + - Keep Your Branch Updated: Regularly pull updates from the main repository to keep your feature branch up to date: + + - Once your pull request is approved, it will be merged into the main branch. Congratulations on contributing to Swarmakit! + +### Development Setup + +1. **Run Tests with GitHub Actions:** + + - GitHub Actions will automatically run tests for your changes. + - Check the Actions tab to verify if your changes pass the tests. + +2. **Enabling GitHub Actions on Your Fork:** + + - **Check for Workflow Files:** Ensure `.yml` workflow files are present under `.github/workflows` in your fork. + - **Enable Actions:** + - Go to the "**Settings**" tab of your fork. + - Under "**Actions**" in the left sidebar, ensure Actions are enabled. If not, enable them. + +## Licensing + +This project is licensed under the [Project License](https://github.com/swarmauri/swarmakit/blob/master/LICENSE). +Please ensure that your contributions comply with the terms of the license. diff --git a/pkgs/experimental/swarmakit/LICENSE b/pkgs/experimental/swarmakit/LICENSE new file mode 100644 index 0000000000..261eeb9e9f --- /dev/null +++ b/pkgs/experimental/swarmakit/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/pkgs/experimental/swarmakit/README.md b/pkgs/experimental/swarmakit/README.md new file mode 100644 index 0000000000..52a1cfbf27 --- /dev/null +++ b/pkgs/experimental/swarmakit/README.md @@ -0,0 +1,88 @@ +![Swamauri Logo](https://res.cloudinary.com/dbjmpekvl/image/upload/v1730099724/Swarmauri-logo-lockup-2048x757_hww01w.png) + +
    + +[![Hits](https://hits.sh/github.com/swarmauri/swarmakit.svg)](https://hits.sh/github.com/swarmauri/swarmakit/) +![NPM Version](https://img.shields.io/npm/v/swarmakit?label=version) +![npm downloads](https://img.shields.io/npm/dt/swarmakit.svg) +[![License](https://img.shields.io/badge/License-Apache_2.0-blue.svg)](https://opensource.org/licenses/Apache-2.0) +[![Build and Publish Monorepo](https://github.com/swarmauri/swarmakit/actions/workflows/publish.yml/badge.svg)](https://github.com/swarmauri/swarmakit/actions/workflows/publish.yml) +
    + +
    + +![Static Badge](https://img.shields.io/badge/React-61DBFB?style=for-the-badge&logo=react&labelColor=black) +![Static Badge](https://img.shields.io/badge/Vue-059669?style=for-the-badge&logo=vuedotjs&labelColor=black) +![Static Badge](https://img.shields.io/badge/Svelte-FF3E00?style=for-the-badge&logo=svelte&labelColor=black) +![Static Badge](https://img.shields.io/badge/TypeScript-1D4ED8?style=for-the-badge&logo=typescript&labelColor=black) +
    + +--- + +# Swarmakit + +Swarmakit offers reusable UI components for React, Vue, and Svelte that integrate seamlessly into Vite-powered applications. + +Each framework's components are built with TypeScript for type safety and optimized for performance, designed with best practices to take full advantage of Vite's fast development environment. + +# Installation + +## 1. Prerequisites + +To install Swarmakit libraries, ensure npm is installed. Run the following command: + +```bash +npm install npm@latest -g +``` + +## 2. Initialize a Vite Application + +To Start a new project with Vite and TypeScript for each framework, use the following commands: + +### Vue 3 + TypeScript + +```bash +npm create vite@latest my-vue-app -- --latest vue-ts +cd my-vue-app +npm install +``` + +replacing `my-vue-app` with your project's name + +### React + TypeScript + +```bash +npm create vite@latest my-react-app -- --latest react-ts +cd my-react-app +npm install +``` + +replacing `my-react-app` with your project's name + +### Svelte + TypeScript + +```bash +npm create vite@latest my-svelte-app -- --latest svelte-ts +cd my-svelte-app +npm install +``` + +replacing `my-svelte-app` with your project's name + +## 3. Install Swarmakit Libraries + +Install the Swarmakit component libraries for each framework as needed: + +```bash +npm install @swarmakit/vue @swarmakit/react @swarmakit/svelte +``` + +For framework specific setup and best practices please refer to their specific documentation: + +1. [React Library Doc](https://github.com/swarmauri/swarmakit/blob/master/libs/react/README.md) +2. [Svelte Library Doc](https://github.com/swarmauri/swarmakit/blob/master/libs/sveltekit/README.md) +3. [Vue Library Doc](https://github.com/swarmauri/swarmakit/blob/master/libs/vue/README.md) + +# Want to help? + +If you want to contribute to Swarmakit, read up on our [guidelines for contributing](https://github.com/swarmauri/swarmakit/blob/master/CONTRIBUTING.md) that will help you get started. diff --git a/pkgs/experimental/swarmakit/libs/layout-engine-vue/README.md b/pkgs/experimental/swarmakit/libs/layout-engine-vue/README.md new file mode 100644 index 0000000000..9475b86ba8 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/layout-engine-vue/README.md @@ -0,0 +1,120 @@ +# @swarmakit/layout-engine-vue + +> Thin Vue wrapper for Layout Engine manifests and SwarmaKit atoms. + +## Installation + +```bash +pnpm add @swarmakit/layout-engine-vue +``` + +Peer dependencies: + +```bash +pnpm add vue @swarmakit/vue +``` + +## Server Quickstart (Python) + +```python +from layout_engine import ( + AtomRegistry, + AtomSpec, + LayoutCompiler, + LayoutManifest, + ManifestBuilder, + TileSpec, + Viewport, +) +from layout_engine.grid.spec import GridSpec, GridTrack, GridTile +from layout_engine.core.size import Size + +atoms = AtomRegistry() +atoms.register( + AtomSpec( + role="swarmakit:vue:hero", + module="@swarmakit/vue", + export="HeroCard", + defaults={"size": "md"}, + family="swarmakit", + ) +) + +compiler = LayoutCompiler() +viewport = Viewport(width=1280, height=720) +grid = GridSpec(columns=[GridTrack(size=Size(1, "fr"))]) +placements = [GridTile(tile_id="hero", col=0, row=0)] +frames = compiler.frames(grid, viewport, placements) + +view_model = compiler.view_model( + grid, + viewport, + frames, + [TileSpec(id="hero", role="swarmakit:vue:hero", props={"title": "Welcome"})], + atoms_registry=atoms, + channels=[{"id": "ui.events", "scope": "page", "topic": "page:{page_id}:ui"}], + ws_routes=[{"path": "/ws/ui", "channels": ["ui.events"]}], +) + +manifest = ManifestBuilder().build(view_model) +# expose `manifest` via FastAPI/Flask etc. at /api/manifest.json +``` + +## Vue Quickstart + +```ts +// main.ts +import { createApp } from "vue"; +import App from "./App.vue"; +import { createLayoutEngineApp } from "@swarmakit/layout-engine-vue"; + +async function bootstrap() { + const engine = await createLayoutEngineApp({ + manifestUrl: "/api/manifest.json", + muxUrl: "ws://localhost:8765/ws/ui", + }); + + const vueApp = createApp(App, { manifest: engine.manifest }); + vueApp.use(engine.plugin); + vueApp.mount("#app"); +} + +bootstrap(); +``` + +```vue + + +``` + +## Commands + +```bash +pnpm install +pnpm dev # start Vite playground (configure separately) +pnpm build # build library bundles +pnpm test # run Vitest suite +``` + +## Testing + +Vitest example test (`tests/loader.spec.ts`) validates manifest hydration and component caching. Add +more coverage as runtime features expand (e.g. WebSocket mux integration). + +--- + +MIT © Swarmauri Labs diff --git a/pkgs/experimental/swarmakit/libs/layout-engine-vue/package.json b/pkgs/experimental/swarmakit/libs/layout-engine-vue/package.json new file mode 100644 index 0000000000..c03398e5e8 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/layout-engine-vue/package.json @@ -0,0 +1,34 @@ +{ + "name": "@swarmakit/layout-engine-vue", + "version": "0.0.1", + "description": "Thin Vue wrapper for Layout Engine manifests and SwarmaKit atoms.", + "type": "module", + "main": "dist/index.cjs", + "module": "dist/index.mjs", + "types": "dist/index.d.ts", + "files": [ + "dist" + ], + "scripts": { + "build": "tsup src/index.ts --format esm,cjs --dts --clean", + "dev": "tsup src/index.ts --format esm,cjs --dts --watch", + "lint": "eslint \"src/**/*.{ts,tsx}\"", + "format": "prettier --write \"src/**/*.{ts,tsx,vue}\"", + "test": "vitest run" + }, + "peerDependencies": { + "@swarmakit/vue": ">=0.0.22", + "vue": "^3.4.0" + }, + "dependencies": { + "eventemitter3": "^5.0.1" + }, + "devDependencies": { + "@types/node": "^20.12.7", + "eslint": "^9.9.0", + "prettier": "^3.3.3", + "tsup": "^8.2.4", + "typescript": "^5.5.4", + "vitest": "^2.0.4" + } +} diff --git a/pkgs/experimental/swarmakit/libs/layout-engine-vue/src/components/LayoutEngineShell.ts b/pkgs/experimental/swarmakit/libs/layout-engine-vue/src/components/LayoutEngineShell.ts new file mode 100644 index 0000000000..5cb2a00074 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/layout-engine-vue/src/components/LayoutEngineShell.ts @@ -0,0 +1,42 @@ +import { defineComponent, h } from "vue"; +import LayoutEngineView from "./LayoutEngineView"; +import { useLayoutManifest } from "../plugin"; +import { useSiteNavigation } from "../site"; + +export default defineComponent({ + name: "LayoutEngineShell", + setup(_, { slots }) { + const manifest = useLayoutManifest(); + const site = useSiteNavigation(manifest); + + const defaultSlot = slots.default; + + return () => { + const nav = site.pages.value; + const current = site.activePage.value; + + return h("div", { class: "layout-engine-shell" }, [ + defaultSlot + ? defaultSlot({ site, manifest }) + : h( + "nav", + { class: "layout-engine-shell__nav" }, + nav.map((page) => + h( + "button", + { + class: [ + "layout-engine-shell__nav-item", + page.id === current?.id && "is-active", + ], + onClick: () => site.navigate(page.id), + }, + page.title ?? page.id + ) + ) + ), + h(LayoutEngineView), + ]); + }; + }, +}); diff --git a/pkgs/experimental/swarmakit/libs/layout-engine-vue/src/components/LayoutEngineView.ts b/pkgs/experimental/swarmakit/libs/layout-engine-vue/src/components/LayoutEngineView.ts new file mode 100644 index 0000000000..001e311f75 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/layout-engine-vue/src/components/LayoutEngineView.ts @@ -0,0 +1,71 @@ +import { defineComponent, h, type VNode } from "vue"; +import { useLayoutManifest, useAtomRegistry } from "../plugin"; +import { useSiteNavigation } from "../site"; + +export default defineComponent({ + name: "LayoutEngineView", + setup(_, { slots }) { + const manifest = useLayoutManifest(); + const registry = useAtomRegistry(); + const site = useSiteNavigation(manifest); + + const renderTiles = (): VNode[] => { + if (!manifest.tiles.length) { + return []; + } + const nodes: VNode[] = []; + for (const tile of manifest.tiles) { + const entry = registry.get(tile.role); + if (!entry) { + console.warn(`Atom '${tile.role}' not registered in registry`); + continue; + } + const frame = tile.frame ?? { x: 0, y: 0, w: 0, h: 0 }; + const style = { + position: "absolute", + left: `${frame.x}px`, + top: `${frame.y}px`, + width: `${frame.w}px`, + height: `${frame.h}px`, + boxSizing: "border-box" as const, + padding: "12px", + display: "flex", + }; + nodes.push( + h( + "div", + { key: tile.id, class: "layout-engine-tile", style }, + [h(entry.component as any, { ...tile.props })] + ) + ); + } + return nodes; + }; + + return () => { + const viewportWidth = manifest.viewport?.width ?? 0; + const viewportHeight = manifest.viewport?.height ?? 0; + if (slots.default) { + return slots.default({ + manifest, + site, + tiles: manifest.tiles, + components: registry, + }); + } + return h( + "div", + { + class: "layout-engine-view", + style: { + position: "relative", + width: viewportWidth ? `${viewportWidth}px` : "100%", + minHeight: viewportHeight ? `${viewportHeight}px` : "auto", + margin: "0 auto", + }, + }, + renderTiles() + ); + }; + }, +}); diff --git a/pkgs/experimental/swarmakit/libs/layout-engine-vue/src/components/NavLink.ts b/pkgs/experimental/swarmakit/libs/layout-engine-vue/src/components/NavLink.ts new file mode 100644 index 0000000000..59a97871cc --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/layout-engine-vue/src/components/NavLink.ts @@ -0,0 +1,33 @@ +import { computed, defineComponent, h } from "vue"; +import { useLayoutManifest } from "../plugin"; +import { useSiteNavigation } from "../site"; + +export default defineComponent({ + name: "LayoutEngineNavLink", + props: { + pageId: { + type: String, + required: true, + }, + }, + setup(props, { slots }) { + const manifest = useLayoutManifest(); + const site = useSiteNavigation(manifest); + const page = computed(() => site.pages.value.find((p) => p.id === props.pageId)); + + const onClick = () => site.navigate(props.pageId); + + return () => + h( + "button", + { + class: [ + "layout-engine-nav-link", + page.value?.id === site.activePage.value?.id && "is-active", + ], + onClick, + }, + slots.default ? slots.default() : page.value?.title ?? props.pageId + ); + }, +}); diff --git a/pkgs/experimental/swarmakit/libs/layout-engine-vue/src/composables.ts b/pkgs/experimental/swarmakit/libs/layout-engine-vue/src/composables.ts new file mode 100644 index 0000000000..b5a453a17a --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/layout-engine-vue/src/composables.ts @@ -0,0 +1,45 @@ +import { computed, ref, type Component, type Ref } from "vue"; +import type { LayoutManifest, LayoutTile, LoadedComponent } from "./types"; +import { loadManifest } from "./loader"; +import { createLayoutEnginePlugin, useAtomRegistry } from "./plugin"; +import { createMuxContext } from "./events"; + +export type LayoutEngineApp = { + plugin: ReturnType; + manifest: Ref; + tiles: Ref; + components: Ref>; + mux?: ReturnType; +}; + +export async function createLayoutEngineApp(options: { + manifestUrl: string; + muxUrl?: string; + muxProtocols?: string | string[]; +}): Promise { + const { manifest, components } = await loadManifest(options.manifestUrl); + const mux = options.muxUrl + ? createMuxContext({ + manifest, + muxUrl: options.muxUrl, + protocols: options.muxProtocols, + }) + : undefined; + const manifestRef = ref(manifest); + const registryRef = ref(components); + + const tiles = computed(() => manifestRef.value.tiles); + const plugin = createLayoutEnginePlugin(manifestRef.value, registryRef.value, mux); + + return { + plugin, + manifest: manifestRef, + tiles, + components: registryRef, + mux, + }; +} + +export function useTileComponent(role: string): Component | undefined { + return useAtomRegistry().get(role)?.component as Component | undefined; +} diff --git a/pkgs/experimental/swarmakit/libs/layout-engine-vue/src/env.d.ts b/pkgs/experimental/swarmakit/libs/layout-engine-vue/src/env.d.ts new file mode 100644 index 0000000000..11f02fe2a0 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/layout-engine-vue/src/env.d.ts @@ -0,0 +1 @@ +/// diff --git a/pkgs/experimental/swarmakit/libs/layout-engine-vue/src/events.ts b/pkgs/experimental/swarmakit/libs/layout-engine-vue/src/events.ts new file mode 100644 index 0000000000..a10083077e --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/layout-engine-vue/src/events.ts @@ -0,0 +1,32 @@ +import { onBeforeUnmount, ref } from "vue"; +import type { LayoutManifest, ManifestChannel } from "./types"; +import { WSMuxClient, type SubscribeHandler } from "./mux"; + +const muxKey = Symbol("layout-engine:mux"); + +export type MuxContext = { + client: WSMuxClient; + channels: ManifestChannel[]; +}; + +export function createMuxContext(options: { + manifest: LayoutManifest; + muxUrl: string; + protocols?: string | string[]; +}): MuxContext { + const client = new WSMuxClient({ url: options.muxUrl, protocols: options.protocols }); + client.connect(); + return { + client, + channels: options.manifest.channels ?? [], + }; +} + +export function useMux(mux: MuxContext, channelId: string, handler: SubscribeHandler) { + const unsubscribe = mux.client.subscribe(channelId, handler); + onBeforeUnmount(() => unsubscribe()); + + return { + publish: (payload: unknown) => mux.client.publish(channelId, payload), + }; +} diff --git a/pkgs/experimental/swarmakit/libs/layout-engine-vue/src/index.ts b/pkgs/experimental/swarmakit/libs/layout-engine-vue/src/index.ts new file mode 100644 index 0000000000..7e34c90205 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/layout-engine-vue/src/index.ts @@ -0,0 +1,20 @@ +export { createLayoutEngineApp } from "./composables"; +export { + createLayoutEnginePlugin, + useLayoutManifest, + useAtomRegistry, + useMuxContext, +} from "./plugin"; +export { useSiteNavigation } from "./site"; +export { default as LayoutEngineView } from "./components/LayoutEngineView"; +export { default as LayoutEngineShell } from "./components/LayoutEngineShell"; +export { default as LayoutEngineNavLink } from "./components/NavLink"; +export { createMuxContext, useMux } from "./events"; +export { WSMuxClient } from "./mux"; +export type { + LayoutManifest, + LayoutTile, + ManifestAtom, + ManifestChannel, + ManifestWsRoute, +} from "./types"; diff --git a/pkgs/experimental/swarmakit/libs/layout-engine-vue/src/loader.ts b/pkgs/experimental/swarmakit/libs/layout-engine-vue/src/loader.ts new file mode 100644 index 0000000000..cd7441c676 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/layout-engine-vue/src/loader.ts @@ -0,0 +1,71 @@ +import { markRaw } from "vue"; +import type { + LayoutManifest, + ManifestAtom, + ManifestLoaderOptions, + AtomRegistryMap, +} from "./types"; + +const manifestCache = new Map(); +const componentCache = new Map(); + +async function defaultFetcher(url: string): Promise { + const res = await fetch(url); + if (!res.ok) { + throw new Error(`Failed to fetch manifest: ${res.status} ${res.statusText}`); + } + return (await res.json()) as LayoutManifest; +} + +async function defaultImportResolver(atom: ManifestAtom): Promise { + if (!atom.module) { + throw new Error(`Missing module specifier for atom ${atom.role}`); + } + const mod = await import(/* @vite-ignore */ atom.module); + const exportName = atom.export ?? "default"; + if (!(exportName in mod)) { + throw new Error(`Export '${exportName}' not found in module ${atom.module}`); + } + return mod[exportName as keyof typeof mod]; +} + +function cacheKeyFromManifest(manifest: LayoutManifest, explicit?: string): string { + if (explicit) return explicit; + const version = manifest.meta?.atoms && typeof manifest.meta.atoms === "object" + ? (manifest.meta.atoms as Record)["revision"] + : undefined; + return String(version ?? manifest.etag ?? manifest.version ?? "default"); +} + +export async function loadManifest( + manifestUrl: string, + options: ManifestLoaderOptions = {} +): Promise<{ manifest: LayoutManifest; components: AtomRegistryMap }> { + const fetcher = options.fetcher ?? defaultFetcher; + const loader = options.importResolver ?? defaultImportResolver; + + const manifest = await fetcher(manifestUrl); + const cacheKey = cacheKeyFromManifest(manifest, options.cacheKey); + + if (manifestCache.has(cacheKey)) { + const cachedManifest = manifestCache.get(cacheKey)!; + const cachedComponents = componentCache.get(cacheKey) ?? new Map(); + return { manifest: cachedManifest, components: cachedComponents }; + } + + const registry = new Map(); + const swarmaAtoms = manifest.tiles + .map((tile) => tile.atom) + .filter((atom): atom is ManifestAtom => Boolean(atom && atom.family === "swarmakit")); + + for (const atom of swarmaAtoms) { + if (registry.has(atom.role)) continue; + const component = await loader(atom); + registry.set(atom.role, { component: markRaw(component), atom }); + } + + manifestCache.set(cacheKey, manifest); + componentCache.set(cacheKey, registry); + + return { manifest, components: registry }; +} diff --git a/pkgs/experimental/swarmakit/libs/layout-engine-vue/src/mux.ts b/pkgs/experimental/swarmakit/libs/layout-engine-vue/src/mux.ts new file mode 100644 index 0000000000..54f2fd21fd --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/layout-engine-vue/src/mux.ts @@ -0,0 +1,182 @@ +import EventEmitter from "eventemitter3"; + +export type MuxMessage = { + channel: string; + payload?: unknown; + [key: string]: unknown; +}; + +export type SubscribeHandler = (message: MuxMessage) => void; + +export type MuxClientOptions = { + url: string; + protocols?: string | string[]; + reconnectDelay?: number; + maxReconnectDelay?: number; + backoffFactor?: number; +}; + +const DEFAULT_RECONNECT_DELAY = 1000; +const DEFAULT_MAX_DELAY = 10000; +const DEFAULT_BACKOFF = 1.7; + +export class WSMuxClient extends EventEmitter { + private socket: WebSocket | null = null; + private readonly options: Required; + private reconnectTimer: ReturnType | null = null; + private readonly subscriptions = new Map>(); + private readonly pendingSubscribes = new Set(); + private readonly outboundQueue: Array> = []; + private reconnectDelay: number; + + constructor(options: MuxClientOptions) { + super(); + if (!options.url) { + throw new Error("WSMuxClient requires a url"); + } + this.options = { + protocols: options.protocols ?? [], + reconnectDelay: options.reconnectDelay ?? DEFAULT_RECONNECT_DELAY, + maxReconnectDelay: options.maxReconnectDelay ?? DEFAULT_MAX_DELAY, + backoffFactor: options.backoffFactor ?? DEFAULT_BACKOFF, + url: options.url, + }; + this.reconnectDelay = this.options.reconnectDelay; + } + + connect(): void { + if (this.socket && (this.socket.readyState === WebSocket.OPEN || this.socket.readyState === WebSocket.CONNECTING)) { + return; + } + this.clearReconnect(); + this.socket = new WebSocket(this.options.url, this.options.protocols); + this.socket.addEventListener("open", this.handleOpen); + this.socket.addEventListener("message", this.handleMessage); + this.socket.addEventListener("close", this.handleClose); + this.socket.addEventListener("error", this.handleError); + } + + disconnect(): void { + this.clearReconnect(); + if (this.socket) { + this.socket.removeEventListener("open", this.handleOpen); + this.socket.removeEventListener("message", this.handleMessage); + this.socket.removeEventListener("close", this.handleClose); + this.socket.removeEventListener("error", this.handleError); + this.socket.close(); + this.socket = null; + } + } + + subscribe(channelId: string, handler: SubscribeHandler): () => void { + if (!this.subscriptions.has(channelId)) { + this.subscriptions.set(channelId, new Set()); + this.enqueueSubscribe(channelId); + } + const set = this.subscriptions.get(channelId)!; + set.add(handler); + this.connect(); + return () => { + const group = this.subscriptions.get(channelId); + if (!group) return; + group.delete(handler); + if (group.size === 0) { + this.subscriptions.delete(channelId); + this.enqueueUnsubscribe(channelId); + } + }; + } + + publish(channelId: string, payload: unknown): void { + this.send({ action: "publish", channel: channelId, payload }); + } + + private enqueueSubscribe(channelId: string) { + this.pendingSubscribes.add(channelId); + this.send({ action: "subscribe", channel: channelId }); + } + + private enqueueUnsubscribe(channelId: string) { + this.send({ action: "unsubscribe", channel: channelId }); + } + + private flushSubscribes() { + for (const channelId of this.subscriptions.keys()) { + this.send({ action: "subscribe", channel: channelId }); + } + this.pendingSubscribes.clear(); + } + + private send(message: Record) { + if (this.socket && this.socket.readyState === WebSocket.OPEN) { + this.socket.send(JSON.stringify(message)); + } else { + this.outboundQueue.push(message); + this.connect(); + } + } + + private handleOpen = () => { + this.emit("open"); + this.reconnectDelay = this.options.reconnectDelay; + while (this.outboundQueue.length) { + const message = this.outboundQueue.shift(); + if (message) { + this.socket?.send(JSON.stringify(message)); + } + } + this.flushSubscribes(); + }; + + private handleMessage = (event: MessageEvent) => { + try { + const data = JSON.parse(String(event.data)); + if (!data || typeof data !== "object" || !("channel" in data)) { + this.emit("raw", data); + return; + } + const channelId = String((data as Record)["channel"] ?? ""); + if (!channelId) return; + this.emit("message", data); + const handlers = this.subscriptions.get(channelId); + if (!handlers) return; + const message: MuxMessage = { + channel: channelId, + payload: (data as Record)["payload"], + ...data, + }; + for (const handler of handlers) { + handler(message); + } + } catch (err) { + this.emit("error", err); + } + }; + + private handleClose = () => { + this.emit("close"); + this.scheduleReconnect(); + }; + + private handleError = (event: Event) => { + this.emit("error", event); + }; + + private scheduleReconnect() { + this.clearReconnect(); + this.reconnectTimer = setTimeout(() => { + this.connect(); + this.reconnectDelay = Math.min( + this.reconnectDelay * this.options.backoffFactor, + this.options.maxReconnectDelay + ); + }, this.reconnectDelay); + } + + private clearReconnect() { + if (this.reconnectTimer) { + clearTimeout(this.reconnectTimer); + this.reconnectTimer = null; + } + } +} diff --git a/pkgs/experimental/swarmakit/libs/layout-engine-vue/src/plugin.ts b/pkgs/experimental/swarmakit/libs/layout-engine-vue/src/plugin.ts new file mode 100644 index 0000000000..2642775cef --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/layout-engine-vue/src/plugin.ts @@ -0,0 +1,47 @@ +import { App, inject, Plugin } from "vue"; +import type { LayoutManifest, AtomRegistryMap } from "./types"; +import type { MuxContext } from "./events"; + +const MANIFEST_KEY = Symbol("layout-engine:manifest"); +const REGISTRY_KEY = Symbol("layout-engine:registry"); +const MUX_KEY = Symbol("layout-engine:mux"); + +export function createLayoutEnginePlugin( + manifest: LayoutManifest, + registry: AtomRegistryMap, + mux?: MuxContext +): Plugin { + return { + install(app: App) { + app.provide(MANIFEST_KEY, manifest); + app.provide(REGISTRY_KEY, registry); + if (mux) { + app.provide(MUX_KEY, mux); + } + }, + }; +} + +export function useLayoutManifest(): LayoutManifest { + const manifest = inject(MANIFEST_KEY); + if (!manifest) { + throw new Error("Layout manifest not found; did you install createLayoutEnginePlugin?"); + } + return manifest; +} + +export function useAtomRegistry(): AtomRegistryMap { + const registry = inject(REGISTRY_KEY); + if (!registry) { + throw new Error("Atom registry not found; did you install createLayoutEnginePlugin?"); + } + return registry; +} + +export function useMuxContext(): MuxContext { + const mux = inject(MUX_KEY); + if (!mux) { + throw new Error("Mux context not provided; pass a 'mux' option to createLayoutEngineApp."); + } + return mux; +} diff --git a/pkgs/experimental/swarmakit/libs/layout-engine-vue/src/router.ts b/pkgs/experimental/swarmakit/libs/layout-engine-vue/src/router.ts new file mode 100644 index 0000000000..82ddae8a55 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/layout-engine-vue/src/router.ts @@ -0,0 +1,44 @@ +import { reactive, watch } from "vue"; +import type { LayoutManifest } from "./types"; +import { useSiteNavigation } from "./site"; + +export type ShellRoute = { + currentPath: string; + params: Record; +}; + +export function createShellRouter(options: { + manifest: LayoutManifest; + onNavigate?: (route: string) => void; +}): ShellRoute { + const state = reactive({ + currentPath: window.location.pathname, + params: {}, + }); + + const site = useSiteNavigation(options.manifest); + + const push = (pageId: string) => { + const route = site.navigate(pageId); + if (!route) return; + state.currentPath = route; + if (options.onNavigate) { + options.onNavigate(route); + } else { + window.history.pushState({}, "", route); + } + }; + + window.addEventListener("popstate", () => { + state.currentPath = window.location.pathname; + }); + + watch( + () => state.currentPath, + (next) => { + console.debug("Shell route changed", next); + } + ); + + return state; +} diff --git a/pkgs/experimental/swarmakit/libs/layout-engine-vue/src/site.ts b/pkgs/experimental/swarmakit/libs/layout-engine-vue/src/site.ts new file mode 100644 index 0000000000..c4da89d629 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/layout-engine-vue/src/site.ts @@ -0,0 +1,63 @@ +import { computed, isRef, reactive, toRefs, watchEffect, type Ref } from "vue"; +import type { LayoutManifest } from "./types"; + +export type SiteNavigation = { + pages: Array<{ id: string; route: string; title?: string; slots?: unknown[] }>; + activePageId?: string | null; + basePath?: string; +}; + +function normaliseSite(manifest: LayoutManifest): SiteNavigation { + const site = manifest.site; + if (!site) { + return { pages: [], activePageId: null, basePath: undefined }; + } + const pages = Array.isArray(site.pages) + ? site.pages.map((page) => ({ + id: String(page?.id ?? ""), + route: String(page?.route ?? "/"), + title: page?.title ? String(page.title) : undefined, + slots: Array.isArray(page?.slots) ? (page?.slots as unknown[]) : undefined, + })) + : []; + return { + pages, + activePageId: (site.active_page ?? (site as Record)?.["activePage"]) as + | string + | null + | undefined, + basePath: site.navigation && typeof site.navigation === "object" + ? (site.navigation as Record)["base_path"] as string | undefined + : undefined, + }; +} + +export function useSiteNavigation(manifest: LayoutManifest | Ref) { + const getManifest = (): LayoutManifest => + isRef(manifest) ? manifest.value : manifest; + const state = reactive(normaliseSite(getManifest())); + + watchEffect(() => { + const next = normaliseSite(getManifest()); + state.pages = next.pages; + state.activePageId = next.activePageId; + state.basePath = next.basePath; + }); + + const activePage = computed(() => + state.pages.find((page) => page.id === state.activePageId) ?? null + ); + + const navigate = (pageId: string): string | null => { + const page = state.pages.find((p) => p.id === pageId); + if (!page) return null; + state.activePageId = page.id; + return page.route; + }; + + return { + ...toRefs(state), + activePage, + navigate, + }; +} diff --git a/pkgs/experimental/swarmakit/libs/layout-engine-vue/src/types.ts b/pkgs/experimental/swarmakit/libs/layout-engine-vue/src/types.ts new file mode 100644 index 0000000000..2fe5062013 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/layout-engine-vue/src/types.ts @@ -0,0 +1,66 @@ +export type LayoutManifest = { + kind: string; + version: string; + viewport: { width: number; height: number }; + grid: Record; + tiles: Array; + etag?: string; + site?: { + active_page?: string | null; + navigation?: Record; + pages?: Array>; + } | null; + channels?: Array; + ws_routes?: Array; + meta?: Record; +}; + +export type LayoutTile = { + id: string; + role: string; + frame: { x: number; y: number; w: number; h: number }; + props: Record; + atom?: ManifestAtom; +}; + +export type ManifestAtom = { + role: string; + module: string; + export: string; + version: string; + framework?: string; + package?: string; + family?: string; + defaults?: Record; + tokens?: Record; + registry?: Record; +}; + +export type ManifestChannel = { + id: string; + scope: string; + topic: string; + description?: string; + payload_schema?: Record; + meta?: Record; +}; + +export type ManifestWsRoute = { + path: string; + channels?: Array; + description?: string; + meta?: Record; +}; + +export type LoadedComponent = { + component: unknown; + atom: ManifestAtom; +}; + +export type AtomRegistryMap = Map; + +export type ManifestLoaderOptions = { + cacheKey?: string; + fetcher?: (url: string) => Promise; + importResolver?: (spec: ManifestAtom) => Promise; +}; diff --git a/pkgs/experimental/swarmakit/libs/layout-engine-vue/tests/loader.spec.ts b/pkgs/experimental/swarmakit/libs/layout-engine-vue/tests/loader.spec.ts new file mode 100644 index 0000000000..9e0511dacd --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/layout-engine-vue/tests/loader.spec.ts @@ -0,0 +1,44 @@ +import { describe, expect, it, vi } from "vitest"; +import { loadManifest } from "../src/loader"; +import type { LayoutManifest, ManifestAtom } from "../src/types"; + +const manifest: LayoutManifest = { + kind: "layout_manifest", + version: "2025.10", + etag: "abc123", + viewport: { width: 800, height: 600 }, + grid: {}, + tiles: [ + { + id: "hero", + role: "swarmakit:vue:hero", + frame: { x: 0, y: 0, w: 800, h: 300 }, + props: {}, + atom: { + role: "swarmakit:vue:hero", + module: "@swarmakit/vue", + export: "HeroCard", + version: "0.0.22", + family: "swarmakit", + } as ManifestAtom, + }, + ], +}; + +describe("loadManifest", () => { + it("loads manifest and caches components", async () => { + const fetcher = vi.fn().mockResolvedValue(manifest); + const resolver = vi.fn().mockResolvedValue({ name: "HeroCard" }); + + const result = await loadManifest("test", { fetcher, importResolver: resolver }); + + expect(fetcher).toHaveBeenCalledTimes(1); + expect(resolver).toHaveBeenCalledTimes(1); + expect(result.components.size).toBe(1); + + resolver.mockClear(); + + await loadManifest("test", { fetcher, importResolver: resolver }); + expect(resolver).not.toHaveBeenCalled(); + }); +}); diff --git a/pkgs/experimental/swarmakit/libs/layout-engine-vue/tsconfig.json b/pkgs/experimental/swarmakit/libs/layout-engine-vue/tsconfig.json new file mode 100644 index 0000000000..07da78c5d7 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/layout-engine-vue/tsconfig.json @@ -0,0 +1,17 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "outDir": "dist", + "rootDir": "src", + "moduleResolution": "bundler", + "module": "ESNext", + "target": "ES2020", + "declaration": true, + "declarationMap": true, + "strict": true, + "jsx": "preserve", + "types": ["vitest/globals"] + }, + "include": ["src"], + "exclude": ["dist", "node_modules"] +} diff --git a/pkgs/experimental/swarmakit/libs/react/.gitignore b/pkgs/experimental/swarmakit/libs/react/.gitignore new file mode 100644 index 0000000000..bc6d3062c4 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/react/.gitignore @@ -0,0 +1,26 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? + +*storybook.log \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/react/.storybook/main.ts b/pkgs/experimental/swarmakit/libs/react/.storybook/main.ts new file mode 100644 index 0000000000..9cf3e235be --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/react/.storybook/main.ts @@ -0,0 +1,17 @@ +import type { StorybookConfig } from "@storybook/react-vite"; + +const config: StorybookConfig = { + stories: ["../src/**/*.mdx", "../src/**/*.stories.@(js|jsx|mjs|ts|tsx)"], + addons: [ + "@storybook/addon-onboarding", + "@storybook/addon-links", + "@storybook/addon-essentials", + "@chromatic-com/storybook", + "@storybook/addon-interactions", + ], + framework: { + name: "@storybook/react-vite", + options: {}, + }, +}; +export default config; diff --git a/pkgs/experimental/swarmakit/libs/react/.storybook/preview.ts b/pkgs/experimental/swarmakit/libs/react/.storybook/preview.ts new file mode 100644 index 0000000000..37914b18f2 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/react/.storybook/preview.ts @@ -0,0 +1,14 @@ +import type { Preview } from "@storybook/react"; + +const preview: Preview = { + parameters: { + controls: { + matchers: { + color: /(background|color)$/i, + date: /Date$/i, + }, + }, + }, +}; + +export default preview; diff --git a/pkgs/experimental/swarmakit/libs/react/README.md b/pkgs/experimental/swarmakit/libs/react/README.md new file mode 100644 index 0000000000..47b98db686 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/react/README.md @@ -0,0 +1,120 @@ +# React + TypeScript + Vite + +
    + +[![Hits](https://hits.seeyoufarm.com/api/count/incr/badge.svg?url=https%3A%2F%2Fgithub.com%2Fswarmauri%2Fswarmakit%2Ftree%2Fmaster%2Flibs%2Freact&count_bg=%2379C83D&title_bg=%23555555&icon=&icon_color=%23E7E7E7&title=hits&edge_flat=false)](https://hits.seeyoufarm.com) +![NPM Version](https://img.shields.io/npm/v/%40swarmakit%2Freact) +![npm downloads](https://img.shields.io/npm/dt/@swarmakit/react.svg) +[![License](https://img.shields.io/badge/License-Apache_2.0-blue.svg)](https://opensource.org/licenses/Apache-2.0) +[![Build and Publish Monorepo](https://github.com/swarmauri/swarmakit/actions/workflows/publish.yml/badge.svg)](https://github.com/swarmauri/swarmakit/actions/workflows/publish.yml) +
    + +
    + +![Static Badge](https://img.shields.io/badge/React-61DBFB?style=for-the-badge&logo=react&labelColor=black) +![Static Badge](https://img.shields.io/badge/TypeScript-1D4ED8?style=for-the-badge&logo=typescript&labelColor=black) +
    + +This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules. + +Currently, two official plugins are available: + +- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md) uses [Babel](https://babeljs.io/) for Fast Refresh +- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh + +## Expanding the ESLint configuration + +If you are developing a production application, we recommend updating the configuration to enable type aware lint rules: + +- Configure the top-level `parserOptions` property like this: + +```js +export default tseslint.config({ + languageOptions: { + // other options... + parserOptions: { + project: ['./tsconfig.node.json', './tsconfig.app.json'], + tsconfigRootDir: import.meta.dirname, + }, + }, +}) +``` + +- Replace `tseslint.configs.recommended` to `tseslint.configs.recommendedTypeChecked` or `tseslint.configs.strictTypeChecked` +- Optionally add `...tseslint.configs.stylisticTypeChecked` +- Install [eslint-plugin-react](https://github.com/jsx-eslint/eslint-plugin-react) and update the config: + +```js +// eslint.config.js +import react from 'eslint-plugin-react' + +export default tseslint.config({ + // Set the react version + settings: { react: { version: '18.3' } }, + plugins: { + // Add the react plugin + react, + }, + rules: { + // other rules... + // Enable its recommended rules + ...react.configs.recommended.rules, + ...react.configs['jsx-runtime'].rules, + }, +}) +``` + +## Installation + +Install the `@swarmakit/react` library through npm: + +```bash +npm install @swarmakit/react +``` + +### Prerequisites + +Node.js and npm should be installed. You can verify installation with: + +```bash +node -v +npm -v +```` + +### Setting Up a Vite + React Project (If you haven't already) + +To initialize a Vite project for React with TypeScript, run: + +```bash +npm create vite@latest my-react-app -- --template react-ts +``` + +replacing `my-react-app` with your project name. + +Then, navigate to your project folder: + +```bash +cd my-react-app +``` + +### Importing Components and Basic Usage in React + +1. **Import Components:** To use a component in your application, import it from the `@swarmakit/react` library as shown below: + + ```javascript + import { ComponentName } from '@swarmakit/react' + ``` + +2. **Example Usage in JSX:** Use the imported component within your React component: + + ```jsx + function App() { + return ( +
    + +
    + ) + } + ``` + +> **Available Components:** Swarmakit React includes a vast library of components. See the full list in the [stories folder on GitHub](https://github.com/swarmauri/swarmakit/tree/master/libs/react/src/stories). diff --git a/pkgs/experimental/swarmakit/libs/react/eslint.config.js b/pkgs/experimental/swarmakit/libs/react/eslint.config.js new file mode 100644 index 0000000000..092408a9f0 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/react/eslint.config.js @@ -0,0 +1,28 @@ +import js from '@eslint/js' +import globals from 'globals' +import reactHooks from 'eslint-plugin-react-hooks' +import reactRefresh from 'eslint-plugin-react-refresh' +import tseslint from 'typescript-eslint' + +export default tseslint.config( + { ignores: ['dist'] }, + { + extends: [js.configs.recommended, ...tseslint.configs.recommended], + files: ['**/*.{ts,tsx}'], + languageOptions: { + ecmaVersion: 2020, + globals: globals.browser, + }, + plugins: { + 'react-hooks': reactHooks, + 'react-refresh': reactRefresh, + }, + rules: { + ...reactHooks.configs.recommended.rules, + 'react-refresh/only-export-components': [ + 'warn', + { allowConstantExport: true }, + ], + }, + }, +) diff --git a/pkgs/experimental/swarmakit/libs/react/index.html b/pkgs/experimental/swarmakit/libs/react/index.html new file mode 100644 index 0000000000..e4b78eae12 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/react/index.html @@ -0,0 +1,13 @@ + + + + + + + Vite + React + TS + + +
    + + + diff --git a/pkgs/experimental/swarmakit/libs/react/package.json b/pkgs/experimental/swarmakit/libs/react/package.json new file mode 100644 index 0000000000..8dc09e6db3 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/react/package.json @@ -0,0 +1,52 @@ +{ + "name": "@swarmakit/react", + "private": false, + "version": "0.0.22", + "type": "module", + "scripts": { + "dev": "vite", + "build": "tsc -b && vite build", + "lint": "eslint .", + "preview": "vite preview", + "storybook": "storybook dev -p 6006", + "build-storybook": "storybook build" + }, + "repository": { + "type": "git", + "url": "https://github.com/swarmauri/swarmakit.git", + "directory": "libs/react" + }, + "dependencies": { + "react": "^18.3.1", + "react-dom": "^18.3.1" + }, + "devDependencies": { + "@chromatic-com/storybook": "^1.9.0", + "@eslint/js": "^8.57.0", + "@storybook/addon-essentials": "^8.3.5", + "@storybook/addon-interactions": "^8.3.5", + "@storybook/addon-links": "^8.3.5", + "@storybook/addon-onboarding": "^8.3.5", + "@storybook/blocks": "^8.3.5", + "@storybook/react": "^8.3.5", + "@storybook/react-vite": "^8.3.5", + "@storybook/test": "^8.3.5", + "@types/react": "^18.3.10", + "@types/react-dom": "^18.3.0", + "@vitejs/plugin-react": "^4.3.2", + "eslint": "^8.57.0", + "eslint-plugin-react-hooks": "^5.1.0-rc.0", + "eslint-plugin-react-refresh": "^0.4.12", + "eslint-plugin-storybook": "^0.9.0", + "globals": "^15.9.0", + "storybook": "^8.3.5", + "typescript": "^5.5.3", + "typescript-eslint": "^8.7.0", + "vite": "^5.4.8" + }, + "eslintConfig": { + "extends": [ + "plugin:storybook/recommended" + ] + } +} diff --git a/pkgs/experimental/swarmakit/libs/react/public/vite.svg b/pkgs/experimental/swarmakit/libs/react/public/vite.svg new file mode 100644 index 0000000000..e7b8dfb1b2 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/react/public/vite.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/react/src/App.css b/pkgs/experimental/swarmakit/libs/react/src/App.css new file mode 100644 index 0000000000..b9d355df2a --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/react/src/App.css @@ -0,0 +1,42 @@ +#root { + max-width: 1280px; + margin: 0 auto; + padding: 2rem; + text-align: center; +} + +.logo { + height: 6em; + padding: 1.5em; + will-change: filter; + transition: filter 300ms; +} +.logo:hover { + filter: drop-shadow(0 0 2em #646cffaa); +} +.logo.react:hover { + filter: drop-shadow(0 0 2em #61dafbaa); +} + +@keyframes logo-spin { + from { + transform: rotate(0deg); + } + to { + transform: rotate(360deg); + } +} + +@media (prefers-reduced-motion: no-preference) { + a:nth-of-type(2) .logo { + animation: logo-spin infinite 20s linear; + } +} + +.card { + padding: 2em; +} + +.read-the-docs { + color: #888; +} diff --git a/pkgs/experimental/swarmakit/libs/react/src/App.tsx b/pkgs/experimental/swarmakit/libs/react/src/App.tsx new file mode 100644 index 0000000000..afe48ac750 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/react/src/App.tsx @@ -0,0 +1,35 @@ +import { useState } from 'react' +import reactLogo from './assets/react.svg' +import viteLogo from '/vite.svg' +import './App.css' + +function App() { + const [count, setCount] = useState(0) + + return ( + <> + +

    Vite + React

    +
    + +

    + Edit src/App.tsx and save to test HMR +

    +
    +

    + Click on the Vite and React logos to learn more +

    + + ) +} + +export default App diff --git a/pkgs/experimental/swarmakit/libs/react/src/assets/react.svg b/pkgs/experimental/swarmakit/libs/react/src/assets/react.svg new file mode 100644 index 0000000000..6c87de9bb3 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/react/src/assets/react.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/react/src/index.css b/pkgs/experimental/swarmakit/libs/react/src/index.css new file mode 100644 index 0000000000..6119ad9a8f --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/react/src/index.css @@ -0,0 +1,68 @@ +:root { + font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif; + line-height: 1.5; + font-weight: 400; + + color-scheme: light dark; + color: rgba(255, 255, 255, 0.87); + background-color: #242424; + + font-synthesis: none; + text-rendering: optimizeLegibility; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +a { + font-weight: 500; + color: #646cff; + text-decoration: inherit; +} +a:hover { + color: #535bf2; +} + +body { + margin: 0; + display: flex; + place-items: center; + min-width: 320px; + min-height: 100vh; +} + +h1 { + font-size: 3.2em; + line-height: 1.1; +} + +button { + border-radius: 8px; + border: 1px solid transparent; + padding: 0.6em 1.2em; + font-size: 1em; + font-weight: 500; + font-family: inherit; + background-color: #1a1a1a; + cursor: pointer; + transition: border-color 0.25s; +} +button:hover { + border-color: #646cff; +} +button:focus, +button:focus-visible { + outline: 4px auto -webkit-focus-ring-color; +} + +@media (prefers-color-scheme: light) { + :root { + color: #213547; + background-color: #ffffff; + } + a:hover { + color: #747bff; + } + button { + background-color: #f9f9f9; + } +} diff --git a/pkgs/experimental/swarmakit/libs/react/src/main.tsx b/pkgs/experimental/swarmakit/libs/react/src/main.tsx new file mode 100644 index 0000000000..6f4ac9bcca --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/react/src/main.tsx @@ -0,0 +1,10 @@ +import { StrictMode } from 'react' +import { createRoot } from 'react-dom/client' +import App from './App.tsx' +import './index.css' + +createRoot(document.getElementById('root')!).render( + + + , +) diff --git a/pkgs/experimental/swarmakit/libs/react/src/stories/Button.stories.ts b/pkgs/experimental/swarmakit/libs/react/src/stories/Button.stories.ts new file mode 100644 index 0000000000..2a05e01b06 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/react/src/stories/Button.stories.ts @@ -0,0 +1,53 @@ +import type { Meta, StoryObj } from '@storybook/react'; +import { fn } from '@storybook/test'; + +import { Button } from './Button'; + +// More on how to set up stories at: https://storybook.js.org/docs/writing-stories#default-export +const meta = { + title: 'Example/Button', + component: Button, + parameters: { + // Optional parameter to center the component in the Canvas. More info: https://storybook.js.org/docs/configure/story-layout + layout: 'centered', + }, + // This component will have an automatically generated Autodocs entry: https://storybook.js.org/docs/writing-docs/autodocs + tags: ['autodocs'], + // More on argTypes: https://storybook.js.org/docs/api/argtypes + argTypes: { + backgroundColor: { control: 'color' }, + }, + // Use `fn` to spy on the onClick arg, which will appear in the actions panel once invoked: https://storybook.js.org/docs/essentials/actions#action-args + args: { onClick: fn() }, +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +// More on writing stories with args: https://storybook.js.org/docs/writing-stories/args +export const Primary: Story = { + args: { + primary: true, + label: 'Button', + }, +}; + +export const Secondary: Story = { + args: { + label: 'Button', + }, +}; + +export const Large: Story = { + args: { + size: 'large', + label: 'Button', + }, +}; + +export const Small: Story = { + args: { + size: 'small', + label: 'Button', + }, +}; diff --git a/pkgs/experimental/swarmakit/libs/react/src/stories/Button.tsx b/pkgs/experimental/swarmakit/libs/react/src/stories/Button.tsx new file mode 100644 index 0000000000..d055c5d065 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/react/src/stories/Button.tsx @@ -0,0 +1,35 @@ +import './button.css'; + +export interface ButtonProps { + /** Is this the principal call to action on the page? */ + primary?: boolean; + /** What background color to use */ + backgroundColor?: string; + /** How large should the button be? */ + size?: 'small' | 'medium' | 'large'; + /** Button contents */ + label: string; + /** Optional click handler */ + onClick?: () => void; +} + +/** Primary UI component for user interaction */ +export const Button = ({ + primary = false, + size = 'medium', + backgroundColor, + label, + ...props +}: ButtonProps) => { + const mode = primary ? 'storybook-button--primary' : 'storybook-button--secondary'; + return ( + + ); +}; diff --git a/pkgs/experimental/swarmakit/libs/react/src/stories/Configure.mdx b/pkgs/experimental/swarmakit/libs/react/src/stories/Configure.mdx new file mode 100644 index 0000000000..6a53730492 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/react/src/stories/Configure.mdx @@ -0,0 +1,364 @@ +import { Meta } from "@storybook/blocks"; + +import Github from "./assets/github.svg"; +import Discord from "./assets/discord.svg"; +import Youtube from "./assets/youtube.svg"; +import Tutorials from "./assets/tutorials.svg"; +import Styling from "./assets/styling.png"; +import Context from "./assets/context.png"; +import Assets from "./assets/assets.png"; +import Docs from "./assets/docs.png"; +import Share from "./assets/share.png"; +import FigmaPlugin from "./assets/figma-plugin.png"; +import Testing from "./assets/testing.png"; +import Accessibility from "./assets/accessibility.png"; +import Theming from "./assets/theming.png"; +import AddonLibrary from "./assets/addon-library.png"; + +export const RightArrow = () => + + + + + +
    +
    + # Configure your project + + Because Storybook works separately from your app, you'll need to configure it for your specific stack and setup. Below, explore guides for configuring Storybook with popular frameworks and tools. If you get stuck, learn how you can ask for help from our community. +
    +
    +
    + A wall of logos representing different styling technologies +

    Add styling and CSS

    +

    Like with web applications, there are many ways to include CSS within Storybook. Learn more about setting up styling within Storybook.

    + Learn more +
    +
    + An abstraction representing the composition of data for a component +

    Provide context and mocking

    +

    Often when a story doesn't render, it's because your component is expecting a specific environment or context (like a theme provider) to be available.

    + Learn more +
    +
    + A representation of typography and image assets +
    +

    Load assets and resources

    +

    To link static files (like fonts) to your projects and stories, use the + `staticDirs` configuration option to specify folders to load when + starting Storybook.

    + Learn more +
    +
    +
    +
    +
    +
    + # Do more with Storybook + + Now that you know the basics, let's explore other parts of Storybook that will improve your experience. This list is just to get you started. You can customise Storybook in many ways to fit your needs. +
    + +
    +
    +
    + A screenshot showing the autodocs tag being set, pointing a docs page being generated +

    Autodocs

    +

    Auto-generate living, + interactive reference documentation from your components and stories.

    + Learn more +
    +
    + A browser window showing a Storybook being published to a chromatic.com URL +

    Publish to Chromatic

    +

    Publish your Storybook to review and collaborate with your entire team.

    + Learn more +
    +
    + Windows showing the Storybook plugin in Figma +

    Figma Plugin

    +

    Embed your stories into Figma to cross-reference the design and live + implementation in one place.

    + Learn more +
    +
    + Screenshot of tests passing and failing +

    Testing

    +

    Use stories to test a component in all its variations, no matter how + complex.

    + Learn more +
    +
    + Screenshot of accessibility tests passing and failing +

    Accessibility

    +

    Automatically test your components for a11y issues as you develop.

    + Learn more +
    +
    + Screenshot of Storybook in light and dark mode +

    Theming

    +

    Theme Storybook's UI to personalize it to your project.

    + Learn more +
    +
    +
    +
    +
    +
    +

    Addons

    +

    Integrate your tools with Storybook to connect workflows.

    + Discover all addons +
    +
    + Integrate your tools with Storybook to connect workflows. +
    +
    + +
    +
    + Github logo + Join our contributors building the future of UI development. + + Star on GitHub +
    +
    + Discord logo +
    + Get support and chat with frontend developers. + + Join Discord server +
    +
    +
    + Youtube logo +
    + Watch tutorials, feature previews and interviews. + + Watch on YouTube +
    +
    +
    + A book +

    Follow guided walkthroughs on for key workflows.

    + + Discover tutorials +
    +
    + + diff --git a/pkgs/experimental/swarmakit/libs/react/src/stories/Header.stories.ts b/pkgs/experimental/swarmakit/libs/react/src/stories/Header.stories.ts new file mode 100644 index 0000000000..80c71d0f52 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/react/src/stories/Header.stories.ts @@ -0,0 +1,33 @@ +import type { Meta, StoryObj } from '@storybook/react'; +import { fn } from '@storybook/test'; + +import { Header } from './Header'; + +const meta = { + title: 'Example/Header', + component: Header, + // This component will have an automatically generated Autodocs entry: https://storybook.js.org/docs/writing-docs/autodocs + tags: ['autodocs'], + parameters: { + // More on how to position stories at: https://storybook.js.org/docs/configure/story-layout + layout: 'fullscreen', + }, + args: { + onLogin: fn(), + onLogout: fn(), + onCreateAccount: fn(), + }, +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +export const LoggedIn: Story = { + args: { + user: { + name: 'Jane Doe', + }, + }, +}; + +export const LoggedOut: Story = {}; diff --git a/pkgs/experimental/swarmakit/libs/react/src/stories/Header.tsx b/pkgs/experimental/swarmakit/libs/react/src/stories/Header.tsx new file mode 100644 index 0000000000..d05ed4f678 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/react/src/stories/Header.tsx @@ -0,0 +1,54 @@ +import { Button } from './Button'; +import './header.css'; + +type User = { + name: string; +}; + +export interface HeaderProps { + user?: User; + onLogin?: () => void; + onLogout?: () => void; + onCreateAccount?: () => void; +} + +export const Header = ({ user, onLogin, onLogout, onCreateAccount }: HeaderProps) => ( +
    +
    +
    + + + + + + + +

    Acme

    +
    +
    + {user ? ( + <> + + Welcome, {user.name}! + +
    +
    +
    +); diff --git a/pkgs/experimental/swarmakit/libs/react/src/stories/Page.stories.ts b/pkgs/experimental/swarmakit/libs/react/src/stories/Page.stories.ts new file mode 100644 index 0000000000..53b9f8fdf9 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/react/src/stories/Page.stories.ts @@ -0,0 +1,32 @@ +import type { Meta, StoryObj } from '@storybook/react'; +import { expect, userEvent, within } from '@storybook/test'; + +import { Page } from './Page'; + +const meta = { + title: 'Example/Page', + component: Page, + parameters: { + // More on how to position stories at: https://storybook.js.org/docs/configure/story-layout + layout: 'fullscreen', + }, +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +export const LoggedOut: Story = {}; + +// More on interaction testing: https://storybook.js.org/docs/writing-tests/interaction-testing +export const LoggedIn: Story = { + play: async ({ canvasElement }) => { + const canvas = within(canvasElement); + const loginButton = canvas.getByRole('button', { name: /Log in/i }); + await expect(loginButton).toBeInTheDocument(); + await userEvent.click(loginButton); + await expect(loginButton).not.toBeInTheDocument(); + + const logoutButton = canvas.getByRole('button', { name: /Log out/i }); + await expect(logoutButton).toBeInTheDocument(); + }, +}; diff --git a/pkgs/experimental/swarmakit/libs/react/src/stories/Page.tsx b/pkgs/experimental/swarmakit/libs/react/src/stories/Page.tsx new file mode 100644 index 0000000000..e117483013 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/react/src/stories/Page.tsx @@ -0,0 +1,73 @@ +import React from 'react'; + +import { Header } from './Header'; +import './page.css'; + +type User = { + name: string; +}; + +export const Page: React.FC = () => { + const [user, setUser] = React.useState(); + + return ( +
    +
    setUser({ name: 'Jane Doe' })} + onLogout={() => setUser(undefined)} + onCreateAccount={() => setUser({ name: 'Jane Doe' })} + /> + +
    +

    Pages in Storybook

    +

    + We recommend building UIs with a{' '} + + component-driven + {' '} + process starting with atomic components and ending with pages. +

    +

    + Render pages with mock data. This makes it easy to build and review page states without + needing to navigate to them in your app. Here are some handy patterns for managing page + data in Storybook: +

    +
      +
    • + Use a higher-level connected component. Storybook helps you compose such data from the + "args" of child component stories +
    • +
    • + Assemble data in the page component from your services. You can mock these services out + using Storybook. +
    • +
    +

    + Get a guided tutorial on component-driven development at{' '} + + Storybook tutorials + + . Read more in the{' '} + + docs + + . +

    +
    + Tip Adjust the width of the canvas with the{' '} + + + + + + Viewports addon in the toolbar +
    +
    +
    + ); +}; diff --git a/pkgs/experimental/swarmakit/libs/react/src/stories/assets/accessibility.png b/pkgs/experimental/swarmakit/libs/react/src/stories/assets/accessibility.png new file mode 100644 index 0000000000..6ffe6feabd Binary files /dev/null and b/pkgs/experimental/swarmakit/libs/react/src/stories/assets/accessibility.png differ diff --git a/pkgs/experimental/swarmakit/libs/react/src/stories/assets/accessibility.svg b/pkgs/experimental/swarmakit/libs/react/src/stories/assets/accessibility.svg new file mode 100644 index 0000000000..107e93f838 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/react/src/stories/assets/accessibility.svg @@ -0,0 +1 @@ +Accessibility \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/react/src/stories/assets/addon-library.png b/pkgs/experimental/swarmakit/libs/react/src/stories/assets/addon-library.png new file mode 100644 index 0000000000..95deb38a88 Binary files /dev/null and b/pkgs/experimental/swarmakit/libs/react/src/stories/assets/addon-library.png differ diff --git a/pkgs/experimental/swarmakit/libs/react/src/stories/assets/assets.png b/pkgs/experimental/swarmakit/libs/react/src/stories/assets/assets.png new file mode 100644 index 0000000000..cfba6817ae Binary files /dev/null and b/pkgs/experimental/swarmakit/libs/react/src/stories/assets/assets.png differ diff --git a/pkgs/experimental/swarmakit/libs/react/src/stories/assets/avif-test-image.avif b/pkgs/experimental/swarmakit/libs/react/src/stories/assets/avif-test-image.avif new file mode 100644 index 0000000000..530709bc12 Binary files /dev/null and b/pkgs/experimental/swarmakit/libs/react/src/stories/assets/avif-test-image.avif differ diff --git a/pkgs/experimental/swarmakit/libs/react/src/stories/assets/context.png b/pkgs/experimental/swarmakit/libs/react/src/stories/assets/context.png new file mode 100644 index 0000000000..e5cd249a2d Binary files /dev/null and b/pkgs/experimental/swarmakit/libs/react/src/stories/assets/context.png differ diff --git a/pkgs/experimental/swarmakit/libs/react/src/stories/assets/discord.svg b/pkgs/experimental/swarmakit/libs/react/src/stories/assets/discord.svg new file mode 100644 index 0000000000..d638958b66 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/react/src/stories/assets/discord.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/react/src/stories/assets/docs.png b/pkgs/experimental/swarmakit/libs/react/src/stories/assets/docs.png new file mode 100644 index 0000000000..a749629df9 Binary files /dev/null and b/pkgs/experimental/swarmakit/libs/react/src/stories/assets/docs.png differ diff --git a/pkgs/experimental/swarmakit/libs/react/src/stories/assets/figma-plugin.png b/pkgs/experimental/swarmakit/libs/react/src/stories/assets/figma-plugin.png new file mode 100644 index 0000000000..8f79b08cdf Binary files /dev/null and b/pkgs/experimental/swarmakit/libs/react/src/stories/assets/figma-plugin.png differ diff --git a/pkgs/experimental/swarmakit/libs/react/src/stories/assets/github.svg b/pkgs/experimental/swarmakit/libs/react/src/stories/assets/github.svg new file mode 100644 index 0000000000..dc513528ca --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/react/src/stories/assets/github.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/react/src/stories/assets/share.png b/pkgs/experimental/swarmakit/libs/react/src/stories/assets/share.png new file mode 100644 index 0000000000..8097a37077 Binary files /dev/null and b/pkgs/experimental/swarmakit/libs/react/src/stories/assets/share.png differ diff --git a/pkgs/experimental/swarmakit/libs/react/src/stories/assets/styling.png b/pkgs/experimental/swarmakit/libs/react/src/stories/assets/styling.png new file mode 100644 index 0000000000..d341e8263e Binary files /dev/null and b/pkgs/experimental/swarmakit/libs/react/src/stories/assets/styling.png differ diff --git a/pkgs/experimental/swarmakit/libs/react/src/stories/assets/testing.png b/pkgs/experimental/swarmakit/libs/react/src/stories/assets/testing.png new file mode 100644 index 0000000000..d4ac39a0ce Binary files /dev/null and b/pkgs/experimental/swarmakit/libs/react/src/stories/assets/testing.png differ diff --git a/pkgs/experimental/swarmakit/libs/react/src/stories/assets/theming.png b/pkgs/experimental/swarmakit/libs/react/src/stories/assets/theming.png new file mode 100644 index 0000000000..1535eb9b81 Binary files /dev/null and b/pkgs/experimental/swarmakit/libs/react/src/stories/assets/theming.png differ diff --git a/pkgs/experimental/swarmakit/libs/react/src/stories/assets/tutorials.svg b/pkgs/experimental/swarmakit/libs/react/src/stories/assets/tutorials.svg new file mode 100644 index 0000000000..b492a9c66f --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/react/src/stories/assets/tutorials.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/react/src/stories/assets/youtube.svg b/pkgs/experimental/swarmakit/libs/react/src/stories/assets/youtube.svg new file mode 100644 index 0000000000..a7515d7e9b --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/react/src/stories/assets/youtube.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/react/src/stories/button.css b/pkgs/experimental/swarmakit/libs/react/src/stories/button.css new file mode 100644 index 0000000000..94d674b761 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/react/src/stories/button.css @@ -0,0 +1,30 @@ +.storybook-button { + display: inline-block; + cursor: pointer; + border: 0; + border-radius: 3em; + font-weight: 700; + line-height: 1; + font-family: 'Nunito Sans', 'Helvetica Neue', Helvetica, Arial, sans-serif; +} +.storybook-button--primary { + background-color: #1ea7fd; + color: white; +} +.storybook-button--secondary { + box-shadow: rgba(0, 0, 0, 0.15) 0px 0px 0px 1px inset; + background-color: transparent; + color: #333; +} +.storybook-button--small { + padding: 10px 16px; + font-size: 12px; +} +.storybook-button--medium { + padding: 11px 20px; + font-size: 14px; +} +.storybook-button--large { + padding: 12px 24px; + font-size: 16px; +} diff --git a/pkgs/experimental/swarmakit/libs/react/src/stories/header.css b/pkgs/experimental/swarmakit/libs/react/src/stories/header.css new file mode 100644 index 0000000000..5efd46c26a --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/react/src/stories/header.css @@ -0,0 +1,32 @@ +.storybook-header { + display: flex; + justify-content: space-between; + align-items: center; + border-bottom: 1px solid rgba(0, 0, 0, 0.1); + padding: 15px 20px; + font-family: 'Nunito Sans', 'Helvetica Neue', Helvetica, Arial, sans-serif; +} + +.storybook-header svg { + display: inline-block; + vertical-align: top; +} + +.storybook-header h1 { + display: inline-block; + vertical-align: top; + margin: 6px 0 6px 10px; + font-weight: 700; + font-size: 20px; + line-height: 1; +} + +.storybook-header button + button { + margin-left: 10px; +} + +.storybook-header .welcome { + margin-right: 10px; + color: #333; + font-size: 14px; +} diff --git a/pkgs/experimental/swarmakit/libs/react/src/stories/page.css b/pkgs/experimental/swarmakit/libs/react/src/stories/page.css new file mode 100644 index 0000000000..87f7ecb17c --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/react/src/stories/page.css @@ -0,0 +1,69 @@ +.storybook-page { + margin: 0 auto; + padding: 48px 20px; + max-width: 600px; + color: #333; + font-size: 14px; + line-height: 24px; + font-family: 'Nunito Sans', 'Helvetica Neue', Helvetica, Arial, sans-serif; +} + +.storybook-page h2 { + display: inline-block; + vertical-align: top; + margin: 0 0 4px; + font-weight: 700; + font-size: 32px; + line-height: 1; +} + +.storybook-page p { + margin: 1em 0; +} + +.storybook-page a { + color: #1ea7fd; + text-decoration: none; +} + +.storybook-page ul { + margin: 1em 0; + padding-left: 30px; +} + +.storybook-page li { + margin-bottom: 8px; +} + +.storybook-page .tip { + display: inline-block; + vertical-align: top; + margin-right: 10px; + border-radius: 1em; + background: #e7fdd8; + padding: 4px 12px; + color: #66bf3c; + font-weight: 700; + font-size: 11px; + line-height: 12px; +} + +.storybook-page .tip-wrapper { + margin-top: 40px; + margin-bottom: 40px; + font-size: 13px; + line-height: 20px; +} + +.storybook-page .tip-wrapper svg { + display: inline-block; + vertical-align: top; + margin-top: 3px; + margin-right: 4px; + width: 12px; + height: 12px; +} + +.storybook-page .tip-wrapper svg path { + fill: #1ea7fd; +} diff --git a/pkgs/experimental/swarmakit/libs/react/src/vite-env.d.ts b/pkgs/experimental/swarmakit/libs/react/src/vite-env.d.ts new file mode 100644 index 0000000000..11f02fe2a0 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/react/src/vite-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/pkgs/experimental/swarmakit/libs/react/tsconfig.app.json b/pkgs/experimental/swarmakit/libs/react/tsconfig.app.json new file mode 100644 index 0000000000..f0a235055d --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/react/tsconfig.app.json @@ -0,0 +1,24 @@ +{ + "compilerOptions": { + "target": "ES2020", + "useDefineForClassFields": true, + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "module": "ESNext", + "skipLibCheck": true, + + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "isolatedModules": true, + "moduleDetection": "force", + "noEmit": true, + "jsx": "react-jsx", + + /* Linting */ + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true + }, + "include": ["src"] +} diff --git a/pkgs/experimental/swarmakit/libs/react/tsconfig.json b/pkgs/experimental/swarmakit/libs/react/tsconfig.json new file mode 100644 index 0000000000..1ffef600d9 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/react/tsconfig.json @@ -0,0 +1,7 @@ +{ + "files": [], + "references": [ + { "path": "./tsconfig.app.json" }, + { "path": "./tsconfig.node.json" } + ] +} diff --git a/pkgs/experimental/swarmakit/libs/react/tsconfig.node.json b/pkgs/experimental/swarmakit/libs/react/tsconfig.node.json new file mode 100644 index 0000000000..0d3d71446a --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/react/tsconfig.node.json @@ -0,0 +1,22 @@ +{ + "compilerOptions": { + "target": "ES2022", + "lib": ["ES2023"], + "module": "ESNext", + "skipLibCheck": true, + + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "isolatedModules": true, + "moduleDetection": "force", + "noEmit": true, + + /* Linting */ + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true + }, + "include": ["vite.config.ts"] +} diff --git a/pkgs/experimental/swarmakit/libs/react/vite.config.ts b/pkgs/experimental/swarmakit/libs/react/vite.config.ts new file mode 100644 index 0000000000..5a33944a9b --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/react/vite.config.ts @@ -0,0 +1,7 @@ +import { defineConfig } from 'vite' +import react from '@vitejs/plugin-react' + +// https://vitejs.dev/config/ +export default defineConfig({ + plugins: [react()], +}) diff --git a/pkgs/experimental/swarmakit/libs/svelte/.gitignore b/pkgs/experimental/swarmakit/libs/svelte/.gitignore new file mode 100644 index 0000000000..f940a995df --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/svelte/.gitignore @@ -0,0 +1,26 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? + +*storybook.log diff --git a/pkgs/experimental/swarmakit/libs/svelte/.storybook/main.ts b/pkgs/experimental/swarmakit/libs/svelte/.storybook/main.ts new file mode 100644 index 0000000000..99dbeb4599 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/svelte/.storybook/main.ts @@ -0,0 +1,25 @@ +import type { StorybookConfig } from "@storybook/svelte-vite"; + +import { join, dirname } from "path"; + +/** + * This function is used to resolve the absolute path of a package. + * It is needed in projects that use Yarn PnP or are set up within a monorepo. + */ +function getAbsolutePath(value: string): any { + return dirname(require.resolve(join(value, "package.json"))); +} +const config: StorybookConfig = { + stories: ["../src/**/*.mdx", "../src/**/*.stories.@(js|ts|svelte)"], + addons: [ + getAbsolutePath("@storybook/addon-svelte-csf"), + getAbsolutePath("@storybook/addon-essentials"), + getAbsolutePath("@chromatic-com/storybook"), + getAbsolutePath("@storybook/addon-interactions"), + ], + framework: { + name: getAbsolutePath("@storybook/svelte-vite"), + options: {}, + }, +}; +export default config; diff --git a/pkgs/experimental/swarmakit/libs/svelte/.storybook/preview.ts b/pkgs/experimental/swarmakit/libs/svelte/.storybook/preview.ts new file mode 100644 index 0000000000..b09bb06ffd --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/svelte/.storybook/preview.ts @@ -0,0 +1,14 @@ +import type { Preview } from "@storybook/svelte"; + +const preview: Preview = { + parameters: { + controls: { + matchers: { + color: /(background|color)$/i, + date: /Date$/i, + }, + }, + }, +}; + +export default preview; diff --git a/pkgs/experimental/swarmakit/libs/svelte/.vscode/extensions.json b/pkgs/experimental/swarmakit/libs/svelte/.vscode/extensions.json new file mode 100644 index 0000000000..bdef820151 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/svelte/.vscode/extensions.json @@ -0,0 +1,3 @@ +{ + "recommendations": ["svelte.svelte-vscode"] +} diff --git a/pkgs/experimental/swarmakit/libs/svelte/README.md b/pkgs/experimental/swarmakit/libs/svelte/README.md new file mode 100644 index 0000000000..c4e3666890 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/svelte/README.md @@ -0,0 +1,81 @@ +# Svelte + TypeScript + Vite + +
    + +[![Hits](https://hits.seeyoufarm.com/api/count/incr/badge.svg?url=https%3A%2F%2Fgithub.com%2Fswarmauri%2Fswarmakit%2Ftree%2Fmaster%2Flibs%2Fsvelte&count_bg=%2379C83D&title_bg=%23555555&icon=&icon_color=%23E7E7E7&title=hits&edge_flat=false)](https://hits.seeyoufarm.com) +![NPM Version](https://img.shields.io/npm/v/%40swarmakit%2Fsvelte) +![npm downloads](https://img.shields.io/npm/dt/@swarmakit/svelte.svg) +[![License](https://img.shields.io/badge/License-Apache_2.0-blue.svg)](https://opensource.org/licenses/Apache-2.0) +[![Build and Publish Monorepo](https://github.com/swarmauri/swarmakit/actions/workflows/publish.yml/badge.svg)](https://github.com/swarmauri/swarmakit/actions/workflows/publish.yml) + +
    + +
    + +![Static Badge](https://img.shields.io/badge/Svelte-FF3E00?style=for-the-badge&logo=svelte&labelColor=black) +![Static Badge](https://img.shields.io/badge/TypeScript-1D4ED8?style=for-the-badge&logo=typescript&labelColor=black) + +
    + +This template should help get you started developing with Svelte and TypeScript in Vite. + +Everything you need to build a Svelte and TypeScript project, powered by [`Svelte`](https://github.com/sveltejs/kit/tree/main/packages/create-svelte). + +## Installation + +Install the `@swarmakit/svelte` library through npm: + +```bash +npm install @swarmakit/svelte +``` + +### Prerequisites + +Node.js and npm should be installed. You can verify installation with: + +```bash +node -v +npm -v +```` + +### Setting Up a Vite + Svelte Project (If you haven't already) + +To initialize a Vite project for Svelte with Typescript, run: + +```bash +npm create vite@latest my-svelte-app -- --template svelte-ts +``` + +replacing `my-svelte-app` with your project name. + +Then, navigate to your project folder: + +```bash +cd my-svelte-app +``` + +### Importing Components and Basic Usage in Svelte + +1. **Import Components:** To use a component in your Svelte files, import it from the `@swarmakit/svelte` library as shown below: + + ```html + + ``` + +2. **Example Usage in a Svelte File:** Use the imported component within your Svelte file: + + ```html + + +
    + +
    + ``` + +> **Available Components:** Swarmakit Sveltekit includes a vast library of components. See the full list in the [components folder on GitHub](https://github.com/swarmauri/swarmakit/tree/master/libs/svelte/src/components). diff --git a/pkgs/experimental/swarmakit/libs/svelte/generate-index.cjs b/pkgs/experimental/swarmakit/libs/svelte/generate-index.cjs new file mode 100644 index 0000000000..17d6ceadca --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/svelte/generate-index.cjs @@ -0,0 +1,39 @@ +// generate-index.js + +const fs = require('fs'); +const path = require('path'); + +// Path to the components directory +const componentsDir = path.join(__dirname, 'src', 'components'); +const indexFilePath = path.join(__dirname, 'src', 'index.ts'); + +// Helper function to recursively find all .svelte files in componentsDir +function findSvelteFiles(dir) { + let svelteFiles = []; + const files = fs.readdirSync(dir); + + files.forEach(file => { + const filePath = path.join(dir, file); + const stat = fs.statSync(filePath); + + if (stat.isDirectory()) { + svelteFiles = svelteFiles.concat(findSvelteFiles(filePath)); + } else if (file.endsWith('.svelte')) { + svelteFiles.push(filePath); + } + }); + + return svelteFiles; +} + +// Generate export statements for each .svelte file +const svelteFiles = findSvelteFiles(componentsDir); +const exportStatements = svelteFiles.map(filePath => { + const relativePath = path.relative(path.join(__dirname, 'src'), filePath); + const componentName = path.basename(filePath, '.svelte'); + return `export { default as ${componentName} } from './${relativePath.replace(/\\/g, '/')}';`; +}); + +// Write to index.ts +fs.writeFileSync(indexFilePath, exportStatements.join('\n'), 'utf-8'); +console.log(`Generated ${indexFilePath} with exports for ${svelteFiles.length} components.`); diff --git a/pkgs/experimental/swarmakit/libs/svelte/index.html b/pkgs/experimental/swarmakit/libs/svelte/index.html new file mode 100644 index 0000000000..a02ea470e0 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/svelte/index.html @@ -0,0 +1,13 @@ + + + + + + + Vite + Svelte + TS + + +
    + + + diff --git a/pkgs/experimental/swarmakit/libs/svelte/package.json b/pkgs/experimental/swarmakit/libs/svelte/package.json new file mode 100644 index 0000000000..fdce5c9fa1 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/svelte/package.json @@ -0,0 +1,51 @@ +{ + "name": "@swarmakit/svelte", + "private": false, + "version": "0.0.22", + "type": "module", + "main": "./dist/index.cjs.js", + "module": "./dist/index.esm.js", + "exports": { + ".": { + "require": "./dist/index.cjs.js", + "import": "./dist/index.esm.js" + } + }, + "repository": { + "type": "git", + "url": "https://github.com/swarmauri/swarmakit.git", + "directory": "libs/svelte" + }, + "scripts": { + "dev": "vite", + "build": "vite build", + "preview": "vite preview", + "check": "svelte-check --tsconfig ./tsconfig.json && tsc -p tsconfig.node.json", + "storybook": "storybook dev -p 6006", + "build-storybook": "storybook build" + }, + "devDependencies": { + "@chromatic-com/storybook": "^3.2.2", + "@storybook/addon-essentials": "^8.4.7", + "@storybook/addon-interactions": "^8.4.7", + "@storybook/addon-svelte-csf": "^5.0.0-next.13", + "@storybook/blocks": "^8.4.7", + "@storybook/svelte": "^8.4.7", + "@storybook/svelte-vite": "^8.4.7", + "@storybook/test": "^8.4.7", + "@types/react": "^18.3.10", + "@types/react-dom": "^18.3.0", + "@sveltejs/vite-plugin-svelte": "^4.0.0", + "@tsconfig/svelte": "^5.0.4", + "storybook": "^8.4.7", + "svelte": "^5.1.3", + "svelte-check": "^4.0.5", + "tslib": "^2.8.0", + "typescript": "~5.6.2", + "vite": "^5.4.10" + }, + "dependencies": { + "@types/quill": "^2.0.14", + "quill": "^2.0.3" + } +} diff --git a/pkgs/experimental/swarmakit/libs/svelte/public/vite.svg b/pkgs/experimental/swarmakit/libs/svelte/public/vite.svg new file mode 100644 index 0000000000..e7b8dfb1b2 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/svelte/public/vite.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/svelte/rollup.config.js b/pkgs/experimental/swarmakit/libs/svelte/rollup.config.js new file mode 100644 index 0000000000..cf4175c98a --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/svelte/rollup.config.js @@ -0,0 +1,29 @@ +// rollup.config.js + +import svelte from 'rollup-plugin-svelte'; +import resolve from '@rollup/plugin-node-resolve'; +import commonjs from '@rollup/plugin-commonjs'; +import typescript from '@rollup/plugin-typescript'; +import pkg from './package.json'; + +export default { + input: 'src/index.ts', // Entry point for the library + output: [ + { + file: pkg.module, + format: 'es', + sourcemap: true + }, + { + file: pkg.main, + format: 'cjs', + sourcemap: true + } + ], + plugins: [ + svelte({ emitCss: true }), + resolve(), + commonjs(), + typescript({ sourceMap: true, declaration: true, outDir: 'dist' }) + ] +}; diff --git a/pkgs/experimental/swarmakit/libs/svelte/src/assets/svelte.svg b/pkgs/experimental/swarmakit/libs/svelte/src/assets/svelte.svg new file mode 100644 index 0000000000..c5e08481f8 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/svelte/src/assets/svelte.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/svelte/src/components/360-DegreeImageViewer/360-DegreeImageViewer.css b/pkgs/experimental/swarmakit/libs/svelte/src/components/360-DegreeImageViewer/360-DegreeImageViewer.css new file mode 100644 index 0000000000..ea5444fd0e --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/svelte/src/components/360-DegreeImageViewer/360-DegreeImageViewer.css @@ -0,0 +1,21 @@ +.viewer-container { + width: 100%; + height: 400px; + display: flex; + align-items: center; + justify-content: center; + background-color: #f3f3f3; + overflow: hidden; + position: relative; +} + +.viewer-container img { + max-width: 100%; + max-height: 100%; + transition: transform 0.2s ease-in-out; +} + +.loading { + font-size: 1.5em; + color: #888; +} \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/svelte/src/components/360-DegreeImageViewer/360-DegreeImageViewer.stories.ts b/pkgs/experimental/swarmakit/libs/svelte/src/components/360-DegreeImageViewer/360-DegreeImageViewer.stories.ts new file mode 100644 index 0000000000..c48e294ba7 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/svelte/src/components/360-DegreeImageViewer/360-DegreeImageViewer.stories.ts @@ -0,0 +1,71 @@ +import ThreeSixtyDegreeImageViewer from './ThreeSixtyDegreeImageViewer.svelte'; +import type { Meta, StoryFn } from '@storybook/svelte'; + +const meta: Meta = { + title: 'Components/Media/360-DegreeImageViewer', + component: ThreeSixtyDegreeImageViewer, + tags: ['autodocs'], + argTypes: { + imageUrls: { + control: { type: 'object' }, + }, + isLoading: { + control: { type: 'boolean' }, + }, + isRotating: { + control: { type: 'boolean' }, + }, + isZoomed: { + control: { type: 'boolean' }, + }, + }, +}; + +export default meta; + +const Template: StoryFn = (args) => ({ + Component: ThreeSixtyDegreeImageViewer, + props: args, +}); + +const sampleImages = Array.from({ length: 36 }, (_, i) => `path/to/image_${i + 1}.jpg`); + +export const Default = Template.bind({}); +Default.args = { + imageUrls: sampleImages, + isLoading: false, + isRotating: false, + isZoomed: false, +}; + +export const Rotating = Template.bind({}); +Rotating.args = { + imageUrls: sampleImages, + isLoading: false, + isRotating: true, + isZoomed: false, +}; + +export const Paused = Template.bind({}); +Paused.args = { + imageUrls: sampleImages, + isLoading: false, + isRotating: false, + isZoomed: false, +}; + +export const ZoomInOut = Template.bind({}); +ZoomInOut.args = { + imageUrls: sampleImages, + isLoading: false, + isRotating: false, + isZoomed: true, +}; + +export const Loading = Template.bind({}); +Loading.args = { + imageUrls: sampleImages, + isLoading: true, + isRotating: false, + isZoomed: false, +}; \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/svelte/src/components/360-DegreeImageViewer/ThreeSixtyDegreeImageViewer.svelte b/pkgs/experimental/swarmakit/libs/svelte/src/components/360-DegreeImageViewer/ThreeSixtyDegreeImageViewer.svelte new file mode 100644 index 0000000000..8f26091822 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/svelte/src/components/360-DegreeImageViewer/ThreeSixtyDegreeImageViewer.svelte @@ -0,0 +1,46 @@ + + +
    e.key === 'Enter' && toggleZoom()}> + {#if isLoading} +
    Loading...
    + {:else} + 360-degree view + {/if} +
    + + \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/svelte/src/components/Accordion/Accordion.css b/pkgs/experimental/swarmakit/libs/svelte/src/components/Accordion/Accordion.css new file mode 100644 index 0000000000..98332d12fc --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/svelte/src/components/Accordion/Accordion.css @@ -0,0 +1,36 @@ +:root { + --accordion-header-bg: #f5f5f5; + --accordion-header-color: #333; + --accordion-header-hover-bg: #e0e0e0; + --accordion-content-bg: #fff; + --accordion-border-radius: 4px; + --accordion-transition-duration: 0.3s; +} + +.accordion { + border: 1px solid #ccc; + border-radius: var(--accordion-border-radius); + overflow: hidden; +} + +.accordion-header { + width: 100%; + background: var(--accordion-header-bg); + color: var(--accordion-header-color); + padding: 10px; + cursor: pointer; + border: none; + text-align: left; + transition: background var(--accordion-transition-duration); +} + +.accordion-header:hover { + background: var(--accordion-header-hover-bg); +} + +.accordion-content { + background: var(--accordion-content-bg); + padding: 10px; + display: block; + transition: max-height var(--accordion-transition-duration); +} \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/svelte/src/components/Accordion/Accordion.stories.ts b/pkgs/experimental/swarmakit/libs/svelte/src/components/Accordion/Accordion.stories.ts new file mode 100644 index 0000000000..bde7909fa1 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/svelte/src/components/Accordion/Accordion.stories.ts @@ -0,0 +1,62 @@ +import Accordion from './Accordion.svelte'; +import type { Meta,StoryFn } from '@storybook/svelte'; + +const meta: Meta = { + title: 'component/Lists/Accordion', + component: Accordion, + tags: ['autodocs'], + argTypes: { + isOpen: { control: 'boolean' }, + title: { control: 'text' }, + content: { control: 'text' } + }, + parameters: { + layout: 'centered', + viewport: { + viewports: { + smallMobile: { name: 'Small Mobile', styles: { width: '320px', height: '568px' } }, + largeMobile: { name: 'Large Mobile', styles: { width: '414px', height: '896px' } }, + tablet: { name: 'Tablet', styles: { width: '768px', height: '1024px' } }, + desktop: { name: 'Desktop', styles: { width: '1024px', height: '768px' } }, + } + } + } +}; + +export default meta; + +const Template:StoryFn = (args) => ({ + Component: Accordion, + props:args, +}) + +export const Default = Template.bind({}); +Default.args = { + isOpen:false, + title:'Accordion Title', + content:'Accordion content goes here.' +} + +export const Open = Template.bind({}); +Open.args = { + isOpen:true, + title:'Accordion Title', + content:'Accordion content goes here.' +} + +export const Closed = Template.bind({}); +Closed.args = { + isOpen:false, + title:'Accordion Title', + content:'Accordion content goes here.' +} + +export const Hover = Template.bind({}); +Hover.args = { + isOpen:false, + title: 'Accordion Title', + content: 'Accordion content goes here.' +}; +Hover.parameters = { + pseudo:{hover:true} +} \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/svelte/src/components/Accordion/Accordion.svelte b/pkgs/experimental/swarmakit/libs/svelte/src/components/Accordion/Accordion.svelte new file mode 100644 index 0000000000..111c023103 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/svelte/src/components/Accordion/Accordion.svelte @@ -0,0 +1,29 @@ + + +
    + + {#if isOpen} +
    + {content} +
    + {/if} +
    + + \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/svelte/src/components/ActionableList/ActionableList.css b/pkgs/experimental/swarmakit/libs/svelte/src/components/ActionableList/ActionableList.css new file mode 100644 index 0000000000..7fb38d49a9 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/svelte/src/components/ActionableList/ActionableList.css @@ -0,0 +1,45 @@ +:root { + --list-bg: #f9f9f9; + --list-item-hover-bg: #e0e0e0; + --button-bg: #ffffff; + --button-disabled-bg: #cccccc; + --loading-text: #666666; +} + +.actionable-list { + background: var(--list-bg); + border-radius: 4px; + padding: 10px; +} + +ul { + list-style-type: none; + padding: 0; +} + +li { + margin-bottom: 5px; +} + +button { + background: var(--button-bg); + border: 1px solid #ccc; + padding: 10px; + width: 100%; + cursor: pointer; + border-radius: 4px; +} + +button:hover { + background: var(--list-item-hover-bg); +} + +button:disabled { + background: var(--button-disabled-bg); + cursor: not-allowed; +} + +.loading { + text-align: center; + color: var(--loading-text); +} \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/svelte/src/components/ActionableList/ActionableList.stories.ts b/pkgs/experimental/swarmakit/libs/svelte/src/components/ActionableList/ActionableList.stories.ts new file mode 100644 index 0000000000..a4ae351beb --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/svelte/src/components/ActionableList/ActionableList.stories.ts @@ -0,0 +1,83 @@ +import ActionableList from './ActionableList.svelte'; +import type { Meta,StoryFn } from '@storybook/svelte'; + +const meta: Meta = { + title: 'component/Lists/ActionableList', + component: ActionableList, + tags: ['autodocs'], + argTypes: { + items: { control: 'object' }, + loading: { control: 'boolean' } + }, + parameters: { + layout: 'centered', + viewport: { + viewports: { + smallMobile: { name: 'Small Mobile', styles: { width: '320px', height: '568px' } }, + largeMobile: { name: 'Large Mobile', styles: { width: '414px', height: '896px' } }, + tablet: { name: 'Tablet', styles: { width: '768px', height: '1024px' } }, + desktop: { name: 'Desktop', styles: { width: '1024px', height: '768px' } }, + } + } + } +}; + +export default meta; + +const Template:StoryFn = (args) => ({ + Component:ActionableList, + props:args, +}); + +export const Default = Template.bind({}); +Default.args = { + items:[ + {id:1, text:'Item 1', action:()=> alert('Action 1 triggered')}, + {id:2, text:'Item 2', action:()=> alert('Action 2 triggered')}, + {id:3, text:'Item 3', action:()=> alert('Action 3 triggered')}, + ], + loading:false, +}; + +export const Hover = Template.bind({}); +Hover.args = { + items:[ + {id:1, text:'Item 1', action:()=> alert('Action 1 triggered')}, + {id:2, text:'Item 2', action:()=> alert('Action 2 triggered')}, + {id:3, text:'Item 3', action:()=> alert('Action 3 triggered')}, + ], + loading:false, +}; +Hover.parameters = { + pseudo: {hover:true}, +} + +export const ActionTriggered = Template.bind({}); +ActionTriggered.args = { + items:[ + {id:1, text:'Item 1', action:()=> alert('Action 1 triggered')}, + {id:2, text:'Item 2', action:()=> alert('Action 2 triggered')}, + {id:3, text:'Item 3', action:()=> alert('Action 3 triggered')}, + ], + loading:false, +} + +export const Disabled = Template.bind({}); +Disabled.args = { + items:[ + {id:1, text:'Item 1', action:()=> alert('Action 1 triggered'),disabled:true}, + {id:2, text:'Item 2', action:()=> alert('Action 2 triggered'),disabled:true}, + {id:3, text:'Item 3', action:()=> alert('Action 3 triggered'),disabled:true}, + ], + loading:false, +}; + +export const Loading = Template.bind({}); +Loading.args = { + items:[ + {id:1, text:'Item 1', action:()=> alert('Action 1 triggered')}, + {id:2, text:'Item 2', action:()=> alert('Action 2 triggered')}, + {id:3, text:'Item 3', action:()=> alert('Action 3 triggered')}, + ], + loading:false, +} \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/svelte/src/components/ActionableList/ActionableList.svelte b/pkgs/experimental/swarmakit/libs/svelte/src/components/ActionableList/ActionableList.svelte new file mode 100644 index 0000000000..2f73c6f26a --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/svelte/src/components/ActionableList/ActionableList.svelte @@ -0,0 +1,34 @@ + + +
    + {#if loading} +
    Loading...
    + {:else} +
      + {#each items as item (item.id)} +
    • + +
    • + {/each} +
    + {/if} +
    + + \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/svelte/src/components/ActivityIndicators/ActivityIndicators.css b/pkgs/experimental/swarmakit/libs/svelte/src/components/ActivityIndicators/ActivityIndicators.css new file mode 100644 index 0000000000..0339875516 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/svelte/src/components/ActivityIndicators/ActivityIndicators.css @@ -0,0 +1,32 @@ +:root { + --loading-bg-color: #e0e0e0; + --success-bg-color: #d4edda; + --error-bg-color: #f8d7da; + --loading-text-color: #6c757d; + --success-text-color: #155724; + --error-text-color: #721c24; + --padding: 1rem; + --border-radius: 4px; +} + +.activity-indicator { + padding: var(--padding); + border-radius: var(--border-radius); + margin: 0.5rem 0; + display: inline-block; +} + +.activity-indicator.loading { + background-color: var(--loading-bg-color); + color: var(--loading-text-color); +} + +.activity-indicator.success { + background-color: var(--success-bg-color); + color: var(--success-text-color); +} + +.activity-indicator.error { + background-color: var(--error-bg-color); + color: var(--error-text-color); +} \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/svelte/src/components/ActivityIndicators/ActivityIndicators.stories.ts b/pkgs/experimental/swarmakit/libs/svelte/src/components/ActivityIndicators/ActivityIndicators.stories.ts new file mode 100644 index 0000000000..2e35ff8e09 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/svelte/src/components/ActivityIndicators/ActivityIndicators.stories.ts @@ -0,0 +1,52 @@ +import ActivityIndicators from './ActivityIndicators.svelte'; +import type { Meta,StoryFn } from '@storybook/svelte'; + +const meta: Meta = { + title: 'component/Indicators/ActivityIndicators', + component: ActivityIndicators, + tags: ['autodocs'], + argTypes: { + state: { + control: { type: 'select' }, + options: ['loading', 'success', 'error'] + }, + }, + parameters: { + layout: 'fullscreen', + viewport: { + viewports: { + smallMobile: { name: 'Small Mobile', styles: { width: '320px', height: '568px' } }, + largeMobile: { name: 'Large Mobile', styles: { width: '414px', height: '896px' } }, + tablet: { name: 'Tablet', styles: { width: '768px', height: '1024px' } }, + desktop: { name: 'Desktop', styles: { width: '1024px', height: '768px' } }, + } + } + } +}; + +export default meta; + +const Template:StoryFn = (args) => ({ + Component: ActivityIndicators, + props:args, +}); + +export const Default = Template.bind({}); +Default.args = { + state: 'loading', +}; + +export const Loading = Template.bind({}); +Loading.args = { + state:'loading', +}; + +export const Success = Template.bind({}); +Success.args = { + state:'success', +}; + +export const Error = Template.bind({}); +Error.args = { + state:'error', +}; \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/svelte/src/components/ActivityIndicators/ActivityIndicators.svelte b/pkgs/experimental/swarmakit/libs/svelte/src/components/ActivityIndicators/ActivityIndicators.svelte new file mode 100644 index 0000000000..048f572f75 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/svelte/src/components/ActivityIndicators/ActivityIndicators.svelte @@ -0,0 +1,19 @@ + + +
    + {#if state === 'loading'} + Loading... + {:else if state === 'success'} + Success! + {:else if state === 'error'} + Error occurred! + {/if} +
    + + \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/svelte/src/components/AudioPlayer/AudioPlayer.css b/pkgs/experimental/swarmakit/libs/svelte/src/components/AudioPlayer/AudioPlayer.css new file mode 100644 index 0000000000..8d9ee0c740 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/svelte/src/components/AudioPlayer/AudioPlayer.css @@ -0,0 +1,14 @@ +.audio-player { + display: flex; + align-items: center; + gap: 10px; +} + +button { + padding: 5px 10px; + cursor: pointer; +} + +input[type="range"] { + width: 100px; +} \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/svelte/src/components/AudioPlayer/AudioPlayer.stories.ts b/pkgs/experimental/swarmakit/libs/svelte/src/components/AudioPlayer/AudioPlayer.stories.ts new file mode 100644 index 0000000000..0dc75e5720 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/svelte/src/components/AudioPlayer/AudioPlayer.stories.ts @@ -0,0 +1,69 @@ +import AudioPlayer from './AudioPlayer.svelte'; +import type { Meta, StoryFn } from '@storybook/svelte'; + +const meta: Meta = { + title: 'Components/Media/AudioPlayer', + component: AudioPlayer, + tags: ['autodocs'], + argTypes: { + src: { + control: { type: 'text' }, + }, + isPlaying: { + control: { type: 'boolean' }, + }, + isMuted: { + control: { type: 'boolean' }, + }, + volume: { + control: { type: 'number' }, + }, + }, +}; + +export default meta; + +const Template: StoryFn = (args) => ({ + Component: AudioPlayer, + props: args, +}); + +export const Default = Template.bind({}); +Default.args = { + src: 'path/to/audio.mp3', + isPlaying: false, + isMuted: false, + volume: 1, +}; + +export const Play = Template.bind({}); +Play.args = { + src: 'path/to/audio.mp3', + isPlaying: true, + isMuted: false, + volume: 1, +}; + +export const Pause = Template.bind({}); +Pause.args = { + src: 'path/to/audio.mp3', + isPlaying: false, + isMuted: false, + volume: 1, +}; + +export const Mute = Template.bind({}); +Mute.args = { + src: 'path/to/audio.mp3', + isPlaying: false, + isMuted: true, + volume: 1, +}; + +export const VolumeControl = Template.bind({}); +VolumeControl.args = { + src: 'path/to/audio.mp3', + isPlaying: false, + isMuted: false, + volume: 0.5, +}; \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/svelte/src/components/AudioPlayer/AudioPlayer.svelte b/pkgs/experimental/swarmakit/libs/svelte/src/components/AudioPlayer/AudioPlayer.svelte new file mode 100644 index 0000000000..b7f6b0e778 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/svelte/src/components/AudioPlayer/AudioPlayer.svelte @@ -0,0 +1,48 @@ + + +
    + + + + +
    + + \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/svelte/src/components/AudioPlayerAdvanced/AudioPlayerAdvanced.css b/pkgs/experimental/swarmakit/libs/svelte/src/components/AudioPlayerAdvanced/AudioPlayerAdvanced.css new file mode 100644 index 0000000000..1c5f547297 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/svelte/src/components/AudioPlayerAdvanced/AudioPlayerAdvanced.css @@ -0,0 +1,14 @@ +.audio-player-advanced { + display: flex; + align-items: center; + gap: 10px; +} + +button { + padding: 5px 10px; + cursor: pointer; +} + +input[type="range"] { + width: 100px; +} \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/svelte/src/components/AudioPlayerAdvanced/AudioPlayerAdvanced.stories.ts b/pkgs/experimental/swarmakit/libs/svelte/src/components/AudioPlayerAdvanced/AudioPlayerAdvanced.stories.ts new file mode 100644 index 0000000000..9da6bbc915 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/svelte/src/components/AudioPlayerAdvanced/AudioPlayerAdvanced.stories.ts @@ -0,0 +1,95 @@ +import AudioPlayerAdvanced from './AudioPlayerAdvanced.svelte'; +import type { Meta, StoryFn } from '@storybook/svelte'; + +const meta: Meta = { + title: 'Components/Media/AudioPlayerAdvanced', + component: AudioPlayerAdvanced, + tags: ['autodocs'], + argTypes: { + src: { + control: { type: 'text' }, + }, + isPlaying: { + control: { type: 'boolean' }, + }, + isMuted: { + control: { type: 'boolean' }, + }, + volume: { + control: { type: 'number' }, + }, + playbackRate: { + control: { type: 'number' }, + }, + }, +}; + +export default meta; + +const Template: StoryFn = (args) => ({ + Component: AudioPlayerAdvanced, + props: args, +}); + +export const Default = Template.bind({}); +Default.args = { + src: 'path/to/audio.mp3', + isPlaying: false, + isMuted: false, + volume: 1, + playbackRate: 1, +}; + +export const Play = Template.bind({}); +Play.args = { + src: 'path/to/audio.mp3', + isPlaying: true, + isMuted: false, + volume: 1, + playbackRate: 1, +}; + +export const Pause = Template.bind({}); +Pause.args = { + src: 'path/to/audio.mp3', + isPlaying: false, + isMuted: false, + volume: 1, + playbackRate: 1, +}; + +export const Seek = Template.bind({}); +Seek.args = { + src: 'path/to/audio.mp3', + isPlaying: false, + isMuted: false, + volume: 1, + playbackRate: 1, +}; + +export const Mute = Template.bind({}); +Mute.args = { + src: 'path/to/audio.mp3', + isPlaying: false, + isMuted: true, + volume: 1, + playbackRate: 1, +}; + +export const VolumeControl = Template.bind({}); +VolumeControl.args = { + src: 'path/to/audio.mp3', + isPlaying: false, + isMuted: false, + volume: 0.5, + playbackRate: 1, +}; + +export const SpeedControl = Template.bind({}); +SpeedControl.args = { + src: 'path/to/audio.mp3', + isPlaying: false, + isMuted: false, + volume: 1, + playbackRate: 1.5, +}; \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/svelte/src/components/AudioPlayerAdvanced/AudioPlayerAdvanced.svelte b/pkgs/experimental/swarmakit/libs/svelte/src/components/AudioPlayerAdvanced/AudioPlayerAdvanced.svelte new file mode 100644 index 0000000000..99c880f67e --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/svelte/src/components/AudioPlayerAdvanced/AudioPlayerAdvanced.svelte @@ -0,0 +1,67 @@ + + +
    + + + + + + +
    + + \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/svelte/src/components/AudioWaveformDisplay/AudioWaveformDisplay.css b/pkgs/experimental/swarmakit/libs/svelte/src/components/AudioWaveformDisplay/AudioWaveformDisplay.css new file mode 100644 index 0000000000..23b6a6cd6c --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/svelte/src/components/AudioWaveformDisplay/AudioWaveformDisplay.css @@ -0,0 +1,15 @@ +.audio-waveform-display { + display: flex; + align-items: center; + gap: 10px; +} + +canvas { + background-color: #f0f0f0; + border: 1px solid #ccc; +} + +button { + padding: 5px 10px; + cursor: pointer; +} \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/svelte/src/components/AudioWaveformDisplay/AudioWaveformDisplay.stories.ts b/pkgs/experimental/swarmakit/libs/svelte/src/components/AudioWaveformDisplay/AudioWaveformDisplay.stories.ts new file mode 100644 index 0000000000..e0f097d69d --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/svelte/src/components/AudioWaveformDisplay/AudioWaveformDisplay.stories.ts @@ -0,0 +1,77 @@ +import AudioWaveformDisplay from './AudioWaveformDisplay.svelte'; +import type { Meta, StoryFn } from '@storybook/svelte'; + +const meta: Meta = { + title: 'Components/Media/AudioWaveformDisplay', + component: AudioWaveformDisplay, + tags: ['autodocs'], + argTypes: { + src: { + control: { type: 'text' }, + }, + isPlaying: { + control: { type: 'boolean' }, + }, + isLoading: { + control: { type: 'boolean' }, + }, + currentTime: { + control: { type: 'number' }, + }, + duration: { + control: { type: 'number' }, + }, + }, +}; + +export default meta; + +const Template: StoryFn = (args) => ({ + Component: AudioWaveformDisplay, + props: args, +}); + +export const Default = Template.bind({}); +Default.args = { + src: 'path/to/audio.mp3', + isPlaying: false, + isLoading: false, + currentTime: 0, + duration: 0, +}; + +export const Playing = Template.bind({}); +Playing.args = { + src: 'path/to/audio.mp3', + isPlaying: true, + isLoading: false, + currentTime: 10, + duration: 100, +}; + +export const Paused = Template.bind({}); +Paused.args = { + src: 'path/to/audio.mp3', + isPlaying: false, + isLoading: false, + currentTime: 10, + duration: 100, +}; + +export const Loading = Template.bind({}); +Loading.args = { + src: 'path/to/audio.mp3', + isPlaying: false, + isLoading: true, + currentTime: 0, + duration: 0, +}; + +export const Scrubbing = Template.bind({}); +Scrubbing.args = { + src: 'path/to/audio.mp3', + isPlaying: false, + isLoading: false, + currentTime: 50, + duration: 100, +}; \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/svelte/src/components/AudioWaveformDisplay/AudioWaveformDisplay.svelte b/pkgs/experimental/swarmakit/libs/svelte/src/components/AudioWaveformDisplay/AudioWaveformDisplay.svelte new file mode 100644 index 0000000000..216ca77173 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/svelte/src/components/AudioWaveformDisplay/AudioWaveformDisplay.svelte @@ -0,0 +1,92 @@ + + +
    + + + +
    + + \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/svelte/src/components/Badge/Badge.css b/pkgs/experimental/swarmakit/libs/svelte/src/components/Badge/Badge.css new file mode 100644 index 0000000000..ec66c30e43 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/svelte/src/components/Badge/Badge.css @@ -0,0 +1,32 @@ +:root { + --default-bg-color: #e0e0e0; + --notification-bg-color: #ff5733; + --status-bg-color: #28a745; + --text-color: #ffffff; + --padding: 0.5rem; + --border-radius: 12px; + --font-size: 0.875rem; +} + +.badge { + padding: var(--padding); + border-radius: var(--border-radius); + font-size: var(--font-size); + color: var(--text-color); + display: inline-block; + text-align: center; + min-width: 2rem; + line-height: 1.5; +} + +.badge.default { + background-color: var(--default-bg-color); +} + +.badge.notification { + background-color: var(--notification-bg-color); +} + +.badge.status { + background-color: var(--status-bg-color); +} \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/svelte/src/components/Badge/Badge.stories.ts b/pkgs/experimental/swarmakit/libs/svelte/src/components/Badge/Badge.stories.ts new file mode 100644 index 0000000000..a2f81b09fb --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/svelte/src/components/Badge/Badge.stories.ts @@ -0,0 +1,51 @@ +import Badge from './Badge.svelte'; +import type { Meta,StoryFn } from '@storybook/svelte'; + +const meta: Meta = { + title: 'component/Indicators/Badge', + component: Badge, + tags: ['autodocs'], + argTypes: { + type: { + control: { type: 'select' }, + options: ['default', 'notification', 'status'] + }, + label: { control: 'text' }, + }, + parameters: { + layout: 'centered', + viewport: { + viewports: { + smallMobile: { name: 'Small Mobile', styles: { width: '320px', height: '568px' } }, + largeMobile: { name: 'Large Mobile', styles: { width: '414px', height: '896px' } }, + tablet: { name: 'Tablet', styles: { width: '768px', height: '1024px' } }, + desktop: { name: 'Desktop', styles: { width: '1024px', height: '768px' } }, + } + } + } +}; + +export default meta; + +const Template:StoryFn = (args)=>({ + Component: Badge, + props:args, +}); + +export const Default = Template.bind({}); +Default.args = { + type:'default', + label:'Default Badge.', +}; + +export const Notification = Template.bind({}); +Notification.args = { + type:'notification', + label:'3', +}; + +export const StatusIndicator = Template.bind({}); +StatusIndicator.args = { + type:'status', + label:'Online', +}; \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/svelte/src/components/Badge/Badge.svelte b/pkgs/experimental/swarmakit/libs/svelte/src/components/Badge/Badge.svelte new file mode 100644 index 0000000000..0860a3e33a --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/svelte/src/components/Badge/Badge.svelte @@ -0,0 +1,15 @@ + + + + {label} + + + \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/svelte/src/components/BadgeWithCounts/BadgeWithCounts.css b/pkgs/experimental/swarmakit/libs/svelte/src/components/BadgeWithCounts/BadgeWithCounts.css new file mode 100644 index 0000000000..806c55c8d7 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/svelte/src/components/BadgeWithCounts/BadgeWithCounts.css @@ -0,0 +1,27 @@ +:root { + --zero-bg-color: #e0e0e0; + --active-bg-color: #ff5733; + --text-color: #ffffff; + --padding: 0.5rem; + --border-radius: 12px; + --font-size: 0.875rem; +} + +.badge { + padding: var(--padding); + border-radius: var(--border-radius); + font-size: var(--font-size); + color: var(--text-color); + display: inline-block; + text-align: center; + min-width: 2rem; + line-height: 1.5; +} + +.badge.zero { + background-color: var(--zero-bg-color); +} + +.badge.active { + background-color: var(--active-bg-color); +} \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/svelte/src/components/BadgeWithCounts/BadgeWithCounts.stories.ts b/pkgs/experimental/swarmakit/libs/svelte/src/components/BadgeWithCounts/BadgeWithCounts.stories.ts new file mode 100644 index 0000000000..1808675182 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/svelte/src/components/BadgeWithCounts/BadgeWithCounts.stories.ts @@ -0,0 +1,54 @@ +import BadgeWithCounts from './BadgeWithCounts.svelte'; +import type { Meta,StoryFn } from '@storybook/svelte'; + +const meta: Meta = { + title: 'component/Indicators/BadgeWithCounts', + component: BadgeWithCounts, + tags: ['autodocs'], + argTypes: { + count: { control: 'number' }, + maxCount: { control: 'number' }, + }, + parameters: { + layout: 'centered', + viewport: { + viewports: { + smallMobile: { name: 'Small Mobile', styles: { width: '320px', height: '568px' } }, + largeMobile: { name: 'Large Mobile', styles: { width: '414px', height: '896px' } }, + tablet: { name: 'Tablet', styles: { width: '768px', height: '1024px' } }, + desktop: { name: 'Desktop', styles: { width: '1024px', height: '768px' } }, + } + } + } +}; + +export default meta; + +const Template:StoryFn = (args) => ({ + Component: BadgeWithCounts, + props:args, +}); + +export const Default = Template.bind({}); +Default.args = { + count: 0, + maxCount: 99, +}; + +export const Zero = Template.bind({}); +Zero.args = { + count: 0, + maxCount: 99, +}; + +export const Active = Template.bind({}); +Active.args = { + count: 43, + maxCount: 99, +} + +export const Overflow = Template.bind({}); +Overflow.args = { + count: 150, + maxCount: 99, +} \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/svelte/src/components/BadgeWithCounts/BadgeWithCounts.svelte b/pkgs/experimental/swarmakit/libs/svelte/src/components/BadgeWithCounts/BadgeWithCounts.svelte new file mode 100644 index 0000000000..a2d884bd41 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/svelte/src/components/BadgeWithCounts/BadgeWithCounts.svelte @@ -0,0 +1,16 @@ + + + + {displayCount} + + + \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/svelte/src/components/BatteryLevelIndicator/BatteryLevelIndicator.css b/pkgs/experimental/swarmakit/libs/svelte/src/components/BatteryLevelIndicator/BatteryLevelIndicator.css new file mode 100644 index 0000000000..59b26081a8 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/svelte/src/components/BatteryLevelIndicator/BatteryLevelIndicator.css @@ -0,0 +1,47 @@ +:root { + --battery-border-color: #333333; + --full-color: #4caf50; + --low-color: #ffeb3b; + --critical-color: #f44336; + --charging-color: #2196f3; + --battery-height: 20px; + --battery-width: 100px; + --border-radius: 4px; +} + +.battery { + border: 2px solid var(--battery-border-color); + border-radius: var(--border-radius); + width: var(--battery-width); + height: var(--battery-height); + position: relative; +} + +.battery::after { + content: ''; + position: absolute; + top: 25%; + right: -6px; + width: 4px; + height: 50%; + background: var(--battery-border-color); + border-radius: 2px; +} + +.level { + height: 100%; + background-color: var(--full-color); + border-radius: calc(var(--border-radius) - 2px); +} + +.battery.low .level { + background-color: var(--low-color); +} + +.battery.critical .level { + background-color: var(--critical-color); +} + +.battery.charging .level { + background-color: var(--charging-color); +} \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/svelte/src/components/BatteryLevelIndicator/BatteryLevelIndicator.stories.ts b/pkgs/experimental/swarmakit/libs/svelte/src/components/BatteryLevelIndicator/BatteryLevelIndicator.stories.ts new file mode 100644 index 0000000000..b05a167dc9 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/svelte/src/components/BatteryLevelIndicator/BatteryLevelIndicator.stories.ts @@ -0,0 +1,60 @@ +import BatteryLevelIndicator from './BatteryLevelIndicator.svelte'; +import type { Meta, StoryFn } from '@storybook/svelte'; + +const meta: Meta = { + title: 'component/Indicators/BatteryLevelIndicator', + component: BatteryLevelIndicator, + tags: ['autodocs'], + argTypes: { + level: { control: 'number' }, + isCharging: { control: 'boolean' }, + }, + parameters: { + layout: 'centered', + viewport: { + viewports: { + smallMobile: { name: 'Small Mobile', styles: { width: '320px', height: '568px' } }, + largeMobile: { name: 'Large Mobile', styles: { width: '414px', height: '896px' } }, + tablet: { name: 'Tablet', styles: { width: '768px', height: '1024px' } }, + desktop: { name: 'Desktop', styles: { width: '1024px', height: '768px' } }, + } + } + } +}; + +export default meta; + +const Template:StoryFn = (args) => ({ + Component: BatteryLevelIndicator, + props:args, +}); + +export const Default = Template.bind({}); +Default.args = { + level:50, + isCharging:false, +}; + +export const Charging = Template.bind({}); +Charging.args = { + level:50, + isCharging:true, +}; + +export const Full = Template.bind({}); +Full.args = { + level:100, + isCharging:false, +}; + +export const LowBattery = Template.bind({}); +LowBattery.args = { + level:20, + isCharging:false, +}; + +export const Critical = Template.bind({}); +Critical.args = { + level:5, + isCharging:false, +}; \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/svelte/src/components/BatteryLevelIndicator/BatteryLevelIndicator.svelte b/pkgs/experimental/swarmakit/libs/svelte/src/components/BatteryLevelIndicator/BatteryLevelIndicator.svelte new file mode 100644 index 0000000000..92a9d70f82 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/svelte/src/components/BatteryLevelIndicator/BatteryLevelIndicator.svelte @@ -0,0 +1,16 @@ + + +
    +
    +
    + + \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/svelte/src/components/Button/Button.css b/pkgs/experimental/swarmakit/libs/svelte/src/components/Button/Button.css new file mode 100644 index 0000000000..7c8ae6a422 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/svelte/src/components/Button/Button.css @@ -0,0 +1,38 @@ +:root { + --button-primary-bg: #007bff; + --button-secondary-bg: #6c757d; + --button-disabled-bg: #d6d6d6; + --button-text-color: #ffffff; + --button-hover-opacity: 0.8; + --button-active-opacity: 0.6; +} + +.button { + padding: 0.5rem 1rem; + font-size: 1rem; + color: var(--button-text-color); + border: none; + cursor: pointer; + transition: opacity 0.3s; +} + +.button.primary { + background-color: var(--button-primary-bg); +} + +.button.secondary { + background-color: var(--button-secondary-bg); +} + +.button:disabled { + background-color: var(--button-disabled-bg); + cursor: not-allowed; +} + +.button:hover:not(:disabled) { + opacity: var(--button-hover-opacity); +} + +.button:active:not(:disabled) { + opacity: var(--button-active-opacity); +} \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/svelte/src/components/Button/Button.stories.ts b/pkgs/experimental/swarmakit/libs/svelte/src/components/Button/Button.stories.ts new file mode 100644 index 0000000000..d4beae0047 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/svelte/src/components/Button/Button.stories.ts @@ -0,0 +1,69 @@ +import Button from './Button.svelte'; +import type { Meta, StoryFn } from '@storybook/svelte'; + +const meta: Meta + + \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/svelte/src/components/Captcha/Captcha.css b/pkgs/experimental/swarmakit/libs/svelte/src/components/Captcha/Captcha.css new file mode 100644 index 0000000000..b693aa3cc4 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/svelte/src/components/Captcha/Captcha.css @@ -0,0 +1,48 @@ +:root { + --captcha-bg: #f9f9f9; + --captcha-border: #ccc; + --captcha-error: #ff4d4d; + --captcha-success: #4caf50; +} + +.captcha-container { + background-color: var(--captcha-bg); + border: 1px solid var(--captcha-border); + padding: 1rem; + border-radius: 4px; + max-width: 300px; + margin: 0 auto; +} + +input[type="text"] { + width: 100%; + padding: 0.5rem; + margin-bottom: 0.5rem; + border: 1px solid var(--captcha-border); + border-radius: 4px; +} + +button { + width: 100%; + padding: 0.5rem; + background-color: var(--captcha-border); + border: none; + border-radius: 4px; + cursor: pointer; + transition: background-color 0.3s; +} + +button:disabled { + background-color: var(--captcha-success); + cursor: not-allowed; +} + +.error-message { + color: var(--captcha-error); + margin-top: 0.5rem; +} + +.solved-message { + color: var(--captcha-success); + margin-top: 0.5rem; +} \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/svelte/src/components/Captcha/Captcha.stories.ts b/pkgs/experimental/swarmakit/libs/svelte/src/components/Captcha/Captcha.stories.ts new file mode 100644 index 0000000000..e6a20676ef --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/svelte/src/components/Captcha/Captcha.stories.ts @@ -0,0 +1,52 @@ +import Captcha from './Captcha.svelte'; +import type { Meta, StoryFn } from '@storybook/svelte'; + +const meta: Meta = { + title: 'component/Forms/Captcha', + component: Captcha, + tags: ['autodocs'], + argTypes: { + question: { control: 'text' }, + errorMessage: { control: 'text' }, + solved: { control: 'boolean' }, + onSolve: { action: 'solved' }, + }, + parameters: { + layout: 'centered', + viewport: { + viewports: { + smallMobile: { name: 'Small Mobile', styles: { width: '320px', height: '568px' } }, + largeMobile: { name: 'Large Mobile', styles: { width: '414px', height: '896px' } }, + tablet: { name: 'Tablet', styles: { width: '768px', height: '1024px' } }, + desktop: { name: 'Desktop', styles: { width: '1024px', height: '768px' } }, + } + } + } +}; + +export default meta; + +const Template:StoryFn = (args) => ({ + Component: Captcha, + props:args +}); + +export const Default = Template.bind({}); +Default.args = { + question: 'What is 2 + 2 ?', + errorMessage: '', + solved:false, +}; + +export const Solved = Template.bind({}); +Solved.args = { + question: 'What is 2 + 2 ?', + solved:true, +}; + +export const Error = Template.bind({}); +Error.args = { + question: 'What is 2 + 2 ?', + errorMessage: 'Incorrect answer, please try again', + solved:false, +}; \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/svelte/src/components/Captcha/Captcha.svelte b/pkgs/experimental/swarmakit/libs/svelte/src/components/Captcha/Captcha.svelte new file mode 100644 index 0000000000..5a46bd8937 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/svelte/src/components/Captcha/Captcha.svelte @@ -0,0 +1,34 @@ + + +
    +

    {question}

    + + + {#if errorMessage} + + {/if} + {#if solved} +

    Captcha Solved!

    + {/if} +
    + + \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/svelte/src/components/CardbasedList/CardbasedList.css b/pkgs/experimental/swarmakit/libs/svelte/src/components/CardbasedList/CardbasedList.css new file mode 100644 index 0000000000..4dda9fbd10 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/svelte/src/components/CardbasedList/CardbasedList.css @@ -0,0 +1,36 @@ +:root { + --card-bg: #ffffff; + --card-hover-bg: #f0f0f0; + --card-selected-bg: #d0f0d0; + --card-disabled-bg: #e0e0e0; + --card-border: #ccc; + --card-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); +} + +.cardbased-list { + display: grid; + gap: 10px; +} + +.card { + background: var(--card-bg); + border: 1px solid var(--card-border); + border-radius: 4px; + box-shadow: var(--card-shadow); + padding: 15px; + cursor: pointer; + transition: background 0.3s; +} + +.card:hover { + background: var(--card-hover-bg); +} + +.card.selected { + background: var(--card-selected-bg); +} + +.card.disabled { + background: var(--card-disabled-bg); + cursor: not-allowed; +} \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/svelte/src/components/CardbasedList/CardbasedList.stories.ts b/pkgs/experimental/swarmakit/libs/svelte/src/components/CardbasedList/CardbasedList.stories.ts new file mode 100644 index 0000000000..df4ea3ea7a --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/svelte/src/components/CardbasedList/CardbasedList.stories.ts @@ -0,0 +1,68 @@ +import CardbasedList from './CardbasedList.svelte'; +import type { Meta, StoryFn } from '@storybook/svelte'; + +const meta: Meta = { + title: 'component/Lists/CardbasedList', + component: CardbasedList, + tags: ['autodocs'], + argTypes: { + cards: { control: 'object' } + }, + parameters: { + layout: 'centered', + viewport: { + viewports: { + smallMobile: { name: 'Small Mobile', styles: { width: '320px', height: '568px' } }, + largeMobile: { name: 'Large Mobile', styles: { width: '414px', height: '896px' } }, + tablet: { name: 'Tablet', styles: { width: '768px', height: '1024px' } }, + desktop: { name: 'Desktop', styles: { width: '1024px', height: '768px' } }, + } + } + } +}; + +export default meta; + +const Template:StoryFn = (args) => ({ + Component:CardbasedList, + props:args, +}); + +export const Default = Template.bind({}); +Default.args = { + cards: [ + { id: 1, title: 'Card 1', description: 'This is card 1 description' }, + { id: 2, title: 'Card 2', description: 'This is card 2 description' }, + { id: 3, title: 'Card 3', description: 'This is card 3 description' }, + ] +}; + +export const Hover = Template.bind({}); +Hover.args = { + cards: [ + { id: 1, title: 'Card 1', description: 'This is card 1 description' }, + { id: 2, title: 'Card 2', description: 'This is card 2 description' }, + { id: 3, title: 'Card 3', description: 'This is card 3 description' }, + ] +}; +Hover.parameters = { + pseudo: {hover:true}, +}; + +export const Selected = Template.bind({}); +Selected.args = { + cards: [ + { id: 1, title: 'Card 1', description: 'This is card 1 description', selected:true, }, + { id: 2, title: 'Card 2', description: 'This is card 2 description', selected:false,}, + { id: 3, title: 'Card 3', description: 'This is card 3 description', selected:true, }, + ] +}; + +export const Disabled = Template.bind({}); +Disabled.args = { + cards: [ + { id: 1, title: 'Card 1', description: 'This is card 1 description', disabled:true, }, + { id: 2, title: 'Card 2', description: 'This is card 2 description', disabled:false,}, + { id: 3, title: 'Card 3', description: 'This is card 3 description', disabled:true, }, + ] +}; \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/svelte/src/components/CardbasedList/CardbasedList.svelte b/pkgs/experimental/swarmakit/libs/svelte/src/components/CardbasedList/CardbasedList.svelte new file mode 100644 index 0000000000..ede4227a3e --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/svelte/src/components/CardbasedList/CardbasedList.svelte @@ -0,0 +1,29 @@ + + +
    + {#each cards as card (card.id)} +
    selectCard(card)} + aria-disabled={card.disabled} + role ='button' + tabindex = "0" + on:keydown={(e)=> e.key === 'Enter' && selectCard(card)} + > +

    {card.title}

    +

    {card.description}

    +
    + {/each} +
    + + \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/svelte/src/components/Carousel/Carousel.css b/pkgs/experimental/swarmakit/libs/svelte/src/components/Carousel/Carousel.css new file mode 100644 index 0000000000..26e184b4b5 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/svelte/src/components/Carousel/Carousel.css @@ -0,0 +1,44 @@ +.carousel { + position: relative; + width: 100%; + max-width: 600px; + margin: auto; + overflow: hidden; + display: flex; + align-items: center; +} + +img { + width: 100%; + display: none; + transition: opacity 0.5s ease; +} + +img.selected { + display: block; + opacity: 1; +} + +button { + position: absolute; + top: 50%; + transform: translateY(-50%); + background-color: rgba(0, 0, 0, 0.5); + color: white; + border: none; + padding: 10px; + cursor: pointer; + z-index: 1; +} + +button:focus { + outline: 2px solid #fff; +} + +button[aria-label="Previous slide"] { + left: 10px; +} + +button[aria-label="Next slide"] { + right: 10px; +} \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/svelte/src/components/Carousel/Carousel.stories.ts b/pkgs/experimental/swarmakit/libs/svelte/src/components/Carousel/Carousel.stories.ts new file mode 100644 index 0000000000..0a7cd1176c --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/svelte/src/components/Carousel/Carousel.stories.ts @@ -0,0 +1,61 @@ +import Carousel from './Carousel.svelte'; +import type { Meta, StoryFn } from '@storybook/svelte'; + +const meta: Meta = { + title: 'Components/Media/Carousel', + component: Carousel, + tags: ['autodocs'], + argTypes: { + images: { + control: { type: 'object' }, + }, + autoPlay: { + control: { type: 'boolean' }, + }, + autoPlayInterval: { + control: { type: 'number' }, + }, + }, +}; + +export default meta; + +const Template: StoryFn = (args) => ({ + Component: Carousel, + props: args, +}); + +export const Default = Template.bind({}); +Default.args = { + images: ['image1.jpg', 'image2.jpg', 'image3.jpg'], + autoPlay: false, + autoPlayInterval: 3000, +}; + +export const AutoPlay = Template.bind({}); +AutoPlay.args = { + images: ['image1.jpg', 'image2.jpg', 'image3.jpg'], + autoPlay: true, + autoPlayInterval: 3000, +}; + +export const Paused = Template.bind({}); +Paused.args = { + images: ['image1.jpg', 'image2.jpg', 'image3.jpg'], + autoPlay: false, + autoPlayInterval: 3000, +}; + +export const Hover = Template.bind({}); +Hover.args = { + images: ['image1.jpg', 'image2.jpg', 'image3.jpg'], + autoPlay: true, + autoPlayInterval: 3000, +}; + +export const Active = Template.bind({}); +Active.args = { + images: ['image1.jpg', 'image2.jpg', 'image3.jpg'], + autoPlay: true, + autoPlayInterval: 1000, +}; \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/svelte/src/components/Carousel/Carousel.svelte b/pkgs/experimental/swarmakit/libs/svelte/src/components/Carousel/Carousel.svelte new file mode 100644 index 0000000000..47bb8fd81e --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/svelte/src/components/Carousel/Carousel.svelte @@ -0,0 +1,53 @@ + + + + + \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/svelte/src/components/CheckList/CheckList.css b/pkgs/experimental/swarmakit/libs/svelte/src/components/CheckList/CheckList.css new file mode 100644 index 0000000000..daefbc7e25 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/svelte/src/components/CheckList/CheckList.css @@ -0,0 +1,42 @@ +:root { + --checklist-bg: #ffffff; + --checklist-border: #ccc; + --checklist-checked-bg: #d0f0d0; + --checklist-partially-checked-bg: #f0d0d0; + --checklist-disabled-bg: #e0e0e0; + --label-color: #333; +} + +.checklist { + list-style: none; + padding: 0; +} + +.checklist-item { + display: flex; + align-items: center; + padding: 10px; + background: var(--checklist-bg); + border: 1px solid var(--checklist-border); + border-radius: 4px; + margin-bottom: 5px; + transition: background 0.3s; +} + +.checklist-item.checked { + background: var(--checklist-checked-bg); +} + +.checklist-item.partially-checked { + background: var(--checklist-partially-checked-bg); +} + +.checklist-item.disabled { + background: var(--checklist-disabled-bg); + cursor: not-allowed; +} + +label { + margin-left: 10px; + color: var(--label-color); +} \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/svelte/src/components/CheckList/CheckList.stories.ts b/pkgs/experimental/swarmakit/libs/svelte/src/components/CheckList/CheckList.stories.ts new file mode 100644 index 0000000000..eee24ac056 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/svelte/src/components/CheckList/CheckList.stories.ts @@ -0,0 +1,74 @@ +import CheckList from './CheckList.svelte'; +import type { Meta, StoryFn } from '@storybook/svelte'; + +const meta: Meta = { + title: 'component/Lists/CheckList', + component: CheckList, + tags: ['autodocs'], + argTypes: { + items: { control: 'object' } + }, + parameters: { + layout: 'centered', + viewport: { + viewports: { + smallMobile: { name: 'Small Mobile', styles: { width: '320px', height: '568px' } }, + largeMobile: { name: 'Large Mobile', styles: { width: '414px', height: '896px' } }, + tablet: { name: 'Tablet', styles: { width: '768px', height: '1024px' } }, + desktop: { name: 'Desktop', styles: { width: '1024px', height: '768px' } }, + } + } + } +}; + +export default meta; + +const Template:StoryFn = (args) => ({ + Component: CheckList, + props: args, +}); + +export const Default = Template.bind({}); +Default.args = { + items : [ + { id: 1, label: 'Item 1' }, + { id: 2, label: 'Item 2' }, + { id: 3, label: 'Item 3' }, + ], +}; + +export const Checked = Template.bind({}); +Checked.args = { + items : [ + { id: 1, label: 'Item 1', checked:true, }, + { id: 2, label: 'Item 2', checked:false, }, + { id: 3, label: 'Item 3' , checked:true, }, + ], +}; + +export const UnChecked = Template.bind({}); +UnChecked.args = { + items : [ + { id: 1, label: 'Item 1', checked:false, }, + { id: 2, label: 'Item 2', checked:false, }, + { id: 3, label: 'Item 3' , checked:false, }, + ], +}; + +export const PartiallyChecked = Template.bind({}); +PartiallyChecked.args = { + items : [ + { id: 1, label: 'Item 1', partiallyChecked:true, }, + { id: 2, label: 'Item 2', partiallyChecked:false, }, + { id: 3, label: 'Item 3' , partiallyChecked:true, }, + ], +}; + +export const Disabled = Template.bind({}); +Disabled.args = { + items : [ + { id: 1, label: 'Item 1', disabled:true, }, + { id: 2, label: 'Item 2', disabled:false, }, + { id: 3, label: 'Item 3' , disabled:true, }, + ], +}; \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/svelte/src/components/CheckList/CheckList.svelte b/pkgs/experimental/swarmakit/libs/svelte/src/components/CheckList/CheckList.svelte new file mode 100644 index 0000000000..7c35cf60fd --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/svelte/src/components/CheckList/CheckList.svelte @@ -0,0 +1,36 @@ + + +
      + {#each items as item (item.id)} +
    • + toggleCheck(item)} + disabled={item.disabled} + aria-checked={item.partiallyChecked ? 'mixed' : item.checked} + id={`checkbox-${item.id}`} + /> + +
    • + {/each} +
    + + \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/svelte/src/components/Checkbox/Checkbox.css b/pkgs/experimental/swarmakit/libs/svelte/src/components/Checkbox/Checkbox.css new file mode 100644 index 0000000000..483cee116f --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/svelte/src/components/Checkbox/Checkbox.css @@ -0,0 +1,37 @@ +:root { + --checkbox-bg: #fff; + --checkbox-border: #ccc; + --checkbox-checked: #4caf50; + --checkbox-disabled: #e0e0e0; + --checkbox-text: #000; +} + +.checkbox-container { + display: flex; + align-items: center; +} + +input[type='checkbox'] { + appearance: none; + background-color: var(--checkbox-bg); + border: 1px solid var(--checkbox-border); + width: 18px; + height: 18px; + margin-right: 0.5rem; + display: inline-block; + position: relative; +} + +input[type='checkbox']:checked { + background-color: var(--checkbox-checked); +} + +input[type='checkbox']:disabled { + background-color: var(--checkbox-disabled); + border-color: var(--checkbox-disabled); +} + +label { + color: var(--checkbox-text); + font-size: 1rem; +} \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/svelte/src/components/Checkbox/Checkbox.stories.ts b/pkgs/experimental/swarmakit/libs/svelte/src/components/Checkbox/Checkbox.stories.ts new file mode 100644 index 0000000000..9247c1ede1 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/svelte/src/components/Checkbox/Checkbox.stories.ts @@ -0,0 +1,59 @@ +import Checkbox from './Checkbox.svelte'; +import type { Meta, StoryFn } from '@storybook/svelte'; + +const meta: Meta = { + title: 'component/Forms/Checkbox', + component: Checkbox, + tags: ['autodocs'], + argTypes: { + label: { control: 'text' }, + checked: { control: 'boolean' }, + disabled: { control: 'boolean' }, + }, + parameters: { + layout: 'centered', + viewport: { + viewports: { + smallMobile: { name: 'Small Mobile', styles: { width: '320px', height: '568px' } }, + largeMobile: { name: 'Large Mobile', styles: { width: '414px', height: '896px' } }, + tablet: { name: 'Tablet', styles: { width: '768px', height: '1024px' } }, + desktop: { name: 'Desktop', styles: { width: '1024px', height: '768px' } }, + } + } + } +}; + +export default meta; + +const Template:StoryFn = (args) => ({ + Component:Checkbox, + props:args, +}); + +export const Default = Template.bind({}); +Default.args = { + label: 'Accept terms and conditions', + checked: false, + disabled: false, +}; + +export const Checked = Template.bind({}); +Checked.args = { + label: 'Accept terms and conditions', + checked: true, + disabled: false, +}; + +export const Unchecked = Template.bind({}); +Unchecked.args = { + label: 'Accept terms and conditions', + checked: false, + disabled: false, +}; + +export const Disabled = Template.bind({}); +Disabled.args = { + label: 'Accept terms and conditions', + checked: false, + disabled: true, +} \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/svelte/src/components/Checkbox/Checkbox.svelte b/pkgs/experimental/swarmakit/libs/svelte/src/components/Checkbox/Checkbox.svelte new file mode 100644 index 0000000000..104ac19560 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/svelte/src/components/Checkbox/Checkbox.svelte @@ -0,0 +1,28 @@ + + +
    + + +
    + + \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/svelte/src/components/CollapsibleMenuList/CollapsibleMenuList.css b/pkgs/experimental/swarmakit/libs/svelte/src/components/CollapsibleMenuList/CollapsibleMenuList.css new file mode 100644 index 0000000000..6d60ad7f91 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/svelte/src/components/CollapsibleMenuList/CollapsibleMenuList.css @@ -0,0 +1,49 @@ +:root { + --menu-bg: #ffffff; + --menu-hover-bg: #f0f0f0; + --menu-expanded-bg: #d0e0f0; + --menu-active-bg: #a0c0f0; + --menu-border: #ccc; + --menu-color: #333; +} + +.collapsible-menu { + list-style: none; + padding: 0; +} + +.menu-item { + cursor: pointer; + background: var(--menu-bg); + border: 1px solid var(--menu-border); + margin-bottom: 5px; + transition: background 0.3s; +} + +.menu-item:hover { + background: var(--menu-hover-bg); +} + +.menu-item.expanded { + background: var(--menu-expanded-bg); +} + +.menu-item.active { + background: var(--menu-active-bg); +} + +.menu-label { + padding: 10px; + color: var(--menu-color); +} + +.submenu { + list-style: none; + padding: 0 10px; + background: var(--menu-expanded-bg); +} + +.submenu li { + padding: 5px 0; + color: var(--menu-color); +} \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/svelte/src/components/CollapsibleMenuList/CollapsibleMenuList.stories.ts b/pkgs/experimental/swarmakit/libs/svelte/src/components/CollapsibleMenuList/CollapsibleMenuList.stories.ts new file mode 100644 index 0000000000..fcdd091027 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/svelte/src/components/CollapsibleMenuList/CollapsibleMenuList.stories.ts @@ -0,0 +1,75 @@ +import CollapsibleMenuList from './CollapsibleMenuList.svelte'; +import type { Meta, StoryFn } from '@storybook/svelte'; + +const meta: Meta = { + title: 'component/Lists/CollapsibleMenuList', + component: CollapsibleMenuList, + tags: ['autodocs'], + argTypes: { + items: { control: 'object' } + }, + parameters: { + layout: 'centered', + viewport: { + viewports: { + smallMobile: { name: 'Small Mobile', styles: { width: '320px', height: '568px' } }, + largeMobile: { name: 'Large Mobile', styles: { width: '414px', height: '896px' } }, + tablet: { name: 'Tablet', styles: { width: '768px', height: '1024px' } }, + desktop: { name: 'Desktop', styles: { width: '1024px', height: '768px' } }, + } + } + } +}; + +export default meta; + +const Template:StoryFn = (args) => ({ + Component: CollapsibleMenuList, + props: args, +}); + +export const Default = Template.bind({}); +Default.args = { + items: [ + { id: 1, label: 'Menu 1' }, + { id: 2, label: 'Menu 2' }, + { id: 3, label: 'Menu 3' } + ], +}; + +export const Expanded = Template.bind({}); +Expanded.args = { + items: [ + { id: 1, label: 'Menu 1' , expanded:true , }, + { id: 2, label: 'Menu 2'}, + { id: 3, label: 'Menu 3', expanded:true ,}, + ], +}; + +export const Collapsed = Template.bind({}); +Collapsed.args = { + items: [ + { id: 1, label: 'Menu 1', expanded:false }, + { id: 2, label: 'Menu 2', expanded:false }, + { id: 3, label: 'Menu 3', expanded:false }, + ], +}; + +export const Hover = Template.bind({}); +Hover.args = { + items: [ + { id: 1, label: 'Menu 1' }, + { id: 2, label: 'Menu 2',expanded: true,}, + { id: 3, label: 'Menu 3' } + ], +}; + + +export const Active = Template.bind({}); +Active.args = { + items: [ + { id: 1, label: 'Menu 1', active:true,}, + { id: 2, label: 'Menu 2'}, + { id: 3, label: 'Menu 3'}, + ], +}; \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/svelte/src/components/CollapsibleMenuList/CollapsibleMenuList.svelte b/pkgs/experimental/swarmakit/libs/svelte/src/components/CollapsibleMenuList/CollapsibleMenuList.svelte new file mode 100644 index 0000000000..a63f29096c --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/svelte/src/components/CollapsibleMenuList/CollapsibleMenuList.svelte @@ -0,0 +1,48 @@ + + +
      + {#each items as item (item.id)} + + {/each} +
    + + \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/svelte/src/components/ColorPicker/ColorPicker.css b/pkgs/experimental/swarmakit/libs/svelte/src/components/ColorPicker/ColorPicker.css new file mode 100644 index 0000000000..c0e678a257 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/svelte/src/components/ColorPicker/ColorPicker.css @@ -0,0 +1,27 @@ +:root { + --color-picker-border: #ccc; + --color-picker-disabled-bg: #e0e0e0; + --color-picker-value-text: #333; +} + +.color-picker-container { + display: flex; + align-items: center; +} + +input[type='color'] { + border: 1px solid var(--color-picker-border); + width: 36px; + height: 36px; + margin-right: 0.5rem; + padding: 0; +} + +input[type='color']:disabled { + background-color: var(--color-picker-disabled-bg); +} + +.color-value { + color: var(--color-picker-value-text); + font-size: 1rem; +} \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/svelte/src/components/ColorPicker/ColorPicker.stories.ts b/pkgs/experimental/swarmakit/libs/svelte/src/components/ColorPicker/ColorPicker.stories.ts new file mode 100644 index 0000000000..90cba50f6c --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/svelte/src/components/ColorPicker/ColorPicker.stories.ts @@ -0,0 +1,48 @@ +import ColorPicker from './ColorPicker.svelte'; +import type { Meta, StoryFn } from '@storybook/svelte'; + +const meta: Meta = { + title: 'component/Forms/ColorPicker', + component: ColorPicker, + tags: ['autodocs'], + argTypes: { + color: { control: 'color' }, + disabled: { control: 'boolean' }, + }, + parameters: { + layout: 'centered', + viewport: { + viewports: { + smallMobile: { name: 'Small Mobile', styles: { width: '320px', height: '568px' } }, + largeMobile: { name: 'Large Mobile', styles: { width: '414px', height: '896px' } }, + tablet: { name: 'Tablet', styles: { width: '768px', height: '1024px' } }, + desktop: { name: 'Desktop', styles: { width: '1024px', height: '768px' } }, + } + } + } +}; + +export default meta; + +const Template:StoryFn = (args) => ({ + Component: ColorPicker, + props:args, +}); + +export const Default = Template.bind({}); +Default.args = { + 'color': '#000000', + disabled:false, +}; + +export const Selected = Template.bind({}); +Selected.args = { + color:'#ff5733', + disabled:false, +}; + +export const Disabled = Template.bind({}); +Disabled.args = { + color:'#000000', + disabled:true, +}; \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/svelte/src/components/ColorPicker/ColorPicker.svelte b/pkgs/experimental/swarmakit/libs/svelte/src/components/ColorPicker/ColorPicker.svelte new file mode 100644 index 0000000000..a6f83ae513 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/svelte/src/components/ColorPicker/ColorPicker.svelte @@ -0,0 +1,24 @@ + + +
    + + {color} +
    + + \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/svelte/src/components/ContextualList/ContextualList.css b/pkgs/experimental/swarmakit/libs/svelte/src/components/ContextualList/ContextualList.css new file mode 100644 index 0000000000..7184135177 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/svelte/src/components/ContextualList/ContextualList.css @@ -0,0 +1,43 @@ +:root { + --list-bg: #ffffff; + --list-border: #ccc; + --list-item-hover-bg: #f0f0f0; + --list-action-bg: #d0f0c0; + --list-color: #333; +} + +.contextual-list { + list-style: none; + padding: 0; + border: 1px solid var(--list-border); + background: var(--list-bg); +} + +.list-item { + padding: 10px; + cursor: pointer; + transition: background 0.3s; + display: flex; + justify-content: space-between; + align-items: center; +} + +.list-item:hover { + background: var(--list-item-hover-bg); +} + +.list-item.action-triggered { + background: var(--list-action-bg); +} + +button { + background: none; + border: none; + cursor: pointer; + color: var(--list-color); + padding: 5px 10px; +} + +button:hover { + text-decoration: underline; +} \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/svelte/src/components/ContextualList/ContextualList.stories.ts b/pkgs/experimental/swarmakit/libs/svelte/src/components/ContextualList/ContextualList.stories.ts new file mode 100644 index 0000000000..dccd19a479 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/svelte/src/components/ContextualList/ContextualList.stories.ts @@ -0,0 +1,60 @@ +import ContextualList from './ContextualList.svelte'; +import type { Meta, StoryFn } from '@storybook/svelte'; + +const meta: Meta = { + title: 'component/Lists/ContextualList', + component: ContextualList, + tags: ['autodocs'], + argTypes: { + items: { control: 'object' }, + visible: { control: 'boolean' } + }, + parameters: { + layout: 'centered', + viewport: { + viewports: { + smallMobile: { name: 'Small Mobile', styles: { width: '320px', height: '568px' } }, + largeMobile: { name: 'Large Mobile', styles: { width: '414px', height: '896px' } }, + tablet: { name: 'Tablet', styles: { width: '768px', height: '1024px' } }, + desktop: { name: 'Desktop', styles: { width: '1024px', height: '768px' } }, + } + } + } +}; + +export default meta; + +const Template:StoryFn = (args) => ({ + Component: ContextualList, + props:args, +}); + +export const Default = Template.bind({}); +Default.args = { + items: [ + { id: 1, label: 'Item 1' }, + { id: 2, label: 'Item 2' }, + { id: 3, label: 'Item 3' } + ], + visible: true, +}; + +export const ActionTriggered = Template.bind({}); +ActionTriggered.args = { + items: [ + { id: 1, label: 'Item 1',actionTriggered:true, }, + { id: 2, label: 'Item 2' }, + { id: 3, label: 'Item 3' } + ], + visible:true, +}; + +export const Dismissed = Template.bind({}); +Dismissed.args = { + items: [ + { id: 1, label: 'Item 1' }, + { id: 2, label: 'Item 2' }, + { id: 3, label: 'Item 3' } + ], + visible:false, +}; \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/svelte/src/components/ContextualList/ContextualList.svelte b/pkgs/experimental/swarmakit/libs/svelte/src/components/ContextualList/ContextualList.svelte new file mode 100644 index 0000000000..4b78288ddf --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/svelte/src/components/ContextualList/ContextualList.svelte @@ -0,0 +1,36 @@ + + +{#if visible} +
      + {#each items as item (item.id)} +
    • + {item.label} + +
    • + {/each} +
    • + +
    • +
    +{/if} + + \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/svelte/src/components/CountdownTimer/CountdownTimer.css b/pkgs/experimental/swarmakit/libs/svelte/src/components/CountdownTimer/CountdownTimer.css new file mode 100644 index 0000000000..bb92bfd3b1 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/svelte/src/components/CountdownTimer/CountdownTimer.css @@ -0,0 +1,42 @@ +:root { + --timer-border-color: #333333; + --background-color: #ffffff; + --text-color: #000000; + --button-background: #4caf50; + --button-text-color: #ffffff; + --button-border-radius: 4px; + --timer-font-size: 24px; + --button-font-size: 16px; + --button-padding: 8px 16px; +} + +.countdown-timer { + display: flex; + flex-direction: column; + align-items: center; + border: 2px solid var(--timer-border-color); + background-color: var(--background-color); + padding: 16px; + border-radius: 8px; +} + +.time-display { + font-size: var(--timer-font-size); + color: var(--text-color); + margin-bottom: 16px; +} + +button { + background-color: var(--button-background); + color: var(--button-text-color); + border: none; + border-radius: var(--button-border-radius); + padding: var(--button-padding); + margin: 4px; + cursor: pointer; + font-size: var(--button-font-size); +} + +button:hover { + background-color: darken(var(--button-background), 10%); +} \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/svelte/src/components/CountdownTimer/CountdownTimer.stories.ts b/pkgs/experimental/swarmakit/libs/svelte/src/components/CountdownTimer/CountdownTimer.stories.ts new file mode 100644 index 0000000000..6065881e14 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/svelte/src/components/CountdownTimer/CountdownTimer.stories.ts @@ -0,0 +1,54 @@ +import CountdownTimer from './CountdownTimer.svelte'; +import type { Meta, StoryFn } from '@storybook/svelte'; + +const meta: Meta = { + title: 'component/Indicators/CountdownTimer', + component: CountdownTimer, + tags: ['autodocs'], + argTypes: { + duration: { control: 'number' }, + autoStart: { control: 'boolean' }, + }, + parameters: { + layout: 'centered', + viewport: { + viewports: { + smallMobile: { name: 'Small Mobile', styles: { width: '320px', height: '568px' } }, + largeMobile: { name: 'Large Mobile', styles: { width: '414px', height: '896px' } }, + tablet: { name: 'Tablet', styles: { width: '768px', height: '1024px' } }, + desktop: { name: 'Desktop', styles: { width: '1024px', height: '768px' } }, + } + } + } +}; + +export default meta; + +const Template:StoryFn = (args) => ({ + Component: CountdownTimer, + props: args, +}); + +export const Default = Template.bind({}); +Default.args = { + duration:60, + autoStart:false, +}; + +export const Running = Template.bind({}); +Running.args = { + duration:60, + autoStart:true, +}; + +export const Paused = Template.bind({}); +Default.args = { + duration:60, + autoStart:false, +}; + +export const Completed = Template.bind({}); +Completed.args = { + duration:0, + autoStart:false, +}; \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/svelte/src/components/CountdownTimer/CountdownTimer.svelte b/pkgs/experimental/swarmakit/libs/svelte/src/components/CountdownTimer/CountdownTimer.svelte new file mode 100644 index 0000000000..1678778f12 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/svelte/src/components/CountdownTimer/CountdownTimer.svelte @@ -0,0 +1,52 @@ + + +
    +
    {timeLeft}s
    + + + +
    + + \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/svelte/src/components/DataGrid/DataGrid.css b/pkgs/experimental/swarmakit/libs/svelte/src/components/DataGrid/DataGrid.css new file mode 100644 index 0000000000..eaa50811cc --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/svelte/src/components/DataGrid/DataGrid.css @@ -0,0 +1,55 @@ +:root { + --grid-border: #ccc; + --grid-header-bg: #f9f9f9; + --grid-row-hover-bg: #f0f0f0; + --grid-color: #333; + --pagination-bg: #ddd; +} + +.data-grid { + width: 100%; + overflow-x: auto; +} + +table { + width: 100%; + border-collapse: collapse; +} + +th, td { + padding: 8px; + border: 1px solid var(--grid-border); + text-align: left; +} + +th { + background-color: var(--grid-header-bg); +} + +tr:hover { + background-color: var(--grid-row-hover-bg); +} + +.pagination { + display: flex; + justify-content: space-between; + background: var(--pagination-bg); + padding: 10px; +} + +button { + background: none; + border: 1px solid var(--grid-border); + padding: 5px 10px; + cursor: pointer; +} + +button:disabled { + cursor: not-allowed; + opacity: 0.5; +} + +.resizable th { + resize: horizontal; + overflow: hidden; +} \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/svelte/src/components/DataGrid/DataGrid.stories.ts b/pkgs/experimental/swarmakit/libs/svelte/src/components/DataGrid/DataGrid.stories.ts new file mode 100644 index 0000000000..3098741a41 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/svelte/src/components/DataGrid/DataGrid.stories.ts @@ -0,0 +1,97 @@ +import DataGrid from './DataGrid.svelte'; +import type { Meta, StoryFn } from '@storybook/svelte'; + +const meta: Meta = { + title: 'component/Lists/DataGrid', + component: DataGrid, + tags: ['autodocs'], + argTypes: { + columns: { control: 'object' }, + rows: { control: 'object' }, + pageSize: { control: 'number' }, + searchQuery: { control: 'text' }, + resizable: { control: 'boolean' } + }, + parameters: { + layout: 'centered', + viewport: { + viewports: { + smallMobile: { name: 'Small Mobile', styles: { width: '320px', height: '568px' } }, + largeMobile: { name: 'Large Mobile', styles: { width: '414px', height: '896px' } }, + tablet: { name: 'Tablet', styles: { width: '768px', height: '1024px' } }, + desktop: { name: 'Desktop', styles: { width: '1024px', height: '768px' } }, + } + } + } +}; + +export default meta; + +const Template:StoryFn = (args) => ({ + Component: DataGrid, + props: args, +}); + +export const Default = Template.bind({}); +Default.args = { + columns: [ + { id: 'id', label: 'ID' }, + { id: 'name', label: 'Name' }, + { id: 'age', label: 'Age' } + ], + rows: [ + { id: 1, name: 'John Doe', age: 28 }, + { id: 2, name: 'Jane Smith', age: 34 }, + { id: 3, name: 'Alice Johnson', age: 45 } + ], + pageSize:10, + searchQuery:'', + resizable:false, +}; + +export const Paginated = Template.bind({}); +Paginated.args = { + columns: [ + { id: 'id', label: 'ID' }, + { id: 'name', label: 'Name' }, + { id: 'age', label: 'Age' } + ], + rows: Array.from({ length: 30 }, (_, i) => ({ id: i + 1, name: `User ${i + 1}`, age: 20 + i })), + pageSize:5, + searchQuery:'', + resizable:false, +}; + +export const Search = Template.bind({}); +Search.args = { + columns: [ + { id: 'id', label: 'ID' }, + { id: 'name', label: 'Name' }, + { id: 'age', label: 'Age' } + ], + rows: [ + { id: 1, name: 'John Doe', age: 28 }, + { id: 2, name: 'Jane Smith', age: 34 }, + { id: 3, name: 'Alice Johnson', age: 45 } + ], + pageSize:5, + searchQuery:'Jane', + resizable:false, +}; + +export const Resizable = Template.bind({}); +Resizable.args = { + columns: [ + { id: 'id', label: 'ID' }, + { id: 'name', label: 'Name' }, + { id: 'age', label: 'Age' } + ], + rows: [ + { id: 1, name: 'John Doe', age: 28 }, + { id: 2, name: 'Jane Smith', age: 34 }, + { id: 3, name: 'Alice Johnson', age: 45 } + ], + pageSize:5, + searchQuery:'', + resizable:true, +}; \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/svelte/src/components/DataGrid/DataGrid.svelte b/pkgs/experimental/swarmakit/libs/svelte/src/components/DataGrid/DataGrid.svelte new file mode 100644 index 0000000000..7810c0091b --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/svelte/src/components/DataGrid/DataGrid.svelte @@ -0,0 +1,68 @@ + + +
    + {#if searchQuery} + + {/if} + + + + + {#each columns as column} + + {/each} + + + + {#each filteredRows.slice(currentPage * pageSize, (currentPage + 1) * pageSize) as row} + + {#each columns as column} + + {/each} + + {/each} + +
    {column.label}
    {row[column.id]}
    + + {#if pageSize < rows.length} + + {/if} +
    + + \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/svelte/src/components/DateAndTimePicker/DateAndTimePicker.css b/pkgs/experimental/swarmakit/libs/svelte/src/components/DateAndTimePicker/DateAndTimePicker.css new file mode 100644 index 0000000000..3bcfb53d29 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/svelte/src/components/DateAndTimePicker/DateAndTimePicker.css @@ -0,0 +1,24 @@ +:root { + --date-time-picker-border: #ccc; + --date-time-picker-disabled-bg: #e0e0e0; + --date-time-picker-font-color: #333; +} + +.date-time-picker-container { + display: flex; + flex-direction: column; + gap: 0.5rem; +} + +input[type='date'], +input[type='time'] { + border: 1px solid var(--date-time-picker-border); + padding: 0.5rem; + font-size: 1rem; + color: var(--date-time-picker-font-color); +} + +input[type='date']:disabled, +input[type='time']:disabled { + background-color: var(--date-time-picker-disabled-bg); +} \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/svelte/src/components/DateAndTimePicker/DateAndTimePicker.stories.ts b/pkgs/experimental/swarmakit/libs/svelte/src/components/DateAndTimePicker/DateAndTimePicker.stories.ts new file mode 100644 index 0000000000..02117997f4 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/svelte/src/components/DateAndTimePicker/DateAndTimePicker.stories.ts @@ -0,0 +1,59 @@ +import DateAndTimePicker from './DateAndTimePicker.svelte'; +import type { Meta, StoryFn } from '@storybook/svelte'; + +const meta: Meta = { + title: 'component/Forms/DateAndTimePicker', + component: DateAndTimePicker, + tags: ['autodocs'], + argTypes: { + date: { control: 'date' }, + time: { control: 'text' }, + disabled: { control: 'boolean' }, + }, + parameters: { + layout: 'centered', + viewport: { + viewports: { + smallMobile: { name: 'Small Mobile', styles: { width: '320px', height: '568px' } }, + largeMobile: { name: 'Large Mobile', styles: { width: '414px', height: '896px' } }, + tablet: { name: 'Tablet', styles: { width: '768px', height: '1024px' } }, + desktop: { name: 'Desktop', styles: { width: '1024px', height: '768px' } }, + } + } + } +}; + +export default meta; + +const Template:StoryFn = (args) => ({ + Component: DateAndTimePicker, + props: args, +}); + +export const Default = Template.bind({}); +Default.args = { + date: '', + time: '', + disabled:false, +}; + +export const DateSelected = Template.bind({}); +DateSelected.args = { + date: '2023-10-01', + time: '', + disabled:false, +}; + +export const TimeSelected = Template.bind({}); +TimeSelected.args = { + date: '', + time: '12:00', + disabled:false, +}; + +export const Disabled = Template.bind({}); +Disabled.args = { + date: '', + time: '', + disabled:true, +}; \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/svelte/src/components/DateAndTimePicker/DateAndTimePicker.svelte b/pkgs/experimental/swarmakit/libs/svelte/src/components/DateAndTimePicker/DateAndTimePicker.svelte new file mode 100644 index 0000000000..21ec2c27b2 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/svelte/src/components/DateAndTimePicker/DateAndTimePicker.svelte @@ -0,0 +1,34 @@ + + +
    + + +
    + + \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/svelte/src/components/DatePicker/DatePicker.css b/pkgs/experimental/swarmakit/libs/svelte/src/components/DatePicker/DatePicker.css new file mode 100644 index 0000000000..e252eddcf6 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/svelte/src/components/DatePicker/DatePicker.css @@ -0,0 +1,24 @@ +:root { + --date-picker-border: #ccc; + --date-picker-disabled-bg: #e0e0e0; + --date-picker-font-color: #333; +} + +.date-picker-container { + display: flex; + flex-direction: column; + gap: 0.5rem; +} + +input[type='date'], +input[type='time'] { + border: 1px solid var(--date-picker-border); + padding: 0.5rem; + font-size: 1rem; + color: var(--date-picker-font-color); +} + +input[type='date']:disabled, +input[type='time']:disabled { + background-color: var(--date-picker-disabled-bg); +} \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/svelte/src/components/DatePicker/DatePicker.stories.ts b/pkgs/experimental/swarmakit/libs/svelte/src/components/DatePicker/DatePicker.stories.ts new file mode 100644 index 0000000000..7699fce3b2 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/svelte/src/components/DatePicker/DatePicker.stories.ts @@ -0,0 +1,52 @@ +import DatePicker from './DatePicker.svelte'; +import type { Meta, StoryFn } from '@storybook/svelte'; + +const meta: Meta = { + title: 'component/Forms/DatePicker', + component: DatePicker, + tags: ['autodocs'], + argTypes: { + date: { control: 'date' }, + startDate: { control: 'date' }, + endDate: { control: 'date' }, + time: { control: 'text' }, + mode: { control: { type: 'select', options: ['single', 'range', 'time'] } }, + }, + parameters: { + layout: 'centered', + viewport: { + viewports: { + smallMobile: { name: 'Small Mobile', styles: { width: '320px', height: '568px' } }, + largeMobile: { name: 'Large Mobile', styles: { width: '414px', height: '896px' } }, + tablet: { name: 'Tablet', styles: { width: '768px', height: '1024px' } }, + desktop: { name: 'Desktop', styles: { width: '1024px', height: '768px' } }, + } + } + } +}; + +export default meta; + +const Template:StoryFn = (args) => ({ + Component: DatePicker, + props:args, +}); + +export const SingleDate = Template.bind({}); +SingleDate.args = { + date:'', + mode:'single', +}; + +export const DateRange = Template.bind({}); +DateRange.args = { + startDate:'', + endDate:'', + mode:'range', +}; + +export const TimePicker = Template.bind({}); +TimePicker.args = { + time:'', + mode:'time', +}; \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/svelte/src/components/DatePicker/DatePicker.svelte b/pkgs/experimental/swarmakit/libs/svelte/src/components/DatePicker/DatePicker.svelte new file mode 100644 index 0000000000..3421f7505a --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/svelte/src/components/DatePicker/DatePicker.svelte @@ -0,0 +1,58 @@ + + +
    + {#if mode === 'single'} + + {:else if mode === 'range'} + + + {:else if mode === 'time'} + + {/if} +
    + + \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/svelte/src/components/DragAndDropFileArea/DragAndDropFileArea.css b/pkgs/experimental/swarmakit/libs/svelte/src/components/DragAndDropFileArea/DragAndDropFileArea.css new file mode 100644 index 0000000000..91e0f8f430 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/svelte/src/components/DragAndDropFileArea/DragAndDropFileArea.css @@ -0,0 +1,31 @@ +:root { + --drop-area-border: #ccc; + --drop-area-hover-border: #007bff; + --drop-area-disabled-bg: #f5f5f5; + --error-color: #d9534f; +} + +.drop-area { + border: 2px dashed var(--drop-area-border); + padding: 1rem; + text-align: center; + transition: border-color 0.3s; + cursor: pointer; +} + +.drop-area.dragging { + border-color: var(--drop-area-hover-border); +} + +.drop-area[aria-disabled='true'] { + background-color: var(--drop-area-disabled-bg); + cursor: not-allowed; +} + +.file-input { + display: none; +} + +.error { + color: var(--error-color); +} \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/svelte/src/components/DragAndDropFileArea/DragAndDropFileArea.stories.ts b/pkgs/experimental/swarmakit/libs/svelte/src/components/DragAndDropFileArea/DragAndDropFileArea.stories.ts new file mode 100644 index 0000000000..95bc96e6ab --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/svelte/src/components/DragAndDropFileArea/DragAndDropFileArea.stories.ts @@ -0,0 +1,88 @@ +import DragAndDropFileArea from './DragAndDropFileArea.svelte'; +import type { Meta, StoryFn } from '@storybook/svelte'; + +const meta: Meta = { + title: 'component/Forms/DragAndDropFileArea', + component: DragAndDropFileArea, + tags: ['autodocs'], + argTypes: { + disabled: { control: 'boolean' }, + multiple: { control: 'boolean' }, + acceptedTypes: { control: 'text' }, + errorMessage: { control: 'text' }, + }, + parameters: { + layout: 'centered', + viewport: { + viewports: { + smallMobile: { name: 'Small Mobile', styles: { width: '320px', height: '568px' } }, + largeMobile: { name: 'Large Mobile', styles: { width: '414px', height: '896px' } }, + tablet: { name: 'Tablet', styles: { width: '768px', height: '1024px' } }, + desktop: { name: 'Desktop', styles: { width: '1024px', height: '768px' } }, + } + } + } +}; + +export default meta; + +const Template:StoryFn = (args) => ({ + Component: DragAndDropFileArea, + props: args, +}); + +export const Default = Template.bind({}); +Default.args = { + disabled: false, + multiple: true, + acceptedTypes:'', + errorMessage:'', +}; + +export const Dragging = Template.bind({}); +Dragging.args = { + disabled: false, + multiple: true, + acceptedTypes:'', + errorMessage:'', +}; + +export const FileHover = Template.bind({}); +FileHover.args = { + disabled: false, + multiple: true, + acceptedTypes:'', + errorMessage:'', +}; + +export const FileDropped = Template.bind({}); +FileDropped.args = { + disabled: false, + multiple: true, + acceptedTypes:'', + errorMessage:'', +}; + +export const FileUploading = Template.bind({}); +FileUploading.args = { + disabled: true, + multiple: true, + acceptedTypes:'', + errorMessage:'', +}; + +export const Error = Template.bind({}); +Error.args = { + disabled: false, + multiple: true, + acceptedTypes:'', + errorMessage:'Invalid file type.', +}; + +export const Disabled = Template.bind({}); +Disabled.args = { + disabled: true, + multiple: true, + acceptedTypes:'', + errorMessage:'', +}; \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/svelte/src/components/DragAndDropFileArea/DragAndDropFileArea.svelte b/pkgs/experimental/swarmakit/libs/svelte/src/components/DragAndDropFileArea/DragAndDropFileArea.svelte new file mode 100644 index 0000000000..9169151b69 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/svelte/src/components/DragAndDropFileArea/DragAndDropFileArea.svelte @@ -0,0 +1,73 @@ + + +
    + +

    {dragging ? 'Drop files here...' : 'Drag and drop files here or click to browse'}

    + {#if errorMessage} +

    {errorMessage}

    + {/if} +
    + + \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/svelte/src/components/EmbeddedMediaIframe/EmbeddedMediaIframe.css b/pkgs/experimental/swarmakit/libs/svelte/src/components/EmbeddedMediaIframe/EmbeddedMediaIframe.css new file mode 100644 index 0000000000..cbd0a24848 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/svelte/src/components/EmbeddedMediaIframe/EmbeddedMediaIframe.css @@ -0,0 +1,27 @@ +.embedded-media-iframe { + position: relative; + width: 100%; + max-width: 800px; + margin: auto; +} + +iframe { + width: 100%; + height: 450px; + border: none; +} + +div[role="button"] { + position: absolute; + bottom: 10px; + right: 10px; + background-color: rgba(0, 0, 0, 0.5); + color: white; + padding: 8px; + cursor: pointer; + border-radius: 4px; +} + +div[role="button"]:focus { + outline: 2px solid #fff; +} \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/svelte/src/components/EmbeddedMediaIframe/EmbeddedMediaIframe.stories.ts b/pkgs/experimental/swarmakit/libs/svelte/src/components/EmbeddedMediaIframe/EmbeddedMediaIframe.stories.ts new file mode 100644 index 0000000000..6230ed3707 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/svelte/src/components/EmbeddedMediaIframe/EmbeddedMediaIframe.stories.ts @@ -0,0 +1,47 @@ +import EmbeddedMediaIframe from './EmbeddedMediaIframe.svelte'; +import type { Meta, StoryFn } from '@storybook/svelte'; + +const meta: Meta = { + title: 'Components/Media/EmbeddedMediaIframe', + component: EmbeddedMediaIframe, + tags: ['autodocs'], + argTypes: { + src: { + control: { type: 'text' }, + }, + title: { + control: { type: 'text' }, + }, + allowFullscreen: { + control: { type: 'boolean' }, + }, + }, +}; + +export default meta; + +const Template: StoryFn = (args) => ({ + Component: EmbeddedMediaIframe, + props: args, +}); + +export const Default = Template.bind({}); +Default.args = { + src: 'https://www.youtube.com/embed/dQw4w9WgXcQ', + title: 'Default Media', + allowFullscreen: false, +}; + +export const Fullscreen = Template.bind({}); +Fullscreen.args = { + src: 'https://www.youtube.com/embed/dQw4w9WgXcQ', + title: 'Fullscreen Enabled Media', + allowFullscreen: true, +}; + +export const Buffering = Template.bind({}); +Buffering.args = { + src: 'https://www.youtube.com/embed/dQw4w9WgXcQ?autoplay=1', + title: 'Buffering Media', + allowFullscreen: false, +}; \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/svelte/src/components/EmbeddedMediaIframe/EmbeddedMediaIframe.svelte b/pkgs/experimental/swarmakit/libs/svelte/src/components/EmbeddedMediaIframe/EmbeddedMediaIframe.svelte new file mode 100644 index 0000000000..b90e828212 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/svelte/src/components/EmbeddedMediaIframe/EmbeddedMediaIframe.svelte @@ -0,0 +1,38 @@ + + +
    + + {#if allowFullscreen} +
    + Toggle Fullscreen +
    + {/if} +
    + + \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/svelte/src/components/ExpandableList/ExpandableList.css b/pkgs/experimental/swarmakit/libs/svelte/src/components/ExpandableList/ExpandableList.css new file mode 100644 index 0000000000..06ca3316c6 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/svelte/src/components/ExpandableList/ExpandableList.css @@ -0,0 +1,42 @@ +:root { + --list-border: #ccc; + --list-item-bg: #fff; + --list-item-hover-bg: #f0f0f0; + --list-item-selected-bg: #e0e0e0; + --list-item-expanded-bg: #f9f9f9; + --list-color: #333; +} + +.expandable-list { + list-style: none; + padding: 0; + margin: 0; +} + +.list-item { + border-bottom: 1px solid var(--list-border); + padding: 10px; + background-color: var(--list-item-bg); + cursor: pointer; +} + +.list-item:hover { + background-color: var(--list-item-hover-bg); +} + +.item-label { + font-weight: bold; +} + +.item-content { + margin-top: 5px; + padding-left: 15px; +} + +.expanded { + background-color: var(--list-item-expanded-bg) !important; +} + +.selected { + background-color: var(--list-item-selected-bg) !important; +} \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/svelte/src/components/ExpandableList/ExpandableList.stories.ts b/pkgs/experimental/swarmakit/libs/svelte/src/components/ExpandableList/ExpandableList.stories.ts new file mode 100644 index 0000000000..84b8285db6 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/svelte/src/components/ExpandableList/ExpandableList.stories.ts @@ -0,0 +1,91 @@ +import ExpandableList from './ExpandableList.svelte'; +import type { Meta, StoryFn } from '@storybook/svelte'; +import { userEvent, within } from '@storybook/test'; + +const meta: Meta = { + title: 'component/Lists/ExpandableList', + component: ExpandableList, + tags: ['autodocs'], + argTypes: { + items: { control: 'object' } + }, + parameters: { + layout: 'centered', + viewport: { + viewports: { + smallMobile: { name: 'Small Mobile', styles: { width: '320px', height: '568px' } }, + largeMobile: { name: 'Large Mobile', styles: { width: '414px', height: '896px' } }, + tablet: { name: 'Tablet', styles: { width: '768px', height: '1024px' } }, + desktop: { name: 'Desktop', styles: { width: '1024px', height: '768px' } }, + } + } + } +}; + +export default meta; + +const Template:StoryFn = (args) => ({ + Component:ExpandableList, + props:args, +}); + +export const Default = Template.bind({}); +Default.args = { + items: [ + { id: '1', label: 'Item 1', content: 'Content of Item 1' }, + { id: '2', label: 'Item 2', content: 'Content of Item 2' }, + { id: '3', label: 'Item 3', content: 'Content of Item 3' } + ] +}; + +export const ItemExpanded = Template.bind({}); +ItemExpanded.args = { + items: [ + { id: '1', label: 'Item 1', content: 'Content of Item 1' }, + { id: '2', label: 'Item 2', content: 'Content of Item 2' }, + { id: '3', label: 'Item 3', content: 'Content of Item 3' } + ] +}; +ItemExpanded.play = async({canvasElement}) => { + const canvas = within(canvasElement); + const firstItem = await canvas.getByText('Item 1'); + await userEvent.click(firstItem); + +}; + +export const ItemCollapsed = Template.bind({}); +ItemCollapsed.args = { + items:[ + { id: '1', label: 'Item 1', content: 'Content of Item 1' }, + { id: '2', label: 'Item 2', content: 'Content of Item 2' }, + { id: '3', label: 'Item 3', content: 'Content of Item 3' }, + ], +}; + +export const Hover = Template.bind({}); +Hover.args = { + items: [ + { id: '1', label: 'Item 1', content: 'Content of Item 1' }, + { id: '2', label: 'Item 2', content: 'Content of Item 2' }, + { id: '3', label: 'Item 3', content: 'Content of Item 3' } + ] +} +Hover.play = async({canvasElement}) => { + const canvas = within(canvasElement); + const firstItem = canvas.getByText('Item 1') + await userEvent.hover(firstItem); +}; + +export const Selected = Template.bind({}); +Selected.args = { + items: [ + { id: '1', label: 'Item 1', content: 'Content of Item 1' }, + { id: '2', label: 'Item 2', content: 'Content of Item 2' }, + { id: '3', label: 'Item 3', content: 'Content of Item 3' } + ], +}; +Selected.play = async({canvasElement}) => { + const canvas = within(canvasElement); + const secondItem = await canvas.getByText('Item 2'); + await userEvent.click(secondItem); +}; \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/svelte/src/components/ExpandableList/ExpandableList.svelte b/pkgs/experimental/swarmakit/libs/svelte/src/components/ExpandableList/ExpandableList.svelte new file mode 100644 index 0000000000..70bf2df687 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/svelte/src/components/ExpandableList/ExpandableList.svelte @@ -0,0 +1,42 @@ + + +
      + {#each items as item (item.id)} +
    • toggleExpand(item.id)} role='menuitem' on:keydown={(e) => { if (e.key === 'Enter' || e.key === ' ') { toggleExpand(item.id); }}} + tabindex="0"> +
      selectItem(item.id)} + on:keydown ={(e)=>{if(e.key === 'Enter' || e.key === ' '){selectItem(item.id)}}} + role='menuitem' + tabindex="0" + > + {item.label} +
      + {#if item.id === $expandedItemId} +
      {item.content}
      + {/if} +
    • + {/each} +
    + + \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/svelte/src/components/FavoritesList/FavoritesList.css b/pkgs/experimental/swarmakit/libs/svelte/src/components/FavoritesList/FavoritesList.css new file mode 100644 index 0000000000..dae39ec589 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/svelte/src/components/FavoritesList/FavoritesList.css @@ -0,0 +1,45 @@ +:root { + --list-border: #ccc; + --list-item-bg: #fff; + --list-item-hover-bg: #f0f0f0; + --list-item-selected-bg: #e0e0e0; + --favorite-color: gold; + --non-favorite-color: #888; + --list-color: #333; +} + +.favorites-list { + list-style: none; + padding: 0; + margin: 0; +} + +.list-item { + border-bottom: 1px solid var(--list-border); + padding: 10px; + display: flex; + justify-content: space-between; + align-items: center; + background-color: var(--list-item-bg); + cursor: pointer; +} + +.list-item:hover { + background-color: var(--list-item-hover-bg); +} + +.favorite-toggle { + background: none; + border: none; + cursor: pointer; + font-size: 1.5em; + color: var(--non-favorite-color); +} + +.favorite-toggle:hover { + color: var(--favorite-color); +} + +.list-item.selected { + background-color: var(--list-item-selected-bg); +} \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/svelte/src/components/FavoritesList/FavoritesList.stories.ts b/pkgs/experimental/swarmakit/libs/svelte/src/components/FavoritesList/FavoritesList.stories.ts new file mode 100644 index 0000000000..e98a227f6c --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/svelte/src/components/FavoritesList/FavoritesList.stories.ts @@ -0,0 +1,84 @@ +import { userEvent } from '@storybook/test'; +import { within } from '@storybook/test'; +import FavoritesList from './FavoritesList.svelte'; +import type { Meta, StoryFn } from '@storybook/svelte'; + +const meta: Meta = { + title: 'component/Lists/FavoritesList', + component: FavoritesList, + tags: ['autodocs'], + argTypes: { + items: { control: 'object' } + }, + parameters: { + layout: 'centered', + viewport: { + viewports: { + smallMobile: { name: 'Small Mobile', styles: { width: '320px', height: '568px' } }, + largeMobile: { name: 'Large Mobile', styles: { width: '414px', height: '896px' } }, + tablet: { name: 'Tablet', styles: { width: '768px', height: '1024px' } }, + desktop: { name: 'Desktop', styles: { width: '1024px', height: '768px' } }, + } + } + } +}; + +export default meta; + +const Template:StoryFn = (args) => ({ + Component:FavoritesList, + props:args, +}); + +export const Default = Template.bind({}); +Default.args = { + items: [ + { id: '1', label: 'Item 1', isFavorite: false }, + { id: '2', label: 'Item 2', isFavorite: true }, + { id: '3', label: 'Item 3', isFavorite: false }, + ] +}; + +export const Starred = Template.bind({}); +Starred.args = { + items: [ + { id: '1', label: 'Item 1', isFavorite: true }, + { id: '2', label: 'Item 2', isFavorite: true }, + { id: '3', label: 'Item 3', isFavorite: true }, + ] +}; + +export const Unstarred = Template.bind({}); +Unstarred.args = { + items: [ + { id: '1', label: 'Item 1', isFavorite: false }, + { id: '2', label: 'Item 2', isFavorite: false }, + { id: '3', label: 'Item 3', isFavorite: false } + ] +}; + +export const Hover = Template.bind({}); +Hover.args = { + items: [ + { id: '1', label: 'Item 1', isFavorite: false }, + { id: '2', label: 'Item 2', isFavorite: true }, + { id: '3', label: 'Item 3', isFavorite: false }, + ] +}; +Hover.play = async({canvasElement}) => { + const canvas = within(canvasElement); + await userEvent.hover(canvas.getByText('Item 1')); +}; + +export const Selected = Template.bind({}); +Selected.args = { + items: [ + { id: '1', label: 'Item 1', isFavorite: false }, + { id: '2', label: 'Item 2', isFavorite: true }, + { id: '3', label: 'Item 3', isFavorite: false }, + ] +}; +Selected.play = async({canvasElement}) => { + const canvas = within(canvasElement); + await userEvent.click(canvas.getByText('Item 2')); +}; \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/svelte/src/components/FavoritesList/FavoritesList.svelte b/pkgs/experimental/swarmakit/libs/svelte/src/components/FavoritesList/FavoritesList.svelte new file mode 100644 index 0000000000..440a183a1c --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/svelte/src/components/FavoritesList/FavoritesList.svelte @@ -0,0 +1,41 @@ + + +
      + {#each items as item (item.id)} +
    • selectItem(item.id)} + on:keydown={(e)=>{if(e.key === 'Enter' || e.key === ' ') {selectItem(item.id)}}} + role="menuitem" + tabindex='0' + > + {item.label} + +
    • + {/each} +
    + + \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/svelte/src/components/FileInputWithPreview/FileInputWithPreview.css b/pkgs/experimental/swarmakit/libs/svelte/src/components/FileInputWithPreview/FileInputWithPreview.css new file mode 100644 index 0000000000..6ff69691c5 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/svelte/src/components/FileInputWithPreview/FileInputWithPreview.css @@ -0,0 +1,37 @@ +.file-input-with-preview input[type="file"] { + margin: 5px; + padding: 8px; + border: 1px solid #ccc; + border-radius: 4px; + font-size: 14px; +} + +.file-input-with-preview .preview { + margin-top: 10px; + display: flex; + align-items: center; +} + +.file-input-with-preview .preview img { + max-width: 100px; + margin-right: 10px; +} + +.file-input-with-preview .preview button { + padding: 5px 10px; + background-color: #007bff; + color: #fff; + border: none; + border-radius: 4px; + cursor: pointer; +} + +.file-input-with-preview .preview button:disabled { + background-color: #e9ecef; + cursor: not-allowed; +} + +.file-input-with-preview .error { + color: red; + margin-top: 5px; +} \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/svelte/src/components/FileInputWithPreview/FileInputWithPreview.stories.ts b/pkgs/experimental/swarmakit/libs/svelte/src/components/FileInputWithPreview/FileInputWithPreview.stories.ts new file mode 100644 index 0000000000..896f59c14a --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/svelte/src/components/FileInputWithPreview/FileInputWithPreview.stories.ts @@ -0,0 +1,35 @@ +import FileInputWithPreview from './FileInputWithPreview.svelte'; + +export default { + title: 'Forms/FileInputWithPreview', + component: FileInputWithPreview, + tags: ['autodocs'], +}; + +export const FileUploaded = { + args: { + error: '', + disabled: false, + }, +}; + +export const PreviewDisplayed = { + args: { + error: '', + disabled: false, + }, +}; + +export const Error = { + args: { + error: 'File format not supported', + disabled: false, + }, +}; + +export const Disabled = { + args: { + error: '', + disabled: true, + }, +}; \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/svelte/src/components/FileInputWithPreview/FileInputWithPreview.svelte b/pkgs/experimental/swarmakit/libs/svelte/src/components/FileInputWithPreview/FileInputWithPreview.svelte new file mode 100644 index 0000000000..3ed8db5f32 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/svelte/src/components/FileInputWithPreview/FileInputWithPreview.svelte @@ -0,0 +1,89 @@ + + +
    + + {#if previewUrl} +
    + File preview + +
    + {/if} + {#if error} +
    {error}
    + {/if} +
    + + \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/svelte/src/components/FileUpload/FileUpload.css b/pkgs/experimental/swarmakit/libs/svelte/src/components/FileUpload/FileUpload.css new file mode 100644 index 0000000000..2a7730852f --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/svelte/src/components/FileUpload/FileUpload.css @@ -0,0 +1,38 @@ +:root { + --progress-bar-bg: #f3f3f3; + --progress-bar-fill: #4caf50; + --drop-area-border: #ccc; + --drop-area-hover-border: #007bff; +} + +.file-upload { + border: 1px solid var(--drop-area-border); + padding: 1rem; + text-align: center; + cursor: pointer; + transition: border-color 0.3s; +} + +.drop-area { + padding: 1rem; + border: 2px dashed var(--drop-area-border); + margin-bottom: 1rem; + transition: border-color 0.3s; +} + +.file-input { + display: none; +} + +.progress-bar { + background-color: var(--progress-bar-bg); + border-radius: 5px; + overflow: hidden; + margin-top: 1rem; +} + +.progress { + height: 10px; + background-color: var(--progress-bar-fill); + transition: width 0.3s; +} \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/svelte/src/components/FileUpload/FileUpload.stories.ts b/pkgs/experimental/swarmakit/libs/svelte/src/components/FileUpload/FileUpload.stories.ts new file mode 100644 index 0000000000..2cff233152 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/svelte/src/components/FileUpload/FileUpload.stories.ts @@ -0,0 +1,108 @@ +import FileUpload from './FileUpload.svelte'; +import type { Meta, StoryFn } from '@storybook/svelte'; + +const meta: Meta = { + title: 'component/Forms/FileUpload', + component: FileUpload, + tags: ['autodocs'], + argTypes: { + multiple: { control: 'boolean' }, + uploadProgress: { control: 'number', min: 0, max: 100 }, + isDragAndDrop: { control: 'boolean' }, + }, + parameters: { + layout: 'centered', + viewport: { + viewports: { + smallMobile: { name: 'Small Mobile', styles: { width: '320px', height: '568px' } }, + largeMobile: { name: 'Large Mobile', styles: { width: '414px', height: '896px' } }, + tablet: { name: 'Tablet', styles: { width: '768px', height: '1024px' } }, + desktop: { name: 'Desktop', styles: { width: '1024px', height: '768px' } }, + } + } + } +}; + +export default meta; + +const Template:StoryFn = (args) => ({ + Component:FileUpload, + props:args, +}); + +export const Default = Template.bind({}); +Default.args = { + multiple: false, + uploadProgress: 0, + isDragAndDrop: false, +}; + +export const SingleFile = Template.bind({}); +SingleFile.args = { + multiple: false, + uploadProgress: 0, + isDragAndDrop: false, +}; + +export const MultipleFiles = Template.bind({}); +MultipleFiles.args = { + multiple: true, + uploadProgress: 0, + isDragAndDrop: false, +}; + +export const DragAndDrop = Template.bind({}); +DragAndDrop.args = { + multiple: false, + uploadProgress: 0, + isDragAndDrop: true, +}; + +export const Progress = Template.bind({}); +Progress.args = { + multiple: false, + uploadProgress: 50, + isDragAndDrop: false, +}; + +// type Story = StoryObj; + +// export const Default: Story = { +// args: { +// multiple: false, +// uploadProgress: 0, +// isDragAndDrop: false, +// } +// }; + +// export const SingleFile: Story = { +// args: { +// multiple: false, +// uploadProgress: 0, +// isDragAndDrop: false, +// } +// }; + +// export const MultipleFiles: Story = { +// args: { +// multiple: true, +// uploadProgress: 0, +// isDragAndDrop: false, +// } +// }; + +// export const DragAndDrop: Story = { +// args: { +// multiple: false, +// uploadProgress: 0, +// isDragAndDrop: true, +// } +// }; + +// export const Progress: Story = { +// args: { +// multiple: false, +// uploadProgress: 50, +// isDragAndDrop: false, +// } +// }; \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/svelte/src/components/FileUpload/FileUpload.svelte b/pkgs/experimental/swarmakit/libs/svelte/src/components/FileUpload/FileUpload.svelte new file mode 100644 index 0000000000..08f027ff4e --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/svelte/src/components/FileUpload/FileUpload.svelte @@ -0,0 +1,52 @@ + + +
    + {#if isDragAndDrop} +
    + Drag and drop files here or click to browse +
    + {/if} + + {#if uploadProgress > 0} +
    +
    +
    + {/if} +
    + + \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/svelte/src/components/FilterableList/FilterableList.css b/pkgs/experimental/swarmakit/libs/svelte/src/components/FilterableList/FilterableList.css new file mode 100644 index 0000000000..4c6324fe1b --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/svelte/src/components/FilterableList/FilterableList.css @@ -0,0 +1,63 @@ +:root { + --input-border: #ccc; + --input-focus-border: #007bff; + --button-bg: #007bff; + --button-color: #fff; + --button-hover-bg: #0056b3; + --list-border: #ccc; + --list-item-bg: #fff; + --list-color: #333; +} + +.filterable-list { + display: flex; + flex-direction: column; + gap: 10px; +} + +input[type="text"] { + padding: 8px; + border: 1px solid var(--input-border); + border-radius: 4px; + outline: none; +} + +input[type="text"]:focus { + border-color: var(--input-focus-border); +} + +button { + padding: 8px 12px; + background-color: var(--button-bg); + color: var(--button-color); + border: none; + border-radius: 4px; + cursor: pointer; +} + +button:hover { + background-color: var(--button-hover-bg); +} + +ul { + list-style: none; + padding: 0; + margin: 0; + border: 1px solid var(--list-border); + border-radius: 4px; +} + +li { + padding: 10px; + border-bottom: 1px solid var(--list-border); + background-color: var(--list-item-bg); +} + +li:last-child { + border-bottom: none; +} + +li.no-results { + text-align: center; + color: var(--list-color); +} \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/svelte/src/components/FilterableList/FilterableList.stories.ts b/pkgs/experimental/swarmakit/libs/svelte/src/components/FilterableList/FilterableList.stories.ts new file mode 100644 index 0000000000..f602172a4c --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/svelte/src/components/FilterableList/FilterableList.stories.ts @@ -0,0 +1,67 @@ +import FilterableList from './FilterableList.svelte'; +import type { Meta, StoryFn } from '@storybook/svelte'; +import { userEvent, within } from '@storybook/test' + +const meta: Meta = { + title: 'component/Lists/FilterableList', + component: FilterableList, + tags: ['autodocs'], + argTypes: { + items: { control: 'object' } + }, + parameters: { + layout: 'centered', + viewport: { + viewports: { + smallMobile: { name: 'Small Mobile', styles: { width: '320px', height: '568px' } }, + largeMobile: { name: 'Large Mobile', styles: { width: '414px', height: '896px' } }, + tablet: { name: 'Tablet', styles: { width: '768px', height: '1024px' } }, + desktop: { name: 'Desktop', styles: { width: '1024px', height: '768px' } }, + } + } + } +}; + +export default meta; + +const Template:StoryFn = (args) => ({ + Component:FilterableList, + props:args, +}); + +export const Default = Template.bind({}); +Default.args = { + items: ['Apple', 'Banana', 'Cherry', 'Date', 'Elderberry'], +}; + +export const FilterApplied = Template.bind({}); +FilterApplied.args = { + items: ['Apple', 'Banana', 'Cherry', 'Date', 'Elderberry'], +}; +FilterApplied.play = async({canvasElement}) => { + const canvas = within(canvasElement); + const input = canvas.getByPlaceholderText('Filter items...'); + await userEvent.type(input, 'Ba'); +}; + +export const NoResults = Template.bind({}); +NoResults.args = { + items: ['Apple', 'Banana', 'Cherry', 'Date', 'Elderberry'], +}; +NoResults.play = async({canvasElement}) => { + const canvas = within(canvasElement); + const input = canvas.getByPlaceholderText('Filter items...'); + await userEvent.type(input, 'Zucchini'); +}; + +export const ClearFilter = Template.bind({}); +ClearFilter.args = { + items: ['Apple', 'Banana', 'Cherry', 'Date', 'Elderberry'], +}; +ClearFilter.play = async({canvasElement}) => { + const canvas = within(canvasElement); + const input = canvas.getByPlaceholderText('Filter items...'); + await userEvent.type(input, 'Ch'); + const clearButton = canvas.getByRole('button', { name: 'Clear filter' }); + await userEvent.click(clearButton); +}; \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/svelte/src/components/FilterableList/FilterableList.svelte b/pkgs/experimental/swarmakit/libs/svelte/src/components/FilterableList/FilterableList.svelte new file mode 100644 index 0000000000..e60bd6e9b6 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/svelte/src/components/FilterableList/FilterableList.svelte @@ -0,0 +1,42 @@ + + +
    + + +
      + {#if $filteredItems.length > 0} + {#each $filteredItems as item (item)} +
    • {item}
    • + {/each} + {:else} +
    • No results found
    • + {/if} +
    +
    + + \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/svelte/src/components/GroupedList/GroupedList.css b/pkgs/experimental/swarmakit/libs/svelte/src/components/GroupedList/GroupedList.css new file mode 100644 index 0000000000..ce4a6ae2c0 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/svelte/src/components/GroupedList/GroupedList.css @@ -0,0 +1,55 @@ +:root { + --group-button-bg: #f0f0f0; + --group-button-hover-bg: #e0e0e0; + --group-border: #ccc; + --item-bg: #fff; + --item-hover-bg: #f8f8f8; + --item-selected-bg: #d0eaff; + --item-color: #333; +} + +.grouped-list { + display: flex; + flex-direction: column; + gap: 10px; +} + +.group { + border: 1px solid var(--group-border); + border-radius: 4px; + overflow: hidden; +} + +button { + width: 100%; + padding: 10px; + background-color: var(--group-button-bg); + border: none; + cursor: pointer; + text-align: left; +} + +button:hover { + background-color: var(--group-button-hover-bg); +} + +ul { + list-style: none; + padding: 0; + margin: 0; +} + +li { + padding: 10px; + background-color: var(--item-bg); + cursor: pointer; +} + +li:hover { + background-color: var(--item-hover-bg); +} + +li.selected { + background-color: var(--item-selected-bg); + color: var(--item-color); +} \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/svelte/src/components/GroupedList/GroupedList.stories.ts b/pkgs/experimental/swarmakit/libs/svelte/src/components/GroupedList/GroupedList.stories.ts new file mode 100644 index 0000000000..d39ea6a5d7 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/svelte/src/components/GroupedList/GroupedList.stories.ts @@ -0,0 +1,80 @@ +import GroupedList from './GroupedList.svelte'; +import type { Meta, StoryFn } from '@storybook/svelte'; +import {userEvent, within} from '@storybook/test'; + +const meta: Meta = { + title: 'component/Lists/GroupedList', + component: GroupedList, + tags: ['autodocs'], + argTypes: { + groups: { control: 'object' } + }, + parameters: { + layout: 'centered', + viewport: { + viewports: { + smallMobile: { name: 'Small Mobile', styles: { width: '320px', height: '568px' } }, + largeMobile: { name: 'Large Mobile', styles: { width: '414px', height: '896px' } }, + tablet: { name: 'Tablet', styles: { width: '768px', height: '1024px' } }, + desktop: { name: 'Desktop', styles: { width: '1024px', height: '768px' } }, + } + } + } +}; + +export default meta; + +const Template:StoryFn = (args) => ({ + Component: GroupedList, + props:args, +}); + +export const Default = Template.bind({}); +Default.args = { + groups: [ + { title: 'Fruits', items: ['Apple', 'Banana', 'Cherry'], expanded: true }, + { title: 'Vegetables', items: ['Carrot', 'Lettuce', 'Peas'], expanded: false }, + ] +}; + +export const GroupExpanded = Template.bind({}); +GroupExpanded.args = { + groups: [ + { title: 'Fruits', items: ['Apple', 'Banana', 'Cherry'], expanded: true }, + { title: 'Vegetables', items: ['Carrot', 'Lettuce', 'Peas'], expanded: false }, + ] +}; + +export const GroupCollapsed = Template.bind({}); +GroupCollapsed.args = { + groups: [ + { title: 'Fruits', items: ['Apple', 'Banana', 'Cherry'], expanded: false }, + { title: 'Vegetables', items: ['Carrot', 'Lettuce', 'Peas'], expanded: false }, + ] +}; + +export const ItemHover = Template.bind({}); +ItemHover.args = { + groups: [ + { title: 'Fruits', items: ['Apple', 'Banana', 'Cherry'], expanded: true }, + { title: 'Vegetables', items: ['Carrot', 'Lettuce', 'Peas'], expanded: false }, + ] +}; +ItemHover.play = async({canvasElement}) => { + const canvas = within(canvasElement); + const listItem = canvas.getByText('Banana'); + await userEvent.hover(listItem); +}; + +export const ItemSelected = Template.bind({}); +ItemSelected.args = { + groups: [ + { title: 'Fruits', items: ['Apple', 'Banana', 'Cherry'], expanded: true }, + { title: 'Vegetables', items: ['Carrot', 'Lettuce', 'Peas'], expanded: false }, + ] +}; +ItemSelected.play = async({canvasElement}) => { + const canvas = within(canvasElement); + const listItem = canvas.getByText('Banana'); + await userEvent.click(listItem); +}; \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/svelte/src/components/GroupedList/GroupedList.svelte b/pkgs/experimental/swarmakit/libs/svelte/src/components/GroupedList/GroupedList.svelte new file mode 100644 index 0000000000..e7fc3e38a4 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/svelte/src/components/GroupedList/GroupedList.svelte @@ -0,0 +1,49 @@ + + +
    + {#each groups as group (group.title)} +
    + + {#if group.expanded} +
      + {#each group.items as item (item)} +
    • selectItem(item)} + on:keydown={(e)=>{if(e.key === 'Enter' || e.key === ' '){selectItem(item)}}} + on:mouseover={() => selectItem(item)} + on:focus= {()=> selectItem(item)} + role='menuitem'> + {item} +
    • + {/each} +
    + {/if} +
    + {/each} +
    + + \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/svelte/src/components/IconButton/IconButton.css b/pkgs/experimental/swarmakit/libs/svelte/src/components/IconButton/IconButton.css new file mode 100644 index 0000000000..41066adf94 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/svelte/src/components/IconButton/IconButton.css @@ -0,0 +1,34 @@ +:root { + --icon-button-bg: transparent; + --icon-button-hover-bg: rgba(0, 0, 0, 0.1); + --icon-button-active-bg: rgba(0, 0, 0, 0.2); + --icon-button-disabled-bg: rgba(0, 0, 0, 0.05); + --icon-size: 24px; +} + +.icon-button { + background-color: var(--icon-button-bg); + border: none; + cursor: pointer; + padding: 0.5rem; + border-radius: 50%; + transition: background-color 0.3s; +} + +.icon-button img { + width: var(--icon-size); + height: var(--icon-size); +} + +.icon-button:hover:not(:disabled) { + background-color: var(--icon-button-hover-bg); +} + +.icon-button:active:not(:disabled) { + background-color: var(--icon-button-active-bg); +} + +.icon-button:disabled { + background-color: var(--icon-button-disabled-bg); + cursor: not-allowed; +} \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/svelte/src/components/IconButton/IconButton.stories.ts b/pkgs/experimental/swarmakit/libs/svelte/src/components/IconButton/IconButton.stories.ts new file mode 100644 index 0000000000..b9dbe8c886 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/svelte/src/components/IconButton/IconButton.stories.ts @@ -0,0 +1,57 @@ +import IconButton from './IconButton.svelte'; +import type { Meta, StoryFn } from '@storybook/svelte'; + +const meta: Meta = { + title: 'component/Buttons/IconButton', + component: IconButton, + tags: ['autodocs'], + argTypes: { + icon: { control: 'text' }, + disabled: { control: 'boolean' }, + onClick: { action: 'clicked' }, + }, + parameters: { + layout: 'centered', + viewport: { + viewports: { + smallMobile: { name: 'Small Mobile', styles: { width: '320px', height: '568px' } }, + largeMobile: { name: 'Large Mobile', styles: { width: '414px', height: '896px' } }, + tablet: { name: 'Tablet', styles: { width: '768px', height: '1024px' } }, + desktop: { name: 'Desktop', styles: { width: '1024px', height: '768px' } }, + } + } + } +}; + +export default meta; + +const Template:StoryFn = (args) => ({ + Component:IconButton, + props:args, +}); + +export const Default = Template.bind({}); +Default.args = { + icon: 'path/to/icon.svg', + disabled: false, + ariaLabel: 'Default Icon Button', +}; + +export const Active = Template.bind({}); +Active.args = { + icon: 'path/to/icon.svg', + ariaLabel: 'Active Icon Button', +}; + +export const Hover = Template.bind({}); +Hover.args = { + icon: 'path/to/icon.svg', + ariaLabel: 'Hover Icon Button', +}; + +export const Disabled = Template.bind({}); +Disabled.args = { + icon: 'path/to/icon.svg', + disabled: true, + ariaLabel: 'Disabled Icon Button', +}; \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/svelte/src/components/IconButton/IconButton.svelte b/pkgs/experimental/swarmakit/libs/svelte/src/components/IconButton/IconButton.svelte new file mode 100644 index 0000000000..8614db4e4e --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/svelte/src/components/IconButton/IconButton.svelte @@ -0,0 +1,19 @@ + + + + + \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/svelte/src/components/ImageSlider/ImageSlider.css b/pkgs/experimental/swarmakit/libs/svelte/src/components/ImageSlider/ImageSlider.css new file mode 100644 index 0000000000..b4c3097e6c --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/svelte/src/components/ImageSlider/ImageSlider.css @@ -0,0 +1,35 @@ +.image-slider { + position: relative; + width: 100%; + max-width: 800px; + margin: auto; + outline: none; +} + +.slider-image { + width: 100%; + height: auto; + display: block; +} + +.controls { + position: absolute; + top: 50%; + width: 100%; + display: flex; + justify-content: space-between; + transform: translateY(-50%); +} + +button { + background-color: rgba(0, 0, 0, 0.5); + color: white; + border: none; + padding: 10px; + cursor: pointer; + border-radius: 4px; +} + +button:hover { + background-color: rgba(0, 0, 0, 0.7); +} \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/svelte/src/components/ImageSlider/ImageSlider.stories.ts b/pkgs/experimental/swarmakit/libs/svelte/src/components/ImageSlider/ImageSlider.stories.ts new file mode 100644 index 0000000000..e2f05e0baf --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/svelte/src/components/ImageSlider/ImageSlider.stories.ts @@ -0,0 +1,53 @@ +import ImageSlider from './ImageSlider.svelte'; +import type { Meta, StoryFn } from '@storybook/svelte'; + +const meta: Meta = { + title: 'Components/Media/ImageSlider', + component: ImageSlider, + tags: ['autodocs'], + argTypes: { + images: { + control: { type: 'object' }, + }, + activeIndex: { + control: { type: 'number' }, + }, + }, +}; + +export default meta; + +const Template: StoryFn = (args) => ({ + Component: ImageSlider, + props: args, +}); + +export const Default = Template.bind({}); +Default.args = { + images: [ + 'https://via.placeholder.com/800x450?text=Image+1', + 'https://via.placeholder.com/800x450?text=Image+2', + 'https://via.placeholder.com/800x450?text=Image+3', + ], + activeIndex: 0, +}; + +export const Active = Template.bind({}); +Active.args = { + images: [ + 'https://via.placeholder.com/800x450?text=Image+1', + 'https://via.placeholder.com/800x450?text=Image+2', + 'https://via.placeholder.com/800x450?text=Image+3', + ], + activeIndex: 1, +}; + +export const Hover = Template.bind({}); +Hover.args = { + images: [ + 'https://via.placeholder.com/800x450?text=Image+1', + 'https://via.placeholder.com/800x450?text=Image+2', + 'https://via.placeholder.com/800x450?text=Image+3', + ], + activeIndex: 0, +}; \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/svelte/src/components/ImageSlider/ImageSlider.svelte b/pkgs/experimental/swarmakit/libs/svelte/src/components/ImageSlider/ImageSlider.svelte new file mode 100644 index 0000000000..ba71f93034 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/svelte/src/components/ImageSlider/ImageSlider.svelte @@ -0,0 +1,34 @@ + + + + + \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/svelte/src/components/InteractivePollResults/InteractivePollResults.css b/pkgs/experimental/swarmakit/libs/svelte/src/components/InteractivePollResults/InteractivePollResults.css new file mode 100644 index 0000000000..b71fffe470 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/svelte/src/components/InteractivePollResults/InteractivePollResults.css @@ -0,0 +1,55 @@ +:root { + --poll-bar-background: #4caf50; + --poll-option-background: #f1f1f1; + --poll-label-color: #333333; + --total-votes-color: #666666; + --poll-font-size: 16px; + --poll-bar-height: 24px; + --poll-bar-border-radius: 4px; + --poll-padding: 8px; +} + +.poll-results { + display: flex; + flex-direction: column; + gap: 8px; + padding: var(--poll-padding); +} + +.poll-option { + display: flex; + align-items: center; + gap: 8px; + background-color: var(--poll-option-background); + padding: var(--poll-padding); + border-radius: var(--poll-bar-border-radius); +} + +.poll-label { + flex: 1; + color: var(--poll-label-color); + font-size: var(--poll-font-size); +} + +.poll-bar-container { + flex: 2; + background-color: #e0e0e0; + border-radius: var(--poll-bar-border-radius); + overflow: hidden; +} + +.poll-bar { + height: var(--poll-bar-height); + background-color: var(--poll-bar-background); +} + +.poll-votes { + font-size: var(--poll-font-size); + color: var(--poll-label-color); +} + +.total-votes { + margin-top: 8px; + font-size: var(--poll-font-size); + color: var(--total-votes-color); +} \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/svelte/src/components/InteractivePollResults/InteractivePollResults.stories.ts b/pkgs/experimental/swarmakit/libs/svelte/src/components/InteractivePollResults/InteractivePollResults.stories.ts new file mode 100644 index 0000000000..7bb239471b --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/svelte/src/components/InteractivePollResults/InteractivePollResults.stories.ts @@ -0,0 +1,67 @@ +import InteractivePollResults from './InteractivePollResults.svelte'; +import type { Meta, StoryFn } from '@storybook/svelte'; + +const meta: Meta = { + title: 'component/Indicators/InteractivePollResults', + component: InteractivePollResults, + tags: ['autodocs'], + argTypes: { + options: { control: 'object' }, + totalVotes: { control: 'number' }, + }, + parameters: { + layout: 'centered', + viewport: { + viewports: { + smallMobile: { name: 'Small Mobile', styles: { width: '320px', height: '568px' } }, + largeMobile: { name: 'Large Mobile', styles: { width: '414px', height: '896px' } }, + tablet: { name: 'Tablet', styles: { width: '768px', height: '1024px' } }, + desktop: { name: 'Desktop', styles: { width: '1024px', height: '768px' } }, + } + } + } +}; + +export default meta; + +const Template:StoryFn = (args) => ({ + Component: InteractivePollResults, + props:args, +}); + +export const Default = Template.bind({}); +Default.args = { + options: [ + { label: 'Option B', votes: 30 }, + { label: 'Option A', votes: 50 }, + ], + totalVotes: 80, +}; + + +export const LiveResults = Template.bind({}); +LiveResults.args = { + options: [ + { label: 'Option B', votes: 70 }, + { label: 'Option A', votes: 30 }, + ], + totalVotes: 100, +}; + +export const Completed = Template.bind({}); +Completed.args = { + options: [ + { label: 'Option B', votes: 120 }, + { label: 'Option A', votes: 80 }, + ], + totalVotes: 200, +}; + +export const Closed = Template.bind({}); +Closed.args = { + options: [ + { label: 'Option B', votes: 50 }, + { label: 'Option A', votes: 50 }, + ], + totalVotes: 100, +}; \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/svelte/src/components/InteractivePollResults/InteractivePollResults.svelte b/pkgs/experimental/swarmakit/libs/svelte/src/components/InteractivePollResults/InteractivePollResults.svelte new file mode 100644 index 0000000000..dacd491e34 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/svelte/src/components/InteractivePollResults/InteractivePollResults.svelte @@ -0,0 +1,35 @@ + + +
    + {#each options as { label, votes }} +
    + {label} +
    +
    +
    + {votes} votes +
    + {/each} +
    Total Votes: {totalVotes}
    +
    + + \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/svelte/src/components/LoadingBarsWithSteps/LoadingBarsWithSteps.css b/pkgs/experimental/swarmakit/libs/svelte/src/components/LoadingBarsWithSteps/LoadingBarsWithSteps.css new file mode 100644 index 0000000000..ae8854d45c --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/svelte/src/components/LoadingBarsWithSteps/LoadingBarsWithSteps.css @@ -0,0 +1,45 @@ +:root { + --step-active-background: #4caf50; + --step-completed-background: #8bc34a; + --step-inactive-background: #e0e0e0; + --step-label-color: #333333; + --step-font-size: 16px; + --loading-bar-height: 8px; + --step-padding: 8px; + --step-bar-border-radius: 4px; +} + +.loading-bars-with-steps { + display: flex; + flex-direction: column; + gap: 16px; + padding: var(--step-padding); +} + +.step { + display: flex; + flex-direction: column; + gap: 4px; +} + +.step-label { + font-size: var(--step-font-size); + color: var(--step-label-color); +} + +.loading-bar { + height: var(--loading-bar-height); + border-radius: var(--step-bar-border-radius); +} + +.step.active .loading-bar { + background-color: var(--step-active-background); +} + +.step.completed .loading-bar { + background-color: var(--step-completed-background); +} + +.step.inactive .loading-bar { + background-color: var(--step-inactive-background); +} \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/svelte/src/components/LoadingBarsWithSteps/LoadingBarsWithSteps.stories.ts b/pkgs/experimental/swarmakit/libs/svelte/src/components/LoadingBarsWithSteps/LoadingBarsWithSteps.stories.ts new file mode 100644 index 0000000000..57b5305e27 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/svelte/src/components/LoadingBarsWithSteps/LoadingBarsWithSteps.stories.ts @@ -0,0 +1,65 @@ +import LoadingBarsWithSteps from './LoadingBarsWithSteps.svelte'; +import type { Meta, StoryFn } from '@storybook/svelte'; + +const meta: Meta = { + title: 'component/Indicators/LoadingBarsWithSteps', + component: LoadingBarsWithSteps, + tags: ['autodocs'], + argTypes: { + steps: { control: 'object' }, + }, + parameters: { + layout: 'centered', + viewport: { + viewports: { + smallMobile: { name: 'Small Mobile', styles: { width: '320px', height: '568px' } }, + largeMobile: { name: 'Large Mobile', styles: { width: '414px', height: '896px' } }, + tablet: { name: 'Tablet', styles: { width: '768px', height: '1024px' } }, + desktop: { name: 'Desktop', styles: { width: '1024px', height: '768px' } }, + } + } + } +}; + +export default meta; + +const Template:StoryFn = (args) => ({ + Component:LoadingBarsWithSteps, + props:args, +}); + +export const Default = Template.bind({}); +Default.args = { + steps: [ + { label: 'Step 1', status: 'active' }, + { label: 'Step 2', status: 'inactive' }, + { label: 'Step 3', status: 'inactive' }, + ], +}; + +export const StepActive = Template.bind({}); +StepActive.args = { + steps: [ + { label: 'Step 1', status: 'completed' }, + { label: 'Step 2', status: 'active' }, + { label: 'Step 3', status: 'inactive' }, + ], +}; + +export const StepCompleted = Template.bind({}); +StepCompleted.args = { + steps: [ + { label: 'Step 1', status: 'completed' }, + { label: 'Step 2', status: 'completed' }, + { label: 'Step 3', status: 'active' }, + ], +}; + +export const StepInActive = Template.bind({}); +StepInActive.args = { + steps: [ + { label: 'Step 1', status: 'completed' }, + { label: 'Step 2', status: 'completed' }, + { label: 'Step 3', status: 'inactive' }, + ], +}; \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/svelte/src/components/LoadingBarsWithSteps/LoadingBarsWithSteps.svelte b/pkgs/experimental/swarmakit/libs/svelte/src/components/LoadingBarsWithSteps/LoadingBarsWithSteps.svelte new file mode 100644 index 0000000000..fca853daf3 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/svelte/src/components/LoadingBarsWithSteps/LoadingBarsWithSteps.svelte @@ -0,0 +1,22 @@ + + +
    + {#each steps as { label, status }} +
    +
    {label}
    +
    +
    + {/each} +
    + + \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/svelte/src/components/LoadingSpinner/LoadingSpinner.css b/pkgs/experimental/swarmakit/libs/svelte/src/components/LoadingSpinner/LoadingSpinner.css new file mode 100644 index 0000000000..4546066703 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/svelte/src/components/LoadingSpinner/LoadingSpinner.css @@ -0,0 +1,28 @@ +:root { + --spinner-size: 50px; + --spinner-border-width: 5px; + --spinner-color: #3498db; + --spinner-background-color: #f3f3f3; +} + +.loading-spinner { + display: inline-flex; + align-items: center; + justify-content: center; + width: var(--spinner-size); + height: var(--spinner-size); +} + +.spinner { + border: var(--spinner-border-width) solid var(--spinner-background-color); + border-top: var(--spinner-border-width) solid var(--spinner-color); + border-radius: 50%; + width: 100%; + height: 100%; + animation: spin 1s linear infinite; +} + +@keyframes spin { + 0% { transform: rotate(0deg); } + 100% { transform: rotate(360deg); } +} \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/svelte/src/components/LoadingSpinner/LoadingSpinner.stories.ts b/pkgs/experimental/swarmakit/libs/svelte/src/components/LoadingSpinner/LoadingSpinner.stories.ts new file mode 100644 index 0000000000..0d0e834b33 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/svelte/src/components/LoadingSpinner/LoadingSpinner.stories.ts @@ -0,0 +1,44 @@ +import LoadingSpinner from './LoadingSpinner.svelte'; +import type { Meta, StoryFn } from '@storybook/svelte'; + +const meta: Meta = { + title: 'component/Indicators/LoadingSpinner', + component: LoadingSpinner, + tags: ['autodocs'], + argTypes: { + active: { control: 'boolean' }, + }, + parameters: { + layout: 'centered', + viewport: { + viewports: { + smallMobile: { name: 'Small Mobile', styles: { width: '320px', height: '568px' } }, + largeMobile: { name: 'Large Mobile', styles: { width: '414px', height: '896px' } }, + tablet: { name: 'Tablet', styles: { width: '768px', height: '1024px' } }, + desktop: { name: 'Desktop', styles: { width: '1024px', height: '768px' } }, + } + } + } +}; + +export default meta; + +const Template:StoryFn = (args) => ({ + Component: LoadingSpinner, + props:args, +}); + +export const Default = Template.bind({}); +Default.args = { + active:true, +}; + +export const Active = Template.bind({}); +Active.args = { + active:true, +}; + +export const Inactive = Template.bind({}); +Inactive.args = { + active:false, +}; \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/svelte/src/components/LoadingSpinner/LoadingSpinner.svelte b/pkgs/experimental/swarmakit/libs/svelte/src/components/LoadingSpinner/LoadingSpinner.svelte new file mode 100644 index 0000000000..821b593271 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/svelte/src/components/LoadingSpinner/LoadingSpinner.svelte @@ -0,0 +1,14 @@ + + +
    + {#if active} +
    + {/if} +
    + + \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/svelte/src/components/LoadmorebuttoninList/LoadmorebuttoninList.css b/pkgs/experimental/swarmakit/libs/svelte/src/components/LoadmorebuttoninList/LoadmorebuttoninList.css new file mode 100644 index 0000000000..75ad984990 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/svelte/src/components/LoadmorebuttoninList/LoadmorebuttoninList.css @@ -0,0 +1,57 @@ +:root { + --button-bg: #007bff; + --button-hover-bg: #0056b3; + --button-disabled-bg: #c0c0c0; + --text-color: #fff; + --list-border: #ccc; + --end-message-color: #888; +} + +.list-container { + display: flex; + flex-direction: column; + gap: 10px; + max-width: 300px; + margin: 0 auto; +} + +ul { + list-style: none; + padding: 0; + margin: 0; + border: 1px solid var(--list-border); + border-radius: 4px; +} + +li { + padding: 10px; + border-bottom: 1px solid var(--list-border); +} + +li:last-child { + border-bottom: none; +} + +button { + padding: 10px; + background-color: var(--button-bg); + color: var(--text-color); + border: none; + border-radius: 4px; + cursor: pointer; + transition: background-color 0.2s; +} + +button:hover { + background-color: var(--button-hover-bg); +} + +button:disabled { + background-color: var(--button-disabled-bg); + cursor: not-allowed; +} + +.end-message { + color: var(--end-message-color); + text-align: center; +} \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/svelte/src/components/LoadmorebuttoninList/LoadmorebuttoninList.stories.ts b/pkgs/experimental/swarmakit/libs/svelte/src/components/LoadmorebuttoninList/LoadmorebuttoninList.stories.ts new file mode 100644 index 0000000000..a53bd716a3 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/svelte/src/components/LoadmorebuttoninList/LoadmorebuttoninList.stories.ts @@ -0,0 +1,53 @@ +import LoadMoreButtonInList from './LoadmorebuttoninList.svelte' +import type { Meta, StoryFn } from '@storybook/svelte'; + +const meta: Meta = { + title: 'component/Lists/LoadMoreButtonInList', + component: LoadMoreButtonInList, + tags: ['autodocs'], + argTypes: { + items: { control: 'object' }, + loadMore: { action: 'loadMore' }, + isLoading: { control: 'boolean' }, + isEndOfList: { control: 'boolean' } + }, + parameters: { + layout: 'centered', + viewport: { + viewports: { + smallMobile: { name: 'Small Mobile', styles: { width: '320px', height: '568px' } }, + largeMobile: { name: 'Large Mobile', styles: { width: '414px', height: '896px' } }, + tablet: { name: 'Tablet', styles: { width: '768px', height: '1024px' } }, + desktop: { name: 'Desktop', styles: { width: '1024px', height: '768px' } }, + } + } + } +}; + +export default meta; + +const Template:StoryFn = (args) => ({ + Component: LoadMoreButtonInList, + props: args, +}); + +export const Default = Template.bind({}); +Default.args = { + items: ['Item 1', 'Item 2', 'Item 3'], + isLoading: false, + isEndOfList: false +}; + +export const Loading = Template.bind({}); +Loading.args = { + items: ['Item 1', 'Item 2', 'Item 3'], + isLoading: true, + isEndOfList: false +}; + +export const EndOfList = Template.bind({}); +EndOfList.args = { + items: ['Item 1', 'Item 2', 'Item 3'], + isLoading: false, + isEndOfList: true +}; \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/svelte/src/components/LoadmorebuttoninList/LoadmorebuttoninList.svelte b/pkgs/experimental/swarmakit/libs/svelte/src/components/LoadmorebuttoninList/LoadmorebuttoninList.svelte new file mode 100644 index 0000000000..4a8dc37b47 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/svelte/src/components/LoadmorebuttoninList/LoadmorebuttoninList.svelte @@ -0,0 +1,41 @@ + + +
    +
      + {#each items as item (item)} +
    • {item}
    • + {/each} +
    + {#if !isEndOfList} + + {:else} +

    End of List

    + {/if} +
    + + \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/svelte/src/components/MultiselectList/MultiselectList.css b/pkgs/experimental/swarmakit/libs/svelte/src/components/MultiselectList/MultiselectList.css new file mode 100644 index 0000000000..d38430eb66 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/svelte/src/components/MultiselectList/MultiselectList.css @@ -0,0 +1,43 @@ +:root { + --item-bg: #fff; + --item-hover-bg: #f0f0f0; + --item-selected-bg: #007bff; + --item-disabled-bg: #e0e0e0; + --text-color: #333; + --selected-text-color: #fff; + --border-color: #ccc; +} + +.multiselect-list { + display: flex; + flex-direction: column; + max-width: 300px; + margin: 0 auto; +} + +.list-item { + padding: 10px; + background-color: var(--item-bg); + color: var(--text-color); + border-bottom: 1px solid var(--border-color); + cursor: pointer; + transition: background-color 0.2s; +} + +.list-item:hover { + background-color: var(--item-hover-bg); +} + +.list-item.selected { + background-color: var(--item-selected-bg); + color: var(--selected-text-color); +} + +.list-item[aria-disabled="true"] { + background-color: var(--item-disabled-bg); + cursor: not-allowed; +} + +.list-item:last-child { + border-bottom: none; +} \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/svelte/src/components/MultiselectList/MultiselectList.stories.ts b/pkgs/experimental/swarmakit/libs/svelte/src/components/MultiselectList/MultiselectList.stories.ts new file mode 100644 index 0000000000..304d8dcad8 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/svelte/src/components/MultiselectList/MultiselectList.stories.ts @@ -0,0 +1,80 @@ +import MultiselectList from './MultiselectList.svelte'; +import type { Meta, StoryFn } from '@storybook/svelte'; + +const meta: Meta = { + title: 'component/Lists/MultiselectList', + component: MultiselectList, + tags: ['autodocs'], + argTypes: { + items: { control: 'object' }, + disabled: { control: 'boolean' } + }, + parameters: { + layout: 'centered', + viewport: { + viewports: { + smallMobile: { name: 'Small Mobile', styles: { width: '320px', height: '568px' } }, + largeMobile: { name: 'Large Mobile', styles: { width: '414px', height: '896px' } }, + tablet: { name: 'Tablet', styles: { width: '768px', height: '1024px' } }, + desktop: { name: 'Desktop', styles: { width: '1024px', height: '768px' } }, + } + } + } +}; + +export default meta; + +const Template:StoryFn = (args) => ({ + Component:MultiselectList, + props:args, +}); + +export const Default = Template.bind({}); +Default.args = { + items:[ + { id: '1', label: 'Option 1', selected: false }, + { id: '2', label: 'Option 2', selected: false }, + { id: '3', label: 'Option 3', selected: false } + ], + disabled:false, +}; + +export const ItemSelected = Template.bind({}); +ItemSelected.args = { + items:[ + { id: '1', label: 'Option 1', selected: true }, + { id: '2', label: 'Option 2', selected: false }, + { id: '3', label: 'Option 3', selected: false } + ], + disabled:false, +}; + +export const ItemDeselected = Template.bind({}); +ItemDeselected.args = { + items:[ + { id: '1', label: 'Option 1', selected: false }, + { id: '2', label: 'Option 2', selected: true }, + { id: '3', label: 'Option 3', selected: false } + ], + disabled:false, +}; + +export const Disabled = Template.bind({}); +Disabled.args = { + items:[ + { id: '1', label: 'Option 1', selected: false }, + { id: '2', label: 'Option 2', selected: false }, + { id: '3', label: 'Option 3', selected: false } + ], + disabled:true, +}; + +export const Hover = Template.bind({}); +Hover.args = { + items:[ + { id: '1', label: 'Option 1', selected: false }, + { id: '2', label: 'Option 2', selected: false }, + { id: '3', label: 'Option 3', selected: false } + ], + disabled:true, +}; \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/svelte/src/components/MultiselectList/MultiselectList.svelte b/pkgs/experimental/swarmakit/libs/svelte/src/components/MultiselectList/MultiselectList.svelte new file mode 100644 index 0000000000..6a9a9163bf --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/svelte/src/components/MultiselectList/MultiselectList.svelte @@ -0,0 +1,32 @@ + + +
    + {#each items as { id, label, selected } (id)} +
    toggleSelect(id)} + aria-selected={selected} + aria-disabled={disabled} + tabindex={disabled ? -1 : 0} + on:keydown={(e)=>{if (e.key === 'Enter' || e.key === ' '){toggleSelect(id)}}} + role = "tab" + > + {label} +
    + {/each} +
    + + \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/svelte/src/components/NotificationBellIcon/NotificationBellIcon.css b/pkgs/experimental/swarmakit/libs/svelte/src/components/NotificationBellIcon/NotificationBellIcon.css new file mode 100644 index 0000000000..8b91eae5e1 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/svelte/src/components/NotificationBellIcon/NotificationBellIcon.css @@ -0,0 +1,32 @@ +:root { + --bell-size: 24px; + --bell-color: #333; + --notification-dot-size: 8px; + --notification-dot-color: #e74c3c; +} + +.notification-bell { + position: relative; + display: inline-flex; + align-items: center; + justify-content: center; + width: var(--bell-size); + height: var(--bell-size); + color: var(--bell-color); + cursor: pointer; +} + +.bell-icon { + width: 100%; + height: 100%; +} + +.notification-dot { + position: absolute; + top: 0; + right: 0; + width: var(--notification-dot-size); + height: var(--notification-dot-size); + background-color: var(--notification-dot-color); + border-radius: 50%; +} \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/svelte/src/components/NotificationBellIcon/NotificationBellIcon.stories.ts b/pkgs/experimental/swarmakit/libs/svelte/src/components/NotificationBellIcon/NotificationBellIcon.stories.ts new file mode 100644 index 0000000000..a7d3c242d6 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/svelte/src/components/NotificationBellIcon/NotificationBellIcon.stories.ts @@ -0,0 +1,54 @@ +import NotificationBellIcon from './NotificationBellIcon.svelte'; +import type { Meta, StoryFn } from '@storybook/svelte'; + +const meta: Meta = { + title: 'component/Indicators/NotificationBellIcon', + component: NotificationBellIcon, + tags: ['autodocs'], + argTypes: { + hasNotifications: { control: 'boolean' }, + dismissed: { control: 'boolean' }, + }, + parameters: { + layout: 'centered', + viewport: { + viewports: { + smallMobile: { name: 'Small Mobile', styles: { width: '320px', height: '568px' } }, + largeMobile: { name: 'Large Mobile', styles: { width: '414px', height: '896px' } }, + tablet: { name: 'Tablet', styles: { width: '768px', height: '1024px' } }, + desktop: { name: 'Desktop', styles: { width: '1024px', height: '768px' } }, + } + } + } +}; + +export default meta; + +const Template:StoryFn = (args) => ({ + Component: NotificationBellIcon, + props:args, +}); + +export const Default = Template.bind({}); +Default.args = { + hasNotifications:false, + dismissed:false, +}; + +export const NoNotifications = Template.bind({}); +NoNotifications.args = { + hasNotifications:false, + dismissed:true, +}; + +export const NewNotifications = Template.bind({}); +NewNotifications.args = { + hasNotifications:true, + dismissed:false, +}; + +export const Dismissed = Template.bind({}); +Dismissed.args = { + hasNotifications:true, + dismissed:true, +}; \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/svelte/src/components/NotificationBellIcon/NotificationBellIcon.svelte b/pkgs/experimental/swarmakit/libs/svelte/src/components/NotificationBellIcon/NotificationBellIcon.svelte new file mode 100644 index 0000000000..bb61cd769c --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/svelte/src/components/NotificationBellIcon/NotificationBellIcon.svelte @@ -0,0 +1,18 @@ + + +
    + + {#if hasNotifications && !dismissed} + + {/if} +
    + + \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/svelte/src/components/NumberInputWithIncrement/NumberInputWithIncrement.css b/pkgs/experimental/swarmakit/libs/svelte/src/components/NumberInputWithIncrement/NumberInputWithIncrement.css new file mode 100644 index 0000000000..befa35dfc8 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/svelte/src/components/NumberInputWithIncrement/NumberInputWithIncrement.css @@ -0,0 +1,35 @@ +:root { + --button-bg: #f0f0f0; + --button-bg-hover: #e0e0e0; + --button-bg-disabled: #d0d0d0; + --input-border: #ccc; +} + +.number-input { + display: flex; + align-items: center; +} + +button { + background-color: var(--button-bg); + border: none; + padding: 0.5rem; + cursor: pointer; + transition: background-color 0.3s; +} + +button:hover:not(:disabled) { + background-color: var(--button-bg-hover); +} + +button:disabled { + background-color: var(--button-bg-disabled); + cursor: not-allowed; +} + +input[type="number"] { + width: 3rem; + text-align: center; + border: 1px solid var(--input-border); + margin: 0 0.5rem; +} \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/svelte/src/components/NumberInputWithIncrement/NumberInputWithIncrement.stories.ts b/pkgs/experimental/swarmakit/libs/svelte/src/components/NumberInputWithIncrement/NumberInputWithIncrement.stories.ts new file mode 100644 index 0000000000..645524fbe7 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/svelte/src/components/NumberInputWithIncrement/NumberInputWithIncrement.stories.ts @@ -0,0 +1,83 @@ +import { userEvent, within } from '@storybook/test'; +import NumberInputWithIncrement from './NumberInputWithIncrement.svelte'; +import type { Meta, StoryFn } from '@storybook/svelte'; + +const meta: Meta = { + title: 'component/Forms/NumberInputWithIncrement', + component: NumberInputWithIncrement, + tags: ['autodocs'], + argTypes: { + value: { control: 'number' }, + min: { control: 'number' }, + max: { control: 'number' }, + step: { control: 'number' }, + disabled: { control: 'boolean' }, + }, + parameters: { + layout: 'centered', + viewport: { + viewports: { + smallMobile: { name: 'Small Mobile', styles: { width: '320px', height: '568px' } }, + largeMobile: { name: 'Large Mobile', styles: { width: '414px', height: '896px' } }, + tablet: { name: 'Tablet', styles: { width: '768px', height: '1024px' } }, + desktop: { name: 'Desktop', styles: { width: '1024px', height: '768px' } }, + } + } + } +}; + +export default meta; + +const Template:StoryFn = (args) => ({ + Component:NumberInputWithIncrement, + props:args, +}); + +export const Default = Template.bind({}); +Default.args = { + value: 0, + min: 0, + max: 100, + step: 1, + disabled: false, +}; + +export const Increment = Template.bind({}); +Increment.args = { + value: 5, + min: 0, + max: 10, + step: 1, + disabled: false, +}; +Increment.play = async({canvasElement}) => { + const canvas = within(canvasElement); + + const incrementButton = await canvas.getByText('+'); + + await userEvent.click(incrementButton); +}; + +export const Decrement = Template.bind({}); +Decrement.args = { + value: 5, + min: 0, + max: 10, + step: 1, + disabled: false, +}; +Decrement.play = async({canvasElement}) => { + const canvas = within(canvasElement); + + const decrementButton = await canvas.getByText('-'); + await userEvent.click(decrementButton) +}; + +export const Disabled = Template.bind({}); +Disabled.args = { + value: 5, + min: 0, + max: 10, + step: 1, + disabled: true, +}; \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/svelte/src/components/NumberInputWithIncrement/NumberInputWithIncrement.svelte b/pkgs/experimental/swarmakit/libs/svelte/src/components/NumberInputWithIncrement/NumberInputWithIncrement.svelte new file mode 100644 index 0000000000..8fb1772f0c --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/svelte/src/components/NumberInputWithIncrement/NumberInputWithIncrement.svelte @@ -0,0 +1,40 @@ + + +
    + + + +
    + + \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/svelte/src/components/NumberedList/NumberedList.css b/pkgs/experimental/swarmakit/libs/svelte/src/components/NumberedList/NumberedList.css new file mode 100644 index 0000000000..3cbb411619 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/svelte/src/components/NumberedList/NumberedList.css @@ -0,0 +1,43 @@ +:root { + --item-bg: #fff; + --item-hover-bg: #f0f0f0; + --item-selected-bg: #007bff; + --item-disabled-bg: #e0e0e0; + --text-color: #333; + --selected-text-color: #fff; + --border-color: #ccc; +} + +.numbered-list { + padding: 0; + list-style: decimal inside; + max-width: 300px; + margin: 0 auto; +} + +.list-item { + padding: 10px; + background-color: var(--item-bg); + color: var(--text-color); + border-bottom: 1px solid var(--border-color); + cursor: pointer; + transition: background-color 0.2s; +} + +.list-item:hover { + background-color: var(--item-hover-bg); +} + +.list-item.selected { + background-color: var(--item-selected-bg); + color: var(--selected-text-color); +} + +.list-item[aria-disabled="true"] { + background-color: var(--item-disabled-bg); + cursor: not-allowed; +} + +.list-item:last-child { + border-bottom: none; +} \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/svelte/src/components/NumberedList/NumberedList.stories.ts b/pkgs/experimental/swarmakit/libs/svelte/src/components/NumberedList/NumberedList.stories.ts new file mode 100644 index 0000000000..7f5dc58a1f --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/svelte/src/components/NumberedList/NumberedList.stories.ts @@ -0,0 +1,70 @@ +import NumberedList from './NumberedList.svelte'; +import type { Meta, StoryFn } from '@storybook/svelte'; + +const meta: Meta = { + title: 'component/Lists/NumberedList', + component: NumberedList, + tags: ['autodocs'], + argTypes: { + items: { control: 'object' }, + disabled: { control: 'boolean' } + }, + parameters: { + layout: 'centered', + viewport: { + viewports: { + smallMobile: { name: 'Small Mobile', styles: { width: '320px', height: '568px' } }, + largeMobile: { name: 'Large Mobile', styles: { width: '414px', height: '896px' } }, + tablet: { name: 'Tablet', styles: { width: '768px', height: '1024px' } }, + desktop: { name: 'Desktop', styles: { width: '1024px', height: '768px' } }, + } + } + } +}; + +export default meta; + +const Template:StoryFn = (args) => ({ + Component:NumberedList, + props:args, +}); + +export const Default = Template.bind({}); +Default.args = { + items: [ + { id: '1', label: 'Item 1', selected: false }, + { id: '2', label: 'Item 2', selected: false }, + { id: '3', label: 'Item 3', selected: false } + ], + disabled:false, +}; + +export const Selected = Template.bind({}); +Selected.args = { + items: [ + { id: '1', label: 'Item 1', selected: true }, + { id: '2', label: 'Item 2', selected: false }, + { id: '3', label: 'Item 3', selected: false } + ], + disabled:false, +}; + +export const Hover = Template.bind({}); +Hover.args = { + items: [ + { id: '1', label: 'Item 1', selected: false }, + { id: '2', label: 'Item 2', selected: false }, + { id: '3', label: 'Item 3', selected: false } + ], + disabled:false, +}; + +export const Disabled = Template.bind({}); +Disabled.args = { + items: [ + { id: '1', label: 'Item 1', selected: false }, + { id: '2', label: 'Item 2', selected: false }, + { id: '3', label: 'Item 3', selected: false } + ], + disabled:true, +}; \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/svelte/src/components/NumberedList/NumberedList.svelte b/pkgs/experimental/swarmakit/libs/svelte/src/components/NumberedList/NumberedList.svelte new file mode 100644 index 0000000000..c343fd0db9 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/svelte/src/components/NumberedList/NumberedList.svelte @@ -0,0 +1,32 @@ + + +
      + {#each items as { id, label, selected } (id)} +
    1. handleSelect(id)} + aria-selected={selected} + aria-disabled={disabled} + tabindex={disabled ? -1 : 0} + on:keydown={(e)=>{if(e.key === 'Enter' || e.key === ' '){handleSelect(id)}}} + role="tab" + > + {label} +
    2. + {/each} +
    + + \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/svelte/src/components/Pagination/Pagination.css b/pkgs/experimental/swarmakit/libs/svelte/src/components/Pagination/Pagination.css new file mode 100644 index 0000000000..48e2723a35 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/svelte/src/components/Pagination/Pagination.css @@ -0,0 +1,40 @@ +:root { + --button-bg: #fff; + --button-hover-bg: #f0f0f0; + --button-active-bg: #007bff; + --button-disabled-bg: #e0e0e0; + --text-color: #333; + --active-text-color: #fff; + --border-color: #ccc; +} + +.pagination { + display: flex; + justify-content: center; + align-items: center; + gap: 5px; +} + +.page-button { + padding: 8px 12px; + background-color: var(--button-bg); + color: var(--text-color); + border: 1px solid var(--border-color); + cursor: pointer; + transition: background-color 0.2s; +} + +.page-button:hover { + background-color: var(--button-hover-bg); +} + +.page-button.active { + background-color: var(--button-active-bg); + color: var(--active-text-color); +} + +.page-button[disabled], +.page-button[aria-disabled="true"] { + background-color: var(--button-disabled-bg); + cursor: not-allowed; +} \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/svelte/src/components/Pagination/Pagination.stories.ts b/pkgs/experimental/swarmakit/libs/svelte/src/components/Pagination/Pagination.stories.ts new file mode 100644 index 0000000000..e4f2bdbfbb --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/svelte/src/components/Pagination/Pagination.stories.ts @@ -0,0 +1,59 @@ +import Pagination from './Pagination.svelte'; +import type { Meta, StoryFn } from '@storybook/svelte'; + +const meta: Meta = { + title: 'component/Lists/Pagination', + component: Pagination, + tags: ['autodocs'], + argTypes: { + totalItems: { control: 'number' }, + itemsPerPage: { control: 'number' }, + currentPage: { control: 'number' } + }, + parameters: { + layout: 'centered', + viewport: { + viewports: { + smallMobile: { name: 'Small Mobile', styles: { width: '320px', height: '568px' } }, + largeMobile: { name: 'Large Mobile', styles: { width: '414px', height: '896px' } }, + tablet: { name: 'Tablet', styles: { width: '768px', height: '1024px' } }, + desktop: { name: 'Desktop', styles: { width: '1024px', height: '768px' } }, + } + } + } +}; + +export default meta; + +const Template:StoryFn = (args) => ({ + Component: Pagination, + props:args, +}); + +export const Default = Template.bind({}); +Default.args = { + totalItems: 50, + itemsPerPage: 10, + currentPage: 1 +}; + +export const Active = Template.bind({}); +Active.args = { + totalItems: 50, + itemsPerPage: 10, + currentPage: 3 +}; + +export const Inactive = Template.bind({}); +Inactive.args = { + totalItems: 50, + itemsPerPage: 10, + currentPage: 1 +}; + +export const Hover = Template.bind({}); +Hover.args = { + totalItems: 50, + itemsPerPage: 10, + currentPage: 2 +}; \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/svelte/src/components/Pagination/Pagination.svelte b/pkgs/experimental/swarmakit/libs/svelte/src/components/Pagination/Pagination.svelte new file mode 100644 index 0000000000..dcfdc008c5 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/svelte/src/components/Pagination/Pagination.svelte @@ -0,0 +1,45 @@ + + + + + \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/svelte/src/components/PasswordConfirmationField/PasswordConfirmationField.css b/pkgs/experimental/swarmakit/libs/svelte/src/components/PasswordConfirmationField/PasswordConfirmationField.css new file mode 100644 index 0000000000..1add039e83 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/svelte/src/components/PasswordConfirmationField/PasswordConfirmationField.css @@ -0,0 +1,28 @@ +:root { + --input-border: #ccc; + --input-border-error: #ff4d4d; + --error-color: #ff4d4d; +} + +.password-confirmation-field { + display: flex; + flex-direction: column; + gap: 0.5rem; +} + +input { + padding: 0.5rem; + border: 1px solid var(--input-border); + border-radius: 4px; + transition: border-color 0.3s; +} + +input:focus { + outline: none; + border-color: var(--input-border-error); +} + +.error { + color: var(--error-color); + font-size: 0.875rem; +} \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/svelte/src/components/PasswordConfirmationField/PasswordConfirmationField.stories.ts b/pkgs/experimental/swarmakit/libs/svelte/src/components/PasswordConfirmationField/PasswordConfirmationField.stories.ts new file mode 100644 index 0000000000..40394c728b --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/svelte/src/components/PasswordConfirmationField/PasswordConfirmationField.stories.ts @@ -0,0 +1,59 @@ +import PasswordConfirmationField from './PasswordConfirmationField.svelte'; +import type { Meta, StoryFn } from '@storybook/svelte'; + +const meta: Meta = { + title: 'component/Forms/PasswordConfirmationField', + component: PasswordConfirmationField, + tags: ['autodocs'], + argTypes: { + password: { control: 'text' }, + confirmPassword: { control: 'text' }, + disabled: { control: 'boolean' }, + }, + parameters: { + layout: 'centered', + viewport: { + viewports: { + smallMobile: { name: 'Small Mobile', styles: { width: '320px', height: '568px' } }, + largeMobile: { name: 'Large Mobile', styles: { width: '414px', height: '896px' } }, + tablet: { name: 'Tablet', styles: { width: '768px', height: '1024px' } }, + desktop: { name: 'Desktop', styles: { width: '1024px', height: '768px' } }, + } + } + } +}; + +export default meta; + +const Template:StoryFn = (args) => ({ + Component: PasswordConfirmationField, + props:args, +}); + +export const Default = Template.bind({}); +Default.args = { + password: '', + confirmPassword: '', + disabled: false, +}; + +export const Matching = Template.bind({}); +Matching.args = { + password: 'password123', + confirmPassword: 'password123', + disabled: false, +}; + +export const NotMatching = Template.bind({}); +NotMatching.args = { + password: 'password123', + confirmPassword: 'password222', + disabled: false, +}; + +export const Disabled = Template.bind({}); +Disabled.args = { + password: 'password123', + confirmPassword: 'password123', + disabled: true, +}; \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/svelte/src/components/PasswordConfirmationField/PasswordConfirmationField.svelte b/pkgs/experimental/swarmakit/libs/svelte/src/components/PasswordConfirmationField/PasswordConfirmationField.svelte new file mode 100644 index 0000000000..00bce9da2e --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/svelte/src/components/PasswordConfirmationField/PasswordConfirmationField.svelte @@ -0,0 +1,35 @@ + + +
    + + + {#if !passwordsMatch} +

    Passwords do not match

    + {/if} +
    + + \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/svelte/src/components/PinnedList/PinnedList.css b/pkgs/experimental/swarmakit/libs/svelte/src/components/PinnedList/PinnedList.css new file mode 100644 index 0000000000..3073ca06f9 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/svelte/src/components/PinnedList/PinnedList.css @@ -0,0 +1,46 @@ +:root { + --item-bg: #fff; + --item-hover-bg: #f0f0f0; + --item-selected-bg: #007bff; + --item-text-color: #333; + --item-selected-text-color: #fff; + --border-color: #ccc; +} + +.pinned-list { + list-style: none; + padding: 0; + margin: 0; +} + +.list-item { + display: flex; + justify-content: space-between; + align-items: center; + padding: 8px; + background-color: var(--item-bg); + color: var(--item-text-color); + border: 1px solid var(--border-color); + transition: background-color 0.2s; + cursor: pointer; +} + +.list-item:hover { + background-color: var(--item-hover-bg); +} + +.list-item.pinned { + font-weight: bold; +} + +.list-item.selected { + background-color: var(--item-selected-bg); + color: var(--item-selected-text-color); +} + +.pin-button { + background: none; + border: none; + font-size: 16px; + cursor: pointer; +} \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/svelte/src/components/PinnedList/PinnedList.stories.ts b/pkgs/experimental/swarmakit/libs/svelte/src/components/PinnedList/PinnedList.stories.ts new file mode 100644 index 0000000000..afeff77022 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/svelte/src/components/PinnedList/PinnedList.stories.ts @@ -0,0 +1,81 @@ +import PinnedList from './PinnedList.svelte'; +import type { Meta, StoryFn } from '@storybook/svelte'; + +const meta: Meta = { + title: 'component/Lists/PinnedList', + component: PinnedList, + tags: ['autodocs'], + argTypes: { + items: { control: 'object' }, + selectedItem: { control: 'number' } + }, + parameters: { + layout: 'centered', + viewport: { + viewports: { + smallMobile: { name: 'Small Mobile', styles: { width: '320px', height: '568px' } }, + largeMobile: { name: 'Large Mobile', styles: { width: '414px', height: '896px' } }, + tablet: { name: 'Tablet', styles: { width: '768px', height: '1024px' } }, + desktop: { name: 'Desktop', styles: { width: '1024px', height: '768px' } }, + } + } + } +}; + +export default meta; + +const Template:StoryFn = (args) => ({ + Component: PinnedList, + props: args, +}); + +export const Default = Template.bind({}); +Default.args = { + items: [ + { id: 1, text: 'Item 1', pinned: false }, + { id: 2, text: 'Item 2', pinned: false }, + { id: 3, text: 'Item 3', pinned: false } + ], + selectedItem: null +}; + +export const Pinned = Template.bind({}); +Pinned.args = { + items: [ + { id: 1, text: 'Item 1', pinned: true }, + { id: 2, text: 'Item 2', pinned: false }, + { id: 3, text: 'Item 3', pinned: false } + ], + selectedItem: null +}; + +export const Unpinned = Template.bind({}); +Unpinned.args = { + items: [ + { id: 1, text: 'Item 1', pinned: false }, + { id: 2, text: 'Item 2', pinned: false }, + { id: 3, text: 'Item 3', pinned: false } + ], + selectedItem: null +}; + + +export const Hover = Template.bind({}); +Hover.args = { + items: [ + { id: 1, text: 'Item 1', pinned: false }, + { id: 2, text: 'Item 2', pinned: false }, + { id: 3, text: 'Item 3', pinned: false } + ], + selectedItem: null +}; + +export const selectedItem = Template.bind({}); +selectedItem.args = { + items: [ + { id: 1, text: 'Item 1', pinned: false }, + { id: 2, text: 'Item 2', pinned: false }, + { id: 3, text: 'Item 3', pinned: false } + ], + selectedItem: 2, +}; \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/svelte/src/components/PinnedList/PinnedList.svelte b/pkgs/experimental/swarmakit/libs/svelte/src/components/PinnedList/PinnedList.svelte new file mode 100644 index 0000000000..803bd321a3 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/svelte/src/components/PinnedList/PinnedList.svelte @@ -0,0 +1,44 @@ + + +
      + {#each items as item (item.id)} +
    • selectItem(item.id)} + on:keydown={(e)=>{if(e.key === 'Enter' || e.key === ' '){selectItem(item.id)}}} + aria-selected={selectedItem === item.id} + tabindex ="0" + role="tab" + > + {item.text} + +
    • + {/each} +
    + + \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/svelte/src/components/ProgressBar/ProgressBar.css b/pkgs/experimental/swarmakit/libs/svelte/src/components/ProgressBar/ProgressBar.css new file mode 100644 index 0000000000..055eae4214 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/svelte/src/components/ProgressBar/ProgressBar.css @@ -0,0 +1,29 @@ +:root { + --progress-bar-height: 16px; + --progress-bar-background: #e0e0e0; + --progress-bar-color: #76c7c0; + --progress-bar-disabled-color: #b0b0b0; + --progress-bar-hover-color: #60a9a7; +} + +.progress-bar-container { + width: 100%; + background-color: var(--progress-bar-background); + border-radius: 8px; + overflow: hidden; +} + +.progress-bar { + height: var(--progress-bar-height); + background-color: var(--progress-bar-color); + border-radius: 8px; + transition: width 0.2s ease-in-out; +} + +.progress-bar-container[aria-disabled="true"] .progress-bar { + background-color: var(--progress-bar-disabled-color); +} + +.progress-bar-container:hover .progress-bar:not([aria-disabled="true"]) { + background-color: var(--progress-bar-hover-color); +} \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/svelte/src/components/ProgressBar/ProgressBar.stories.ts b/pkgs/experimental/swarmakit/libs/svelte/src/components/ProgressBar/ProgressBar.stories.ts new file mode 100644 index 0000000000..ae001ab523 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/svelte/src/components/ProgressBar/ProgressBar.stories.ts @@ -0,0 +1,60 @@ +import ProgressBar from './ProgressBar.svelte'; +import type { Meta, StoryFn, StoryObj } from '@storybook/svelte'; + +const meta: Meta = { + title: 'component/Indicators/ProgressBar', + component: ProgressBar, + tags: ['autodocs'], + argTypes: { + progress: { control: { type: 'range', min: 0, max: 100 } }, + disabled: { control: 'boolean' }, + }, + parameters: { + layout: 'centered', + viewport: { + viewports: { + smallMobile: { name: 'Small Mobile', styles: { width: '320px', height: '568px' } }, + largeMobile: { name: 'Large Mobile', styles: { width: '414px', height: '896px' } }, + tablet: { name: 'Tablet', styles: { width: '768px', height: '1024px' } }, + desktop: { name: 'Desktop', styles: { width: '1024px', height: '768px' } }, + } + } + } +}; + +export default meta; + +const Template:StoryFn = (args) => ({ + Component:ProgressBar, + props:args, +}); + +export const Default = Template.bind({}); +Default.args = { + progress: 50, + disabled: false, +}; + +export const Complete = Template.bind({}); +Complete.args = { + progress: 100, + disabled: false, +}; + +export const Incomplete = Template.bind({}); +Incomplete.args = { + progress: 25, + disabled: false, +}; + +export const Hover = Template.bind({}); +Hover.args = { + progress: 70, + disabled: false, +}; + +export const Disabled = Template.bind({}); +Disabled.args = { + progress: 50, + disabled: true, +}; \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/svelte/src/components/ProgressBar/ProgressBar.svelte b/pkgs/experimental/swarmakit/libs/svelte/src/components/ProgressBar/ProgressBar.svelte new file mode 100644 index 0000000000..b07d1a7ee4 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/svelte/src/components/ProgressBar/ProgressBar.svelte @@ -0,0 +1,13 @@ + + +
    +
    +
    + + \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/svelte/src/components/ProgressCircle/ProgressCircle.css b/pkgs/experimental/swarmakit/libs/svelte/src/components/ProgressCircle/ProgressCircle.css new file mode 100644 index 0000000000..d18c7d9818 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/svelte/src/components/ProgressCircle/ProgressCircle.css @@ -0,0 +1,42 @@ +:root { + --circle-background-color: #e0e0e0; + --circle-progress-color: #76c7c0; + --circle-complete-color: #4caf50; + --circle-incomplete-color: #f44336; + --circle-paused-color: #ff9800; + --circle-active-color: #2196f3; +} + +.progress-circle { + transform: rotate(-90deg); +} + +.background { + fill: none; + stroke: var(--circle-background-color); + stroke-width: 10; +} + +.progress { + fill: none; + stroke: var(--circle-progress-color); + stroke-width: 10; + transition: stroke-dashoffset 0.35s; + transform-origin: center; +} + +[data-state='complete'] .progress { + stroke: var(--circle-complete-color); +} + +[data-state='incomplete'] .progress { + stroke: var(--circle-incomplete-color); +} + +[data-state='paused'] .progress { + stroke: var(--circle-paused-color); +} + +[data-state='active'] .progress { + stroke: var(--circle-active-color); +} \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/svelte/src/components/ProgressCircle/ProgressCircle.stories.ts b/pkgs/experimental/swarmakit/libs/svelte/src/components/ProgressCircle/ProgressCircle.stories.ts new file mode 100644 index 0000000000..ffa8c3f81d --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/svelte/src/components/ProgressCircle/ProgressCircle.stories.ts @@ -0,0 +1,66 @@ +import ProgressCircle from './ProgressCircle.svelte'; +import type { Meta, StoryFn } from '@storybook/svelte'; + +const meta: Meta = { + title: 'component/Indicators/ProgressCircle', + component: ProgressCircle, + tags: ['autodocs'], + argTypes: { + progress: { control: { type: 'range', min: 0, max: 100 } }, + state: { control: 'select', options: ['complete', 'incomplete', 'in-progress', 'paused', 'active'] }, + }, + parameters: { + layout: 'centered', + viewport: { + viewports: { + smallMobile: { name: 'Small Mobile', styles: { width: '320px', height: '568px' } }, + largeMobile: { name: 'Large Mobile', styles: { width: '414px', height: '896px' } }, + tablet: { name: 'Tablet', styles: { width: '768px', height: '1024px' } }, + desktop: { name: 'Desktop', styles: { width: '1024px', height: '768px' } }, + } + } + } +}; + +export default meta; + +const Template:StoryFn = (args) => ({ + Component: ProgressCircle, + props:args, +}); + +export const Default = Template.bind({}); +Default.args = { + progress: 50, + state: 'in-progress', +}; + +export const Complete = Template.bind({}); +Complete.args = { + progress: 100, + state: 'complete', +}; + +export const InComplete = Template.bind({}); +InComplete.args = { + progress: 0, + state: 'incomplete', +}; + +export const InProgress = Template.bind({}); +InProgress.args = { + progress: 50, + state: 'in-progress', +}; + +export const Paused = Template.bind({}); +Paused.args = { + progress: 50, + state: 'paused', +}; + +export const Active = Template.bind({}); +Active.args = { + progress: 75, + state: 'active', +}; \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/svelte/src/components/ProgressCircle/ProgressCircle.svelte b/pkgs/experimental/swarmakit/libs/svelte/src/components/ProgressCircle/ProgressCircle.svelte new file mode 100644 index 0000000000..f5b718ce5f --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/svelte/src/components/ProgressCircle/ProgressCircle.svelte @@ -0,0 +1,33 @@ + + + + + + + + \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/svelte/src/components/README.md b/pkgs/experimental/swarmakit/libs/svelte/src/components/README.md new file mode 100644 index 0000000000..4287ca8617 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/svelte/src/components/README.md @@ -0,0 +1 @@ +# \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/svelte/src/components/RadioButton/RadioButton.css b/pkgs/experimental/swarmakit/libs/svelte/src/components/RadioButton/RadioButton.css new file mode 100644 index 0000000000..d519f23fc2 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/svelte/src/components/RadioButton/RadioButton.css @@ -0,0 +1,48 @@ +:root { + --radio-border: #ccc; + --radio-border-selected: #007bff; + --radio-border-disabled: #d3d3d3; + --label-color: #333; + --label-color-disabled: #a9a9a9; +} + +.radio-button { + display: flex; + align-items: center; + cursor: pointer; + user-select: none; +} + +input[type="radio"] { + appearance: none; + margin: 0; + width: 1rem; + height: 1rem; + border: 2px solid var(--radio-border); + border-radius: 50%; + display: inline-block; + position: relative; + vertical-align: middle; + cursor: pointer; + outline: none; + transition: border-color 0.3s; +} + +input[type="radio"]:checked { + border-color: var(--radio-border-selected); +} + +input[type="radio"]:disabled { + border-color: var(--radio-border-disabled); + cursor: not-allowed; +} + +label { + margin-left: 0.5rem; + color: var(--label-color); +} + +input[type="radio"]:disabled + label { + color: var(--label-color-disabled); + cursor: not-allowed; +} \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/svelte/src/components/RadioButton/RadioButton.stories.ts b/pkgs/experimental/swarmakit/libs/svelte/src/components/RadioButton/RadioButton.stories.ts new file mode 100644 index 0000000000..7f099d600f --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/svelte/src/components/RadioButton/RadioButton.stories.ts @@ -0,0 +1,59 @@ +import RadioButton from './RadioButton.svelte'; +import type { Meta, StoryFn } from '@storybook/svelte'; + +const meta: Meta = { + title: 'component/Forms/RadioButton', + component: RadioButton, + tags: ['autodocs'], + argTypes: { + selected: { control: 'boolean' }, + disabled: { control: 'boolean' }, + label: { control: 'text' }, + }, + parameters: { + layout: 'centered', + viewport: { + viewports: { + smallMobile: { name: 'Small Mobile', styles: { width: '320px', height: '568px' } }, + largeMobile: { name: 'Large Mobile', styles: { width: '414px', height: '896px' } }, + tablet: { name: 'Tablet', styles: { width: '768px', height: '1024px' } }, + desktop: { name: 'Desktop', styles: { width: '1024px', height: '768px' } }, + } + } + } +}; + +export default meta; + +const Template:StoryFn = (args) => ({ + Component:RadioButton, + props:args, +}); + +export const Default = Template.bind({}); +Default.args = { + selected: false, + disabled: false, + label: 'Default', +}; + +export const Selected = Template.bind({}); +Selected.args = { + selected: true, + disabled: false, + label: 'Selected', +}; + +export const UnSelected = Template.bind({}); +UnSelected.args = { + selected: false, + disabled: false, + label: 'Unselected', +}; + +export const Disabled = Template.bind({}); +Disabled.args = { + selected: false, + disabled: true, + label: 'Disabled', +}; \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/svelte/src/components/RadioButton/RadioButton.svelte b/pkgs/experimental/swarmakit/libs/svelte/src/components/RadioButton/RadioButton.svelte new file mode 100644 index 0000000000..28d6df2079 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/svelte/src/components/RadioButton/RadioButton.svelte @@ -0,0 +1,26 @@ + + + + + \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/svelte/src/components/RangeSlider/RangeSlider.css b/pkgs/experimental/swarmakit/libs/svelte/src/components/RangeSlider/RangeSlider.css new file mode 100644 index 0000000000..930043454e --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/svelte/src/components/RangeSlider/RangeSlider.css @@ -0,0 +1,57 @@ +:root { + --slider-track-bg: #ddd; + --slider-thumb-bg: #007bff; + --slider-thumb-bg-hover: #0056b3; + --slider-thumb-bg-active: #003f7f; + --slider-thumb-bg-disabled: #b0b0b0; + --label-color: #333; + --label-color-disabled: #a9a9a9; +} + +.range-slider { + display: flex; + align-items: center; + width: 100%; +} + +input[type='range'] { + -webkit-appearance: none; + width: 100%; + height: 6px; + background: var(--slider-track-bg); + outline: none; + opacity: 0.8; + transition: opacity 0.2s; + margin: 0 10px; +} + +input[type='range']::-webkit-slider-thumb { + -webkit-appearance: none; + appearance: none; + width: 20px; + height: 20px; + background: var(--slider-thumb-bg); + cursor: pointer; +} + +input[type='range']:hover::-webkit-slider-thumb { + background: var(--slider-thumb-bg-hover); +} + +input[type='range']:active::-webkit-slider-thumb { + background: var(--slider-thumb-bg-active); +} + +input[type='range']:disabled::-webkit-slider-thumb { + background: var(--slider-thumb-bg-disabled); + cursor: not-allowed; +} + +label { + color: var(--label-color); +} + +input[type='range']:disabled + label { + color: var(--label-color-disabled); + cursor: not-allowed; +} \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/svelte/src/components/RangeSlider/RangeSlider.stories.ts b/pkgs/experimental/swarmakit/libs/svelte/src/components/RangeSlider/RangeSlider.stories.ts new file mode 100644 index 0000000000..666389ffe2 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/svelte/src/components/RangeSlider/RangeSlider.stories.ts @@ -0,0 +1,94 @@ +import RangeSlider from './RangeSlider.svelte'; +import type { Meta, StoryFn } from '@storybook/svelte'; + +const meta: Meta = { + title: 'component/Forms/RangeSlider', + component: RangeSlider, + tags: ['autodocs'], + argTypes: { + min: { control: 'number' }, + max: { control: 'number' }, + value: { control: 'number' }, + disabled: { control: 'boolean' }, + label: { control: 'text' }, + labelPosition: { control: 'select', options: ['left', 'center', 'right'] }, + }, + parameters: { + layout: 'centered', + viewport: { + viewports: { + smallMobile: { name: 'Small Mobile', styles: { width: '320px', height: '568px' } }, + largeMobile: { name: 'Large Mobile', styles: { width: '414px', height: '896px' } }, + tablet: { name: 'Tablet', styles: { width: '768px', height: '1024px' } }, + desktop: { name: 'Desktop', styles: { width: '1024px', height: '768px' } }, + } + } + } +}; + +export default meta; + +const Template:StoryFn = (args) => ({ + Component:RangeSlider, + props:args, +}); + +export const Default = Template.bind({}); +Default.args = { + min: 0, + max: 100, + value: 50, + disabled: false, + label: 'Default', + labelPosition: 'right', +}; + +export const Min = Template.bind({}); +Min.args = { + min: 0, + max: 100, + value: 0, + disabled: false, + label: 'Min', + labelPosition: 'right', +}; + +export const Max = Template.bind({}); +Max.args = { + min: 0, + max: 100, + value: 100, + disabled: false, + label: 'Max', + labelPosition: 'right', +}; + +export const Hover = Template.bind({}); +Hover.args = { + min: 0, + max: 100, + value: 50, + disabled: false, + label: 'Hover', + labelPosition: 'right', +}; + +export const Active = Template.bind({}); +Active.args = { + min: 0, + max: 100, + value: 75, + disabled: false, + label: 'Active', + labelPosition: 'right', +}; + +export const Disabled = Template.bind({}); +Disabled.args = { + min: 0, + max: 100, + value: 50, + disabled: true, + label: 'Disabled', + labelPosition: 'right', +}; \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/svelte/src/components/RangeSlider/RangeSlider.svelte b/pkgs/experimental/swarmakit/libs/svelte/src/components/RangeSlider/RangeSlider.svelte new file mode 100644 index 0000000000..c50d0abec5 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/svelte/src/components/RangeSlider/RangeSlider.svelte @@ -0,0 +1,41 @@ + + +
    + {#if labelPosition === 'left'} + + {/if} + + {#if labelPosition === 'center'} + + {/if} + {#if labelPosition === 'right'} + + {/if} +
    + + \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/svelte/src/components/RatingStars/RatingStars.css b/pkgs/experimental/swarmakit/libs/svelte/src/components/RatingStars/RatingStars.css new file mode 100644 index 0000000000..9be02e9a5c --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/svelte/src/components/RatingStars/RatingStars.css @@ -0,0 +1,33 @@ +:root { + --star-color: #e0e0e0; + --star-filled-color: #ffc107; + --star-hover-color: #ffeb3b; + --star-selected-color: #fdd835; +} + +.rating-stars { + display: inline-flex; + gap: 0.5rem; +} + +.star { + font-size: 2rem; + color: var(--star-color); + background: none; + border: none; + cursor: pointer; + transition: color 0.3s; +} + +.star.filled { + color: var(--star-filled-color); +} + +[data-state='hover'] .star:hover, +[data-state='hover'] .star:hover ~ .star { + color: var(--star-hover-color); +} + +[data-state='selected'] .star.filled { + color: var(--star-selected-color); +} \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/svelte/src/components/RatingStars/RatingStars.stories.ts b/pkgs/experimental/swarmakit/libs/svelte/src/components/RatingStars/RatingStars.stories.ts new file mode 100644 index 0000000000..b615abd60a --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/svelte/src/components/RatingStars/RatingStars.stories.ts @@ -0,0 +1,54 @@ +import RatingStars from './RatingStars.svelte'; +import type { Meta, StoryFn } from '@storybook/svelte'; + +const meta: Meta = { + title: 'component/Indicators/RatingStars', + component: RatingStars, + tags: ['autodocs'], + argTypes: { + rating: { control: { type: 'range', min: 0, max: 5 } }, + state: { control: 'select', options: ['hover', 'selected', 'inactive'] }, + }, + parameters: { + layout: 'centered', + viewport: { + viewports: { + smallMobile: { name: 'Small Mobile', styles: { width: '320px', height: '568px' } }, + largeMobile: { name: 'Large Mobile', styles: { width: '414px', height: '896px' } }, + tablet: { name: 'Tablet', styles: { width: '768px', height: '1024px' } }, + desktop: { name: 'Desktop', styles: { width: '1024px', height: '768px' } }, + } + } + } +}; + +export default meta; + +const Template:StoryFn = (args) => ({ + Component:RatingStars, + props:args, +}); + +export const Default = Template.bind({}); +Default.args = { + rating:3, + state:'inactive', +}; + +export const Hover = Template.bind({}); +Hover.args = { + rating:3, + state:'hover', +}; + +export const Selected = Template.bind({}); +Selected.args = { + rating:4, + state:'selected', +}; + +export const Inactive = Template.bind({}); +Inactive.args = { + rating:0, + state:'inactive', +}; \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/svelte/src/components/RatingStars/RatingStars.svelte b/pkgs/experimental/swarmakit/libs/svelte/src/components/RatingStars/RatingStars.svelte new file mode 100644 index 0000000000..e3cdedeb61 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/svelte/src/components/RatingStars/RatingStars.svelte @@ -0,0 +1,31 @@ + + +
    + {#each stars as star} + + {/each} +
    + + \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/svelte/src/components/RichTextEditor/RichTextEditor.css b/pkgs/experimental/swarmakit/libs/svelte/src/components/RichTextEditor/RichTextEditor.css new file mode 100644 index 0000000000..aaf068d18e --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/svelte/src/components/RichTextEditor/RichTextEditor.css @@ -0,0 +1,32 @@ +:root { + --editor-border-color: #ccc; + --editor-focus-border-color: #007bff; + --editor-bg-color: #fff; + --editor-readonly-bg-color: #f8f9fa; +} + +.rich-text-editor { + border: 1px solid var(--editor-border-color); + border-radius: 4px; + background-color: var(--editor-bg-color); + padding: 10px; + min-height: 200px; + max-height: 500px; + overflow-y: auto; +} + +.rich-text-editor:focus-within { + border-color: var(--editor-focus-border-color); +} + +.ql-container.ql-snow { + border: none; +} + +.ql-editor { + padding: 0; +} + +.ql-editor:read-only { + background-color: var(--editor-readonly-bg-color); +} \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/svelte/src/components/RichTextEditor/RichTextEditor.stories.ts b/pkgs/experimental/swarmakit/libs/svelte/src/components/RichTextEditor/RichTextEditor.stories.ts new file mode 100644 index 0000000000..a77b9405ef --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/svelte/src/components/RichTextEditor/RichTextEditor.stories.ts @@ -0,0 +1,48 @@ +import RichTextEditor from './RichTextEditor.svelte'; +import type { Meta, StoryFn } from '@storybook/svelte'; + +const meta: Meta = { + title: 'component/Forms/RichTextEditor', + component: RichTextEditor, + tags: ['autodocs'], + argTypes: { + content: { control: 'text' }, + readOnly: { control: 'boolean' }, + }, + parameters: { + layout: 'fullscreen', + viewport: { + viewports: { + smallMobile: { name: 'Small Mobile', styles: { width: '320px', height: '568px' } }, + largeMobile: { name: 'Large Mobile', styles: { width: '414px', height: '896px' } }, + tablet: { name: 'Tablet', styles: { width: '768px', height: '1024px' } }, + desktop: { name: 'Desktop', styles: { width: '1024px', height: '768px' } }, + } + } + } +}; + +export default meta; + +const Template:StoryFn = (args) => ({ + Component: RichTextEditor, + props:args, +}); + +export const Default = Template.bind({}); +Default.args = { + content: '

    Start writing...

    ', + readOnly: false, +}; + +export const Editing = Template.bind({}); +Editing.args = { + content: '

    Edit this text.

    ', + readOnly: false, +}; + +export const ReadOnly = Template.bind({}); +ReadOnly.args = { + content: '

    This text is read-only.

    ', + readOnly: true, +}; \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/svelte/src/components/RichTextEditor/RichTextEditor.svelte b/pkgs/experimental/swarmakit/libs/svelte/src/components/RichTextEditor/RichTextEditor.svelte new file mode 100644 index 0000000000..b832c3684d --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/svelte/src/components/RichTextEditor/RichTextEditor.svelte @@ -0,0 +1,47 @@ + + +
    + + \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/svelte/src/components/ScrollableList/ScrollableList.css b/pkgs/experimental/swarmakit/libs/svelte/src/components/ScrollableList/ScrollableList.css new file mode 100644 index 0000000000..1d23ecd23b --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/svelte/src/components/ScrollableList/ScrollableList.css @@ -0,0 +1,32 @@ +:root { + --list-bg: #f8f8f8; + --list-border-color: #ccc; + --list-item-bg: #fff; + --list-item-hover-bg: #e0e0e0; + --text-color: #333; + --disabled-bg: #d3d3d3; +} + +.scrollable-list { + max-height: 300px; + overflow-y: auto; + background-color: var(--list-bg); + border: 1px solid var(--list-border-color); +} + +.scrollable-list.disabled { + background-color: var(--disabled-bg); + pointer-events: none; +} + +.list-item { + padding: 8px; + background-color: var(--list-item-bg); + color: var(--text-color); + border-bottom: 1px solid var(--list-border-color); + transition: background-color 0.2s; +} + +.list-item:hover { + background-color: var(--list-item-hover-bg); +} \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/svelte/src/components/ScrollableList/ScrollableList.stories.ts b/pkgs/experimental/swarmakit/libs/svelte/src/components/ScrollableList/ScrollableList.stories.ts new file mode 100644 index 0000000000..68517bd9cf --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/svelte/src/components/ScrollableList/ScrollableList.stories.ts @@ -0,0 +1,60 @@ +import ScrollableList from './ScrollableList.svelte'; +import type { Meta, StoryFn } from '@storybook/svelte'; + +const meta: Meta = { + title: 'component/Lists/ScrollableList', + component: ScrollableList, + tags: ['autodocs'], + argTypes: { + items: { control: 'object' }, + disabled: { control: 'boolean' } + }, + parameters: { + layout: 'centered', + viewport: { + viewports: { + smallMobile: { name: 'Small Mobile', styles: { width: '320px', height: '568px' } }, + largeMobile: { name: 'Large Mobile', styles: { width: '414px', height: '896px' } }, + tablet: { name: 'Tablet', styles: { width: '768px', height: '1024px' } }, + desktop: { name: 'Desktop', styles: { width: '1024px', height: '768px' } }, + } + } + } +}; + +export default meta; + +const Template:StoryFn = (args) => ({ + Component:ScrollableList, + props:args, +}); + +export const Default = Template.bind({}); +Default.args = { + items: Array.from({ length: 20 }, (_, i) => ({ id: i + 1, text: `Item ${i + 1}` })), + disabled: false +}; + +export const Scrolling = Template.bind({}); +Scrolling.args = { + items: Array.from({ length: 20 }, (_, i) => ({ id: i + 1, text: `Item ${i + 1}` })), + disabled: false +}; + +export const EndOfList = Template.bind({}); +EndOfList.args = { + items: Array.from({ length: 5 }, (_, i) => ({ id: i + 1, text: `Item ${i + 1}` })), + disabled: false +}; + +export const Hover = Template.bind({}); +Hover.args = { + items: Array.from({ length: 20 }, (_, i) => ({ id: i + 1, text: `Item ${i + 1}` })), + disabled: false +}; + +export const Disabled = Template.bind({}); +Disabled.args = { + items: Array.from({ length: 20 }, (_, i) => ({ id: i + 1, text: `Item ${i + 1}` })), + disabled: true +}; \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/svelte/src/components/ScrollableList/ScrollableList.svelte b/pkgs/experimental/swarmakit/libs/svelte/src/components/ScrollableList/ScrollableList.svelte new file mode 100644 index 0000000000..746e149ea4 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/svelte/src/components/ScrollableList/ScrollableList.svelte @@ -0,0 +1,34 @@ + + + + + \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/svelte/src/components/SearchBar/SearchBar.css b/pkgs/experimental/swarmakit/libs/svelte/src/components/SearchBar/SearchBar.css new file mode 100644 index 0000000000..901d4cdcb0 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/svelte/src/components/SearchBar/SearchBar.css @@ -0,0 +1,33 @@ +:root { + --search-bar-padding: 0.5rem 1rem; + --search-bar-margin: 0.5rem; + --search-bar-border-radius: 4px; + --search-bar-font-size: 1rem; + --search-bar-bg: #fff; + --search-bar-color: #333; + --search-bar-border: 1px solid #ccc; + --search-bar-border-focused: 1px solid #007bff; + --search-bar-border-disabled: 1px solid #ddd; + --search-bar-bg-disabled: #f5f5f5; +} + +.search-bar { + padding: var(--search-bar-padding); + margin: var(--search-bar-margin); + border-radius: var(--search-bar-border-radius); + font-size: var(--search-bar-font-size); + background-color: var(--search-bar-bg); + color: var(--search-bar-color); + border: var(--search-bar-border); + transition: border 0.3s ease; +} + +.search-bar.is-focused { + border: var(--search-bar-border-focused); +} + +.search-bar:disabled { + background-color: var(--search-bar-bg-disabled); + border: var(--search-bar-border-disabled); + cursor: not-allowed; +} \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/svelte/src/components/SearchBar/SearchBar.stories.ts b/pkgs/experimental/swarmakit/libs/svelte/src/components/SearchBar/SearchBar.stories.ts new file mode 100644 index 0000000000..71da9a5c39 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/svelte/src/components/SearchBar/SearchBar.stories.ts @@ -0,0 +1,59 @@ +import SearchBar from './SearchBar.svelte'; +import type { Meta, StoryFn } from '@storybook/svelte'; + +const meta: Meta = { + title: 'component/Input/SearchBar', + component: SearchBar, + tags: ['autodocs'], + argTypes: { + isFocused: { control: 'boolean' }, + isDisabled: { control: 'boolean' }, + placeholder: { control: 'text' } + }, + parameters: { + layout: 'centered', + viewport: { + viewports: { + smallMobile: { name: 'Small Mobile', styles: { width: '320px', height: '568px' } }, + largeMobile: { name: 'Large Mobile', styles: { width: '414px', height: '896px' } }, + tablet: { name: 'Tablet', styles: { width: '768px', height: '1024px' } }, + desktop: { name: 'Desktop', styles: { width: '1024px', height: '768px' } }, + } + } + } +}; + +export default meta; + +const Template:StoryFn = (args) => ({ + Component:SearchBar, + props:args, +}); + +export const Default = Template.bind({}); +Default.args = { + isFocused: false, + isDisabled: false, + placeholder: 'Search...' +}; + +export const Focused = Template.bind({}); +Focused.args = { + isFocused: true, + isDisabled: false, + placeholder: 'Search...' +}; + +export const UnFocused = Template.bind({}); +UnFocused.args = { + isFocused: false, + isDisabled: false, + placeholder: 'Search...' +}; + +export const Disabled = Template.bind({}); +Disabled.args = { + isFocused: false, + isDisabled: true, + placeholder: 'Search...' +}; \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/svelte/src/components/SearchBar/SearchBar.svelte b/pkgs/experimental/swarmakit/libs/svelte/src/components/SearchBar/SearchBar.svelte new file mode 100644 index 0000000000..8657413086 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/svelte/src/components/SearchBar/SearchBar.svelte @@ -0,0 +1,19 @@ + + + + + \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/svelte/src/components/SearchInputWithFilterOptions/SearchInputWithFilterOptions.css b/pkgs/experimental/swarmakit/libs/svelte/src/components/SearchInputWithFilterOptions/SearchInputWithFilterOptions.css new file mode 100644 index 0000000000..e403bc0f35 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/svelte/src/components/SearchInputWithFilterOptions/SearchInputWithFilterOptions.css @@ -0,0 +1,53 @@ +:root { + --input-border-color: #ccc; + --input-focus-border-color: #007bff; + --button-bg-color: #f0f0f0; + --button-active-bg-color: #007bff; + --button-text-color: #000; + --button-active-text-color: #fff; +} + +.search-input-container { + display: flex; + flex-direction: column; + gap: 10px; +} + +.search-input { + padding: 8px; + border: 1px solid var(--input-border-color); + border-radius: 4px; + font-size: 16px; + width: 100%; +} + +.search-input:focus { + border-color: var(--input-focus-border-color); + outline: none; +} + +.filter-options { + display: flex; + gap: 8px; + flex-wrap: wrap; +} + +.filter-button { + padding: 8px 12px; + background-color: var(--button-bg-color); + color: var(--button-text-color); + border: none; + border-radius: 4px; + cursor: pointer; + transition: background-color 0.3s; +} + +.filter-button.active { + background-color: var(--button-active-bg-color); + color: var(--button-active-text-color); +} + +.filter-button:disabled { + opacity: 0.5; + cursor: not-allowed; +} \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/svelte/src/components/SearchInputWithFilterOptions/SearchInputWithFilterOptions.stories.ts b/pkgs/experimental/swarmakit/libs/svelte/src/components/SearchInputWithFilterOptions/SearchInputWithFilterOptions.stories.ts new file mode 100644 index 0000000000..280791d645 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/svelte/src/components/SearchInputWithFilterOptions/SearchInputWithFilterOptions.stories.ts @@ -0,0 +1,72 @@ +import SearchInputWithFilterOptions from './SearchInputWithFilterOptions.svelte'; +import type { Meta, StoryFn } from '@storybook/svelte'; + +const meta: Meta = { + title: 'component/Forms/SearchInputWithFilterOptions', + component: SearchInputWithFilterOptions, + tags: ['autodocs'], + argTypes: { + query: { control: 'text' }, + filters: { control: 'object' }, + activeFilters: { control: 'object' }, + disabled: { control: 'boolean' }, + }, + parameters: { + layout: 'fullscreen', + viewport: { + viewports: { + smallMobile: { name: 'Small Mobile', styles: { width: '320px', height: '568px' } }, + largeMobile: { name: 'Large Mobile', styles: { width: '414px', height: '896px' } }, + tablet: { name: 'Tablet', styles: { width: '768px', height: '1024px' } }, + desktop: { name: 'Desktop', styles: { width: '1024px', height: '768px' } }, + } + } + } +}; + +export default meta; + +const Template:StoryFn = (args) => ({ + Component:SearchInputWithFilterOptions, + props:args, +}); + +export const Default = Template.bind({}); +Default.args = { + query: '', + filters: ['Option 1', 'Option 2', 'Option 3'], + activeFilters: [], + disabled: false, +}; + +export const Searching = Template.bind({}); +Searching.args = { + query: 'Search Term', + filters: ['Option 1', 'Option 2', 'Option 3'], + activeFilters: [], + disabled: false, +}; + +export const FiltersActive = Template.bind({}); +FiltersActive.args = { + query: '', + filters: ['Option 1', 'Option 2', 'Option 3'], + activeFilters: ['Option 1', 'Option 2'], + disabled: false, +}; + +export const NoResults = Template.bind({}); +NoResults.args = { + query: 'No results term', + filters: ['Option 1', 'Option 2', 'Option 3'], + activeFilters: [], + disabled: false, +}; + +export const Disabled = Template.bind({}); +Disabled.args = { + query: '', + filters: ['Option 1', 'Option 2', 'Option 3'], + activeFilters: [], + disabled: false, +}; \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/svelte/src/components/SearchInputWithFilterOptions/SearchInputWithFilterOptions.svelte b/pkgs/experimental/swarmakit/libs/svelte/src/components/SearchInputWithFilterOptions/SearchInputWithFilterOptions.svelte new file mode 100644 index 0000000000..6e16f13209 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/svelte/src/components/SearchInputWithFilterOptions/SearchInputWithFilterOptions.svelte @@ -0,0 +1,53 @@ + + +
    + +
    + {#each filters as filter} + + {/each} +
    +
    + + \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/svelte/src/components/SelectableListWithItemDetails/SelectableListWithItemDetails.css b/pkgs/experimental/swarmakit/libs/svelte/src/components/SelectableListWithItemDetails/SelectableListWithItemDetails.css new file mode 100644 index 0000000000..99242c3b99 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/svelte/src/components/SelectableListWithItemDetails/SelectableListWithItemDetails.css @@ -0,0 +1,38 @@ +:root { + --list-bg: #f8f8f8; + --list-border-color: #ccc; + --list-item-bg: #fff; + --list-item-hover-bg: #e0e0e0; + --list-item-selected-bg: #d0ebff; + --text-color: #333; + --details-bg: #f0f8ff; +} + +.selectable-list { + max-height: 300px; + background-color: var(--list-bg); + border: 1px solid var(--list-border-color); +} + +.list-item { + padding: 8px; + background-color: var(--list-item-bg); + color: var(--text-color); + border-bottom: 1px solid var(--list-border-color); + cursor: pointer; + transition: background-color 0.2s; +} + +.list-item:hover { + background-color: var(--list-item-hover-bg); +} + +.list-item.selected { + background-color: var(--list-item-selected-bg); +} + +.item-details { + background-color: var(--details-bg); + padding: 4px; + margin-top: 4px; +} \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/svelte/src/components/SelectableListWithItemDetails/SelectableListWithItemDetails.stories.ts b/pkgs/experimental/swarmakit/libs/svelte/src/components/SelectableListWithItemDetails/SelectableListWithItemDetails.stories.ts new file mode 100644 index 0000000000..aac086365e --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/svelte/src/components/SelectableListWithItemDetails/SelectableListWithItemDetails.stories.ts @@ -0,0 +1,86 @@ +import SelectableListWithItemDetails from './SelectableListWithItemDetails.svelte'; +import type { Meta, StoryFn } from '@storybook/svelte'; + +const meta: Meta = { + title: 'component/Lists/SelectableListWithItemDetails', + component: SelectableListWithItemDetails, + tags: ['autodocs'], + argTypes: { + items: { control: 'object' }, + selectedItemId: { control: 'number' }, + detailsOpen: { control: 'boolean' } + }, + parameters: { + layout: 'centered', + viewport: { + viewports: { + smallMobile: { name: 'Small Mobile', styles: { width: '320px', height: '568px' } }, + largeMobile: { name: 'Large Mobile', styles: { width: '414px', height: '896px' } }, + tablet: { name: 'Tablet', styles: { width: '768px', height: '1024px' } }, + desktop: { name: 'Desktop', styles: { width: '1024px', height: '768px' } }, + } + } + } +}; + +export default meta; + +const Template:StoryFn = (args) => ({ + Component:SelectableListWithItemDetails, + props:args, +}); + +export const Default = Template.bind({}); +Default.args = { + items: Array.from({ length: 5 }, (_, i) => ({ + id: i + 1, + text: `Item ${i + 1}`, + details: `Details for Item ${i + 1}` + })), + selectedItemId: null, + detailsOpen: false +}; + +export const ItemSelected = Template.bind({}); +ItemSelected.args = { + items: Array.from({ length: 5 }, (_, i) => ({ + id: i + 1, + text: `Item ${i + 1}`, + details: `Details for Item ${i + 1}` + })), + selectedItemId: 1, + detailsOpen: false +}; + +export const ItemDeselected = Template.bind({}); +ItemDeselected.args = { + items: Array.from({ length: 5 }, (_, i) => ({ + id: i + 1, + text: `Item ${i + 1}`, + details: `Details for Item ${i + 1}` + })), + selectedItemId: null, + detailsOpen: false +}; + +export const DetailsOpened = Template.bind({}); +DetailsOpened.args = { + items: Array.from({ length: 5 }, (_, i) => ({ + id: i + 1, + text: `Item ${i + 1}`, + details: `Details for Item ${i + 1}` + })), + selectedItemId: 1, + detailsOpen: true +}; + +export const DetailsClosed = Template.bind({}); +DetailsClosed.args = { + items: Array.from({ length: 5 }, (_, i) => ({ + id: i + 1, + text: `Item ${i + 1}`, + details: `Details for Item ${i + 1}` + })), + selectedItemId: 1, + detailsOpen: false +}; \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/svelte/src/components/SelectableListWithItemDetails/SelectableListWithItemDetails.svelte b/pkgs/experimental/swarmakit/libs/svelte/src/components/SelectableListWithItemDetails/SelectableListWithItemDetails.svelte new file mode 100644 index 0000000000..56b873d321 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/svelte/src/components/SelectableListWithItemDetails/SelectableListWithItemDetails.svelte @@ -0,0 +1,42 @@ + + +
    + {#each items as item (item.id)} +
    toggleSelection(item.id)} + on:keydown={(e)=>{if(e.key === 'Enter' || e.key === ' '){toggleSelection(item.id)}}} + role="menuitem" + aria-current={selectedItemId === item.id} + tabindex="0" + > + {item.text} + {#if selectedItemId === item.id && detailsOpen} +
    {item.details}
    + {/if} +
    + {/each} +
    + + \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/svelte/src/components/SignalStrengthIndicator/SignalStrengthIndicator.css b/pkgs/experimental/swarmakit/libs/svelte/src/components/SignalStrengthIndicator/SignalStrengthIndicator.css new file mode 100644 index 0000000000..d0dd2cd7e7 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/svelte/src/components/SignalStrengthIndicator/SignalStrengthIndicator.css @@ -0,0 +1,47 @@ +:root { + --bar-color: #e0e0e0; + --bar-active-color: #4caf50; + --bar-weak-color: #ff9800; +} + +.signal-strength-indicator { + display: flex; + align-items: flex-end; + gap: 0.2rem; +} + +.bar { + width: 0.4rem; + background-color: var(--bar-color); + transition: background-color 0.3s; +} + +.bar1 { + height: 0.6rem; +} + +.bar2 { + height: 1.2rem; +} + +.bar3 { + height: 1.8rem; +} + +.bar4 { + height: 2.4rem; +} + +.bar5 { + height: 3rem; +} + +[data-strength='full'] .bar { + background-color: var(--bar-active-color); +} + +[data-strength='weak'] .bar1, +[data-strength='weak'] .bar2, +[data-strength='weak'] .bar3 { + background-color: var(--bar-weak-color); +} \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/svelte/src/components/SignalStrengthIndicator/SignalStrengthIndicator.stories.ts b/pkgs/experimental/swarmakit/libs/svelte/src/components/SignalStrengthIndicator/SignalStrengthIndicator.stories.ts new file mode 100644 index 0000000000..ed07e07c88 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/svelte/src/components/SignalStrengthIndicator/SignalStrengthIndicator.stories.ts @@ -0,0 +1,49 @@ +import SignalStrengthIndicator from './SignalStrengthIndicator.svelte'; +import type { Meta, StoryFn } from '@storybook/svelte'; + +const meta: Meta = { + title: 'component/Indicators/SignalStrengthIndicator', + component: SignalStrengthIndicator, + tags: ['autodocs'], + argTypes: { + strength: { control: 'select', options: ['full', 'weak', 'none'] }, + }, + parameters: { + layout: 'centered', + viewport: { + viewports: { + smallMobile: { name: 'Small Mobile', styles: { width: '320px', height: '568px' } }, + largeMobile: { name: 'Large Mobile', styles: { width: '414px', height: '896px' } }, + tablet: { name: 'Tablet', styles: { width: '768px', height: '1024px' } }, + desktop: { name: 'Desktop', styles: { width: '1024px', height: '768px' } }, + } + } + } +}; + +export default meta; + +const Template:StoryFn = (args) => ({ + Component:SignalStrengthIndicator, + props:args, +}); + +export const Default = Template.bind({}); +Default.args = { + strength: 'none', +}; + +export const FullSignal = Template.bind({}); +FullSignal.args = { + strength: 'full', +}; + +export const WeakSignal = Template.bind({}); +WeakSignal.args = { + strength: 'weak', +}; + +export const NoSignal = Template.bind({}); +NoSignal.args = { + strength: 'none', +}; \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/svelte/src/components/SignalStrengthIndicator/SignalStrengthIndicator.svelte b/pkgs/experimental/swarmakit/libs/svelte/src/components/SignalStrengthIndicator/SignalStrengthIndicator.svelte new file mode 100644 index 0000000000..707440efc7 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/svelte/src/components/SignalStrengthIndicator/SignalStrengthIndicator.svelte @@ -0,0 +1,21 @@ + + +
    +
    +
    +
    +
    +
    +
    + + \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/svelte/src/components/Slider/Slider.css b/pkgs/experimental/swarmakit/libs/svelte/src/components/Slider/Slider.css new file mode 100644 index 0000000000..1585124305 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/svelte/src/components/Slider/Slider.css @@ -0,0 +1,34 @@ +:root { + --slider-thumb-size: 16px; + --slider-track-height: 4px; + --slider-color: #007bff; + --slider-bg: #ddd; + --slider-bg-disabled: #f5f5f5; +} + +.slider { + appearance: none; + width: 100%; + height: var(--slider-track-height); + background: var(--slider-bg); + outline: none; + opacity: 0.7; + transition: opacity 0.2s; +} + +.slider:disabled { + background: var(--slider-bg-disabled); + cursor: not-allowed; +} + +.slider::-webkit-slider-thumb { + appearance: none; + width: var(--slider-thumb-size); + height: var(--slider-thumb-size); + background: var(--slider-color); + cursor: pointer; +} + +.slider:disabled::-webkit-slider-thumb { + background: #ccc; +} \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/svelte/src/components/Slider/Slider.stories.ts b/pkgs/experimental/swarmakit/libs/svelte/src/components/Slider/Slider.stories.ts new file mode 100644 index 0000000000..180dc07b89 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/svelte/src/components/Slider/Slider.stories.ts @@ -0,0 +1,69 @@ +import Slider from './Slider.svelte'; +import type { Meta, StoryFn } from '@storybook/svelte'; + +const meta: Meta = { + title: 'component/Input/Slider', + component: Slider, + tags: ['autodocs'], + argTypes: { + value: { control: 'number' }, + min: { control: 'number' }, + max: { control: 'number' }, + isDisabled: { control: 'boolean' }, + step: { control: 'number' } + }, + parameters: { + layout: 'centered', + viewport: { + viewports: { + smallMobile: { name: 'Small Mobile', styles: { width: '320px', height: '568px' } }, + largeMobile: { name: 'Large Mobile', styles: { width: '414px', height: '896px' } }, + tablet: { name: 'Tablet', styles: { width: '768px', height: '1024px' } }, + desktop: { name: 'Desktop', styles: { width: '1024px', height: '768px' } }, + } + } + } +}; + +export default meta; + +const Template:StoryFn = (args) => ({ + Component: Slider, + props:args, +}); + +export const Default = Template.bind({}); +Default.args = { + value: 50, + min: 0, + max: 100, + isDisabled: false, + step: 1 +}; + +export const Min = Template.bind({}); +Min.args = { + value: 0, + min: 0, + max: 100, + isDisabled: false, + step: 1 +}; + +export const Max = Template.bind({}); +Max.args = { + value: 100, + min: 0, + max: 100, + isDisabled: false, + step: 1 +}; + +export const Disabled = Template.bind({}); +Disabled.args = { + value: 50, + min: 0, + max: 100, + isDisabled: true, + step: 1 +}; \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/svelte/src/components/Slider/Slider.svelte b/pkgs/experimental/swarmakit/libs/svelte/src/components/Slider/Slider.svelte new file mode 100644 index 0000000000..67960cf945 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/svelte/src/components/Slider/Slider.svelte @@ -0,0 +1,26 @@ + + + + + \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/svelte/src/components/SortableList/SortableList.css b/pkgs/experimental/swarmakit/libs/svelte/src/components/SortableList/SortableList.css new file mode 100644 index 0000000000..beaca42c4d --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/svelte/src/components/SortableList/SortableList.css @@ -0,0 +1,22 @@ +.sortable-list { + list-style-type: none; + padding: 0; + margin: 0; +} + +.sortable-item { + padding: 10px; + margin: 5px 0; + border: 1px solid #ddd; + background-color: #f9f9f9; + cursor: move; +} + +.sortable-item[aria-grabbed="true"] { + background-color: #e0e0e0; +} + +.sortable-item[aria-disabled="true"] { + cursor: not-allowed; + background-color: #f0f0f0; +} \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/svelte/src/components/SortableList/SortableList.stories.ts b/pkgs/experimental/swarmakit/libs/svelte/src/components/SortableList/SortableList.stories.ts new file mode 100644 index 0000000000..0cd18618e0 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/svelte/src/components/SortableList/SortableList.stories.ts @@ -0,0 +1,49 @@ +import SortableList from './SortableList.svelte'; +import type { Meta, StoryFn} from '@storybook/svelte'; + +const meta: Meta = { + title: 'Components/Lists/SortableList', + component: SortableList, + tags: ['autodocs'], + argTypes: { + items: { + control: { type: 'object' }, + }, + disabled: { + control: { type: 'boolean' }, + }, + }, +}; + +export default meta; + +const Template: StoryFn = (args) => ({ + Component: SortableList, + props: args, +}); + +const sampleItems = ['Item 1', 'Item 2', 'Item 3', 'Item 4']; + +export const Default = Template.bind({}); +Default.args = { + items: sampleItems, + disabled: false, +}; + +export const Dragging = Template.bind({}); +Dragging.args = { + items: sampleItems, + disabled: false, +}; + +export const Sorted = Template.bind({}); +Sorted.args = { + items: ['Item 2', 'Item 1', 'Item 3', 'Item 4'], + disabled: false, +}; + +export const Disabled = Template.bind({}); +Disabled.args = { + items: sampleItems, + disabled: true, +}; \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/svelte/src/components/SortableList/SortableList.svelte b/pkgs/experimental/swarmakit/libs/svelte/src/components/SortableList/SortableList.svelte new file mode 100644 index 0000000000..0fba09a0e8 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/svelte/src/components/SortableList/SortableList.svelte @@ -0,0 +1,60 @@ + + +
      + {#each items as item, index} +
    • handleDragStart(event, index)} + on:dragover={(event) => handleDragOver(event, index)} + on:drop={handleDrop} + tabindex="0" + role="menuitem" + aria-grabbed={draggingIndex === index ? 'true' : 'false'} + aria-disabled={disabled} + > + {item} +
    • + {/each} +
    + + \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/svelte/src/components/SortableTable/SortableTable.css b/pkgs/experimental/swarmakit/libs/svelte/src/components/SortableTable/SortableTable.css new file mode 100644 index 0000000000..4adef132bc --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/svelte/src/components/SortableTable/SortableTable.css @@ -0,0 +1,22 @@ +.sortable-table { + width: 100%; + border-collapse: collapse; +} + +.sortable-table th, .sortable-table td { + padding: 8px; + text-align: left; + border-bottom: 1px solid #ddd; +} + +.sortable-table th.sortable { + cursor: pointer; + user-select: none; +} + +.filter input[type="text"] { + margin-bottom: 10px; + padding: 5px; + width: 100%; + box-sizing: border-box; +} \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/svelte/src/components/SortableTable/SortableTable.stories.ts b/pkgs/experimental/swarmakit/libs/svelte/src/components/SortableTable/SortableTable.stories.ts new file mode 100644 index 0000000000..9affd677e9 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/svelte/src/components/SortableTable/SortableTable.stories.ts @@ -0,0 +1,60 @@ +import SortableTable from './SortableTable.svelte'; +import type { Meta, StoryFn } from '@storybook/svelte'; +import type { TableColumn, TableRow } from './SortableTable.svelte'; + +const meta: Meta = { + title: 'Components/Lists/SortableTable', + component: SortableTable, + tags: ['autodocs'], + argTypes: { + columns: { + control: { type: 'object' }, + }, + rows: { + control: { type: 'object' }, + }, + }, +}; + +export default meta; + +const Template: StoryFn = (args) => ({ + Component: SortableTable, + props: args, +}); + +const sampleColumns: TableColumn[] = [ + { key: 'id', label: 'ID', sortable: true }, + { key: 'name', label: 'Name', sortable: true }, + { key: 'age', label: 'Age', sortable: true }, +]; + +const sampleRows: TableRow[] = [ + { id: 1, name: 'Alice', age: 30 }, + { id: 2, name: 'Bob', age: 25 }, + { id: 3, name: 'Charlie', age: 35 }, +]; + +export const Default = Template.bind({}); +Default.args = { + columns: sampleColumns, + rows: sampleRows, +}; + +export const Sorting = Template.bind({}); +Sorting.args = { + columns: sampleColumns, + rows: sampleRows, +}; + +export const Filtering = Template.bind({}); +Filtering.args = { + columns: sampleColumns, + rows: sampleRows, +}; + +export const RowSelection = Template.bind({}); +RowSelection.args = { + columns: sampleColumns, + rows: sampleRows, +}; \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/svelte/src/components/SortableTable/SortableTable.svelte b/pkgs/experimental/swarmakit/libs/svelte/src/components/SortableTable/SortableTable.svelte new file mode 100644 index 0000000000..72aaf5b468 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/svelte/src/components/SortableTable/SortableTable.svelte @@ -0,0 +1,95 @@ + + + + + +
    + filterRows()} + aria-label="Filter rows" + /> +
    + + + + {#each columns as column} + + {/each} + + + + {#each filterRows() as row (row.id)} + selectRow(row)} + on:keydown={(e) => e.key === "Enter" && selectRow(row)} + tabindex="0" + > + {#each columns as column} + + {/each} + + {/each} + +
    column.sortable && sortRows(column.key)} + on:keydown={(e) => e.key === "Enter" && column.sortable && sortRows(column.key)} + tabindex="0" + aria-sort={sortKey === column.key ? (sortOrder === 'asc' ? 'ascending' : 'descending') : 'none'} + role="columnheader" + class:sortable={column.sortable} + > + {column.label} {#if column.sortable && sortKey === column.key}{sortOrder === 'asc' ? '↑' : '↓'}{/if} +
    {row[column.key]}
    + + \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/svelte/src/components/StatusDots/StatusDots.css b/pkgs/experimental/swarmakit/libs/svelte/src/components/StatusDots/StatusDots.css new file mode 100644 index 0000000000..ba36e821bc --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/svelte/src/components/StatusDots/StatusDots.css @@ -0,0 +1,33 @@ +:root { + --dot-size: 1rem; + --dot-online-color: #4caf50; + --dot-offline-color: #f44336; + --dot-busy-color: #ff9800; + --dot-idle-color: #9e9e9e; +} + +.status-dots { + display: inline-flex; + align-items: center; + justify-content: center; +} + +.dot { + width: var(--dot-size); + height: var(--dot-size); + border-radius: 50%; + background-color: var(--dot-offline-color); + transition: background-color 0.3s; +} + +[data-status='online'] .dot { + background-color: var(--dot-online-color); +} + +[data-status='busy'] .dot { + background-color: var(--dot-busy-color); +} + +[data-status='idle'] .dot { + background-color: var(--dot-idle-color); +} \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/svelte/src/components/StatusDots/StatusDots.stories.ts b/pkgs/experimental/swarmakit/libs/svelte/src/components/StatusDots/StatusDots.stories.ts new file mode 100644 index 0000000000..742c612c32 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/svelte/src/components/StatusDots/StatusDots.stories.ts @@ -0,0 +1,54 @@ +import StatusDots from './StatusDots.svelte'; +import type { Meta, StoryFn } from '@storybook/svelte'; + +const meta: Meta = { + title: 'component/Indicators/StatusDots', + component: StatusDots, + tags: ['autodocs'], + argTypes: { + status: { control: 'select', options: ['online', 'offline', 'busy', 'idle'] }, + }, + parameters: { + layout: 'centered', + viewport: { + viewports: { + smallMobile: { name: 'Small Mobile', styles: { width: '320px', height: '568px' } }, + largeMobile: { name: 'Large Mobile', styles: { width: '414px', height: '896px' } }, + tablet: { name: 'Tablet', styles: { width: '768px', height: '1024px' } }, + desktop: { name: 'Desktop', styles: { width: '1024px', height: '768px' } }, + } + } + } +}; + +export default meta; + +const Template:StoryFn = (args) => ({ + Component:StatusDots, + props:args, +}); + +export const Default = Template.bind({}); +Default.args = { + status: 'offline', +}; + +export const Online = Template.bind({}); +Online.args = { + status: 'online', +}; + +export const Offline = Template.bind({}); +Offline.args = { + status: 'offline', +}; + +export const Busy = Template.bind({}); +Busy.args = { + status: 'busy', +}; + +export const Idle = Template.bind({}); +Idle.args = { + status: 'idle', +}; \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/svelte/src/components/StatusDots/StatusDots.svelte b/pkgs/experimental/swarmakit/libs/svelte/src/components/StatusDots/StatusDots.svelte new file mode 100644 index 0000000000..d82dc7b486 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/svelte/src/components/StatusDots/StatusDots.svelte @@ -0,0 +1,17 @@ + + +
    +
    +
    + + \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/svelte/src/components/Stepper/Stepper.css b/pkgs/experimental/swarmakit/libs/svelte/src/components/Stepper/Stepper.css new file mode 100644 index 0000000000..b84010fb3b --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/svelte/src/components/Stepper/Stepper.css @@ -0,0 +1,48 @@ +:root { + --step-size: 2rem; + --step-completed-color: #4caf50; + --step-active-color: #2196f3; + --step-disabled-color: #9e9e9e; + --step-separator-color: #cccccc; + --step-label-color: #000000; +} + +.stepper { + display: flex; + align-items: center; +} + +.step { + display: flex; + align-items: center; + flex-direction: column; + text-align: center; +} + +.step-circle { + width: var(--step-size); + height: var(--step-size); + border-radius: 50%; + background-color: var(--step-disabled-color); + transition: background-color 0.3s; +} + +.step-label { + margin-top: 0.5rem; + color: var(--step-label-color); +} + +[data-status='completed'] .step-circle { + background-color: var(--step-completed-color); +} + +[data-status='active'] .step-circle { + background-color: var(--step-active-color); +} + +.step-separator { + flex-grow: 1; + height: 0.2rem; + background-color: var(--step-separator-color); + margin: 0 0.5rem; +} \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/svelte/src/components/Stepper/Stepper.stories.ts b/pkgs/experimental/swarmakit/libs/svelte/src/components/Stepper/Stepper.stories.ts new file mode 100644 index 0000000000..6e07e7ede6 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/svelte/src/components/Stepper/Stepper.stories.ts @@ -0,0 +1,65 @@ +import Stepper from './Stepper.svelte'; +import type { Meta, StoryFn } from '@storybook/svelte'; + +const meta: Meta = { + title: 'component/Indicators/Stepper', + component: Stepper, + tags: ['autodocs'], + argTypes: { + steps: { control: 'object' }, + }, + parameters: { + layout: 'centered', + viewport: { + viewports: { + smallMobile: { name: 'Small Mobile', styles: { width: '320px', height: '568px' } }, + largeMobile: { name: 'Large Mobile', styles: { width: '414px', height: '896px' } }, + tablet: { name: 'Tablet', styles: { width: '768px', height: '1024px' } }, + desktop: { name: 'Desktop', styles: { width: '1024px', height: '768px' } }, + } + } + } +}; + +export default meta; + +const Template:StoryFn = (args) => ({ + Component:Stepper, + props:args, +}); + +export const Default = Template.bind({}); +Default.args = { + steps: [ + { label: 'Step 1', status: 'completed' }, + { label: 'Step 2', status: 'active' }, + { label: 'Step 3', status: 'disabled' }, + ], +}; + +export const Completed = Template.bind({}); +Completed.args = { + steps: [ + { label: 'Step 1', status: 'completed' }, + { label: 'Step 2', status: 'completed' }, + { label: 'Step 3', status: 'completed' }, + ], +}; + +export const Active = Template.bind({}); +Active.args = { + steps: [ + { label: 'Step 1', status: 'completed' }, + { label: 'Step 2', status: 'active' }, + { label: 'Step 3', status: 'disabled' }, + ], +}; + +export const Disabled = Template.bind({}); +Disabled.args = { + steps: [ + { label: 'Step 1', status: 'disabled' }, + { label: 'Step 2', status: 'disabled' }, + { label: 'Step 3', status: 'disabled' }, + ], +}; \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/svelte/src/components/Stepper/Stepper.svelte b/pkgs/experimental/swarmakit/libs/svelte/src/components/Stepper/Stepper.svelte new file mode 100644 index 0000000000..bde8b804c2 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/svelte/src/components/Stepper/Stepper.svelte @@ -0,0 +1,20 @@ + + + + + \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/svelte/src/components/SystemAlertGlobalNotificationBar/SystemAlertGlobalNotificationBar.css b/pkgs/experimental/swarmakit/libs/svelte/src/components/SystemAlertGlobalNotificationBar/SystemAlertGlobalNotificationBar.css new file mode 100644 index 0000000000..1c351686cb --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/svelte/src/components/SystemAlertGlobalNotificationBar/SystemAlertGlobalNotificationBar.css @@ -0,0 +1,46 @@ +:root { + --notification-padding: 1rem; + --notification-border-radius: 4px; + --success-bg-color: #d4edda; + --success-text-color: #155724; + --error-bg-color: #f8d7da; + --error-text-color: #721c24; + --warning-bg-color: #fff3cd; + --warning-text-color: #856404; + --info-bg-color: #d1ecf1; + --info-text-color: #0c5460; +} + +.notification-bar { + display: flex; + align-items: center; + justify-content: center; + padding: var(--notification-padding); + border-radius: var(--notification-border-radius); + width: 100%; + box-sizing: border-box; +} + +.notification-message { + font-size: 1rem; +} + +[data-type='success'] { + background-color: var(--success-bg-color); + color: var(--success-text-color); +} + +[data-type='error'] { + background-color: var(--error-bg-color); + color: var(--error-text-color); +} + +[data-type='warning'] { + background-color: var(--warning-bg-color); + color: var(--warning-text-color); +} + +[data-type='info'] { + background-color: var(--info-bg-color); + color: var(--info-text-color); +} \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/svelte/src/components/SystemAlertGlobalNotificationBar/SystemAlertGlobalNotificationBar.stories.ts b/pkgs/experimental/swarmakit/libs/svelte/src/components/SystemAlertGlobalNotificationBar/SystemAlertGlobalNotificationBar.stories.ts new file mode 100644 index 0000000000..baa60acc9d --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/svelte/src/components/SystemAlertGlobalNotificationBar/SystemAlertGlobalNotificationBar.stories.ts @@ -0,0 +1,63 @@ +import SystemAlertGlobalNotificationBar from './SystemAlertGlobalNotificationBar.svelte'; +import type { Meta, StoryFn } from '@storybook/svelte'; + +const meta: Meta = { + title: 'component/Indicators/SystemAlertGlobalNotificationBar', + component: SystemAlertGlobalNotificationBar, + tags: ['autodocs'], + argTypes: { + message: { control: 'text' }, + type: { + control: { type: 'select' }, + options: ['success', 'error', 'warning', 'info'] + }, + }, + parameters: { + layout: 'centered', + viewport: { + viewports: { + smallMobile: { name: 'Small Mobile', styles: { width: '320px', height: '568px' } }, + largeMobile: { name: 'Large Mobile', styles: { width: '414px', height: '896px' } }, + tablet: { name: 'Tablet', styles: { width: '768px', height: '1024px' } }, + desktop: { name: 'Desktop', styles: { width: '1024px', height: '768px' } }, + } + } + } +}; + +export default meta; + +const Template:StoryFn = (args) => ({ + Component: SystemAlertGlobalNotificationBar, + props:args, +}); + +export const Default = Template.bind({}); +Default.args = { + message: 'This is an information alert!', + type: 'info' +}; + +export const Success = Template.bind({}); +Success.args = { + message: 'Operation completed successfully!', + type: 'success' +}; + +export const Error = Template.bind({}); +Error.args = { + message: 'An error has occured!', + type: 'error' +}; + +export const Warning = Template.bind({}); +Warning.args = { + message: 'Please be aware of the following warning!', + type: 'warning' +}; + +export const Info = Template.bind({}); +Info.args = { +message: 'This is an information alert!', + type: 'info' +}; \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/svelte/src/components/SystemAlertGlobalNotificationBar/SystemAlertGlobalNotificationBar.svelte b/pkgs/experimental/swarmakit/libs/svelte/src/components/SystemAlertGlobalNotificationBar/SystemAlertGlobalNotificationBar.svelte new file mode 100644 index 0000000000..36d6a9d93a --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/svelte/src/components/SystemAlertGlobalNotificationBar/SystemAlertGlobalNotificationBar.svelte @@ -0,0 +1,13 @@ + + + + + \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/svelte/src/components/Tabs/Tabs.css b/pkgs/experimental/swarmakit/libs/svelte/src/components/Tabs/Tabs.css new file mode 100644 index 0000000000..09071afb33 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/svelte/src/components/Tabs/Tabs.css @@ -0,0 +1,35 @@ +.tabs { + display: flex; + flex-direction: column; +} + +.tab-list { + display: flex; +} + +.tab-button { + padding: 10px; + margin-right: 2px; + background-color: #f1f1f1; + border: none; + cursor: pointer; +} + +.tab-button[aria-selected="true"] { + background-color: #ddd; + font-weight: bold; +} + +.tab-button[aria-disabled="true"] { + cursor: not-allowed; + opacity: 0.5; +} + +.tab-button:hover:not([aria-disabled="true"]) { + background-color: #ccc; +} + +.tab-content { + padding: 10px; + border-top: 1px solid #ddd; +} \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/svelte/src/components/Tabs/Tabs.stories.ts b/pkgs/experimental/swarmakit/libs/svelte/src/components/Tabs/Tabs.stories.ts new file mode 100644 index 0000000000..c7bb6ddde0 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/svelte/src/components/Tabs/Tabs.stories.ts @@ -0,0 +1,59 @@ +import Tabs from './Tabs.svelte'; +import type { Meta, StoryFn } from '@storybook/svelte'; + +const meta: Meta = { + title: 'Components/Lists/Tabs', + component: Tabs, + tags: ['autodocs'], + argTypes: { + tabs: { + control: { type: 'object' }, + }, + activeIndex: { + control: { type: 'number' }, + }, + }, +}; + +export default meta; + +const Template: StoryFn = (args) => ({ + Component: Tabs, + props: args, +}); + +const sampleTabs = [ + { label: 'Tab 1', content: 'Content for Tab 1' }, + { label: 'Tab 2', content: 'Content for Tab 2' }, + { label: 'Tab 3', content: 'Content for Tab 3', disabled: true }, +]; + +export const Default = Template.bind({}); +Default.args = { + tabs: sampleTabs, + activeIndex: 0, +}; + +export const Active = Template.bind({}); +Active.args = { + tabs: sampleTabs, + activeIndex: 1, +}; + +export const Inactive = Template.bind({}); +Inactive.args = { + tabs: sampleTabs, + activeIndex: 2, +}; + +export const Disabled = Template.bind({}); +Disabled.args = { + tabs: sampleTabs, + activeIndex: 0, +}; + +export const Hover = Template.bind({}); +Hover.args = { + tabs: sampleTabs, + activeIndex: 0, +}; \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/svelte/src/components/Tabs/Tabs.svelte b/pkgs/experimental/swarmakit/libs/svelte/src/components/Tabs/Tabs.svelte new file mode 100644 index 0000000000..e825594140 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/svelte/src/components/Tabs/Tabs.svelte @@ -0,0 +1,37 @@ + + +
    +
    + {#each tabs as { label, disabled }, index} + + {/each} +
    +
    + {#if tabs.length > 0} + {tabs[activeIndex]?.content} + {/if} +
    +
    + + \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/svelte/src/components/TaskCompletionCheckList/TaskCompletionCheckList.css b/pkgs/experimental/swarmakit/libs/svelte/src/components/TaskCompletionCheckList/TaskCompletionCheckList.css new file mode 100644 index 0000000000..48df96e446 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/svelte/src/components/TaskCompletionCheckList/TaskCompletionCheckList.css @@ -0,0 +1,23 @@ +:root { + --checklist-padding: 1rem; + --checklist-item-margin: 0.5rem 0; + --checklist-label-color: #333; + --checklist-label-font-size: 1rem; +} + +.checklist { + list-style: none; + padding: var(--checklist-padding); +} + +.checklist li { + margin: var(--checklist-item-margin); + display: flex; + align-items: center; +} + +.checklist label { + margin-left: 0.5rem; + color: var(--checklist-label-color); + font-size: var(--checklist-label-font-size); +} \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/svelte/src/components/TaskCompletionCheckList/TaskCompletionCheckList.stories.ts b/pkgs/experimental/swarmakit/libs/svelte/src/components/TaskCompletionCheckList/TaskCompletionCheckList.stories.ts new file mode 100644 index 0000000000..a3e58a3b80 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/svelte/src/components/TaskCompletionCheckList/TaskCompletionCheckList.stories.ts @@ -0,0 +1,65 @@ +import TaskCompletionCheckList from './TaskCompletionCheckList.svelte'; +import type { Meta, StoryFn } from '@storybook/svelte'; + +const meta: Meta = { + title: 'component/Indicators/TaskCompletionCheckList', + component: TaskCompletionCheckList, + tags: ['autodocs'], + argTypes: { + tasks: { control: 'object' } + }, + parameters: { + layout: 'centered', + viewport: { + viewports: { + smallMobile: { name: 'Small Mobile', styles: { width: '320px', height: '568px' } }, + largeMobile: { name: 'Large Mobile', styles: { width: '414px', height: '896px' } }, + tablet: { name: 'Tablet', styles: { width: '768px', height: '1024px' } }, + desktop: { name: 'Desktop', styles: { width: '1024px', height: '768px' } }, + } + } + } +}; + +export default meta; + +const Template:StoryFn = (args) => ({ + Component:TaskCompletionCheckList, + props:args, +}); + +export const Default = Template.bind({}); +Default.args = { + tasks: [ + { id: 1, label: 'Task 1', completed: false }, + { id: 2, label: 'Task 2', completed: false }, + { id: 3, label: 'Task 3', completed: false } + ] +}; + +export const Checked = Template.bind({}); +Checked.args = { + tasks: [ + { id: 1, label: 'Task 1', completed: true }, + { id: 2, label: 'Task 2', completed: true }, + { id: 3, label: 'Task 3', completed: true } + ] +}; + +export const Unchecked = Template.bind({}); +Unchecked.args = { + tasks: [ + { id: 1, label: 'Task 1', completed: false }, + { id: 2, label: 'Task 2', completed: false }, + { id: 3, label: 'Task 3', completed: false } + ] +}; + +export const PartiallyComplete = Template.bind({}); +PartiallyComplete.args = { + tasks: [ + { id: 1, label: 'Task 1', completed: true }, + { id: 2, label: 'Task 2', completed: false }, + { id: 3, label: 'Task 3', completed: true } + ] +}; \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/svelte/src/components/TaskCompletionCheckList/TaskCompletionCheckList.svelte b/pkgs/experimental/swarmakit/libs/svelte/src/components/TaskCompletionCheckList/TaskCompletionCheckList.svelte new file mode 100644 index 0000000000..3767b301d2 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/svelte/src/components/TaskCompletionCheckList/TaskCompletionCheckList.svelte @@ -0,0 +1,34 @@ + + +
      + {#each tasks as { id, label, completed }} +
    • + toggleTaskCompletion(id)} + /> + +
    • + {/each} +
    + + \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/svelte/src/components/Textarea/Textarea.css b/pkgs/experimental/swarmakit/libs/svelte/src/components/Textarea/Textarea.css new file mode 100644 index 0000000000..95922f4699 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/svelte/src/components/Textarea/Textarea.css @@ -0,0 +1,27 @@ +:root { + --textarea-border-color: #ccc; + --textarea-focus-border-color: #007bff; + --textarea-bg-color: #fff; + --textarea-disabled-bg-color: #f7f7f7; +} + +.textarea { + width: 100%; + padding: 10px; + border: 1px solid var(--textarea-border-color); + border-radius: 4px; + background-color: var(--textarea-bg-color); + font-size: 16px; + resize: vertical; +} + +.textarea:focus { + border-color: var(--textarea-focus-border-color); + outline: none; +} + +.textarea:disabled { + background-color: var(--textarea-disabled-bg-color); + opacity: 0.7; + cursor: not-allowed; +} \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/svelte/src/components/Textarea/Textarea.stories.ts b/pkgs/experimental/swarmakit/libs/svelte/src/components/Textarea/Textarea.stories.ts new file mode 100644 index 0000000000..edfeb2a904 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/svelte/src/components/Textarea/Textarea.stories.ts @@ -0,0 +1,42 @@ +import Textarea from './Textarea.svelte'; +import type { Meta, StoryFn } from '@storybook/svelte'; + +const meta: Meta + + \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/svelte/src/components/TimelineList/TimelineList.css b/pkgs/experimental/swarmakit/libs/svelte/src/components/TimelineList/TimelineList.css new file mode 100644 index 0000000000..6bfc4557da --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/svelte/src/components/TimelineList/TimelineList.css @@ -0,0 +1,31 @@ +.timeline { + list-style: none; + padding: 0; + margin: 0; +} + +.timeline-event { + margin-bottom: 20px; + position: relative; +} + +.event-content { + background-color: #f9f9f9; + padding: 15px; + border-radius: 5px; + transition: background-color 0.3s; + cursor: pointer; +} + +.event-content.active { + background-color: #e0f7fa; + font-weight: bold; +} + +.event-content.completed { + background-color: #c8e6c9; +} + +.event-content:hover:not(.completed) { + background-color: #eee; +} \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/svelte/src/components/TimelineList/TimelineList.stories.ts b/pkgs/experimental/swarmakit/libs/svelte/src/components/TimelineList/TimelineList.stories.ts new file mode 100644 index 0000000000..f507cd0bbe --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/svelte/src/components/TimelineList/TimelineList.stories.ts @@ -0,0 +1,51 @@ +import TimelineList from './TimelineList.svelte'; +import type { Meta, StoryFn } from '@storybook/svelte'; + +const meta: Meta = { + title: 'Components/Lists/TimelineList', + component: TimelineList, + tags: ['autodocs'], + argTypes: { + events: { + control: { type: 'object' }, + }, + }, +}; + +export default meta; + +const Template: StoryFn = (args) => ({ + Component: TimelineList, + props: args, +}); + +const sampleEvents = [ + { title: 'Event 1', description: 'Description for Event 1', completed: true, active: false }, + { title: 'Event 2', description: 'Description for Event 2', completed: false, active: true }, + { title: 'Event 3', description: 'Description for Event 3', completed: false, active: false }, +]; + +export const Default = Template.bind({}); +Default.args = { + events: sampleEvents, +}; + +export const Active = Template.bind({}); +Active.args = { + events: sampleEvents.map((event, i) => ({ ...event, active: i === 1 })), +}; + +export const Completed = Template.bind({}); +Completed.args = { + events: sampleEvents.map((event) => ({ ...event, completed: true })), +}; + +export const Hover = Template.bind({}); +Hover.args = { + events: sampleEvents, +}; + +export const Inactive = Template.bind({}); +Inactive.args = { + events: sampleEvents.map((event) => ({ ...event, active: false })), +}; \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/svelte/src/components/TimelineList/TimelineList.svelte b/pkgs/experimental/swarmakit/libs/svelte/src/components/TimelineList/TimelineList.svelte new file mode 100644 index 0000000000..7368d4d4b9 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/svelte/src/components/TimelineList/TimelineList.svelte @@ -0,0 +1,33 @@ + + +
      + {#each events as { title, description, completed, active }, index} +
    • +
      handleClick(index)} + on:keydown={(event) => event.key === 'Enter' && handleClick(index)} + role='button' + > +

      {title}

      +

      {description}

      +
      +
    • + {/each} +
    + + \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/svelte/src/components/Toast/Toast.css b/pkgs/experimental/swarmakit/libs/svelte/src/components/Toast/Toast.css new file mode 100644 index 0000000000..abfe86cef7 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/svelte/src/components/Toast/Toast.css @@ -0,0 +1,58 @@ +:root { + --toast-padding: 1rem; + --toast-margin: 0.5rem; + --toast-border-radius: 4px; + --toast-font-size: 1rem; + --toast-success-bg: #d4edda; + --toast-error-bg: #f8d7da; + --toast-warning-bg: #fff3cd; + --toast-info-bg: #d1ecf1; + --toast-color: #333; + --toast-button-color: #999; +} + +.toast { + position: fixed; + bottom: var(--toast-margin); + left: 50%; + transform: translateX(-50%); + padding: var(--toast-padding); + border-radius: var(--toast-border-radius); + font-size: var(--toast-font-size); + color: var(--toast-color); + display: flex; + justify-content: space-between; + align-items: center; + width: calc(100% - 2 * var(--toast-margin)); + max-width: 500px; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); +} + +.toast p { + margin: 0; + flex-grow: 1; +} + +.toast button { + background: none; + border: none; + color: var(--toast-button-color); + cursor: pointer; + font-size: 1.2rem; +} + +.toast--success { + background-color: var(--toast-success-bg); +} + +.toast--error { + background-color: var(--toast-error-bg); +} + +.toast--warning { + background-color: var(--toast-warning-bg); +} + +.toast--info { + background-color: var(--toast-info-bg); +} \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/svelte/src/components/Toast/Toast.stories.ts b/pkgs/experimental/swarmakit/libs/svelte/src/components/Toast/Toast.stories.ts new file mode 100644 index 0000000000..aa1953ac7a --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/svelte/src/components/Toast/Toast.stories.ts @@ -0,0 +1,74 @@ +import Toast from './Toast.svelte'; +import type { Meta, StoryFn } from '@storybook/svelte'; + +const meta: Meta = { + title: 'component/Indicators/Toast', + component: Toast, + tags: ['autodocs'], + argTypes: { + message: { control: 'text' }, + type: { control: 'select', options: ['success', 'error', 'warning', 'info'] }, + visible: { control: 'boolean' } + }, + parameters: { + layout: 'centered', + viewport: { + viewports: { + smallMobile: { name: 'Small Mobile', styles: { width: '320px', height: '568px' } }, + largeMobile: { name: 'Large Mobile', styles: { width: '414px', height: '896px' } }, + tablet: { name: 'Tablet', styles: { width: '768px', height: '1024px' } }, + desktop: { name: 'Desktop', styles: { width: '1024px', height: '768px' } }, + } + } + } +}; + +export default meta; + +const Template:StoryFn = (args) => ({ + Component: Toast, + props:args, +}) + +export const Default = Template.bind ({}); +Default.args = { + message: 'This is a default toast message.', + type: 'info', + visible: true +} + +export const Success = Template.bind ({}); +Success.args = { + message: 'Operation completed successfully!', + type: 'success', + visible: true +}; + + +export const Error = Template.bind ({}); +Error.args = { + message: 'There was an error processing your request.', + type: 'error', + visible: true +}; + +export const Warning = Template.bind ({}); +Warning.args = { + message: 'This is a warning message.', + type: 'warning', + visible: true +}; + +export const Info = Template.bind ({}); +Info.args = { + message: 'This is some informative text.', + type: 'info', + visible: true +}; + +export const Dismissed = Template.bind ({}); +Dismissed.args = { + message: 'This message has been dismissed.', + type: 'info', + visible: false +}; \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/svelte/src/components/Toast/Toast.svelte b/pkgs/experimental/swarmakit/libs/svelte/src/components/Toast/Toast.svelte new file mode 100644 index 0000000000..4bdd67f1ea --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/svelte/src/components/Toast/Toast.svelte @@ -0,0 +1,27 @@ + + +{#if visible} + +{/if} + + \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/svelte/src/components/ToggleSwitch/ToggleSwitch.css b/pkgs/experimental/swarmakit/libs/svelte/src/components/ToggleSwitch/ToggleSwitch.css new file mode 100644 index 0000000000..891ae4be6b --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/svelte/src/components/ToggleSwitch/ToggleSwitch.css @@ -0,0 +1,58 @@ +:root { + --toggle-width: 50px; + --toggle-height: 25px; + --toggle-bg-color: #ccc; + --toggle-checked-bg-color: #4caf50; + --toggle-slider-color: #fff; + --toggle-disabled-opacity: 0.5; +} + +.toggle-switch { + position: relative; + display: inline-block; + width: var(--toggle-width); + height: var(--toggle-height); +} + +.toggle-switch input { + opacity: 0; + width: 0; + height: 0; +} + +.slider { + position: absolute; + cursor: pointer; + top: 0; + left: 0; + right: 0; + bottom: 0; + background-color: var(--toggle-bg-color); + transition: 0.4s; + border-radius: var(--toggle-height); +} + +.slider:before { + position: absolute; + content: ""; + height: calc(var(--toggle-height) - 4px); + width: calc(var(--toggle-height) - 4px); + left: 2px; + bottom: 2px; + background-color: var(--toggle-slider-color); + transition: 0.4s; + border-radius: 50%; +} + +input:checked + .slider { + background-color: var(--toggle-checked-bg-color); +} + +input:checked + .slider:before { + transform: translateX(calc(var(--toggle-width) - var(--toggle-height))); +} + +input:disabled + .slider { + opacity: var(--toggle-disabled-opacity); + cursor: not-allowed; +} \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/svelte/src/components/ToggleSwitch/ToggleSwitch.stories.ts b/pkgs/experimental/swarmakit/libs/svelte/src/components/ToggleSwitch/ToggleSwitch.stories.ts new file mode 100644 index 0000000000..412d5e4505 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/svelte/src/components/ToggleSwitch/ToggleSwitch.stories.ts @@ -0,0 +1,55 @@ +import ToggleSwitch from './ToggleSwitch.svelte'; +import type { Meta, StoryFn } from '@storybook/svelte'; + +const meta: Meta = { + title: 'component/Forms/ToggleSwitch', + component: ToggleSwitch, + tags: ['autodocs'], + argTypes: { + checked: { control: 'boolean' }, + disabled: { control: 'boolean' }, + }, + parameters: { + layout: 'fullscreen', + viewport: { + viewports: { + smallMobile: { name: 'Small Mobile', styles: { width: '320px', height: '568px' } }, + largeMobile: { name: 'Large Mobile', styles: { width: '414px', height: '896px' } }, + tablet: { name: 'Tablet', styles: { width: '768px', height: '1024px' } }, + desktop: { name: 'Desktop', styles: { width: '1024px', height: '768px' } }, + } + } + } +}; + +export default meta; + +const Template:StoryFn = (args) => ({ + Component: ToggleSwitch, + props:args +}); + +export const Default = Template.bind({}); +Default.args = { + checked: false, + disabled: false, +}; + + +export const On = Template.bind({}); +On.args = { + checked: true, + disabled: false, +}; + +export const Off = Template.bind({}); +Off.args = { + checked: false, + disabled: false, +}; + +export const Disabled = Template.bind({}); +Disabled.args = { + checked: false, + disabled: true, +}; \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/svelte/src/components/ToggleSwitch/ToggleSwitch.svelte b/pkgs/experimental/swarmakit/libs/svelte/src/components/ToggleSwitch/ToggleSwitch.svelte new file mode 100644 index 0000000000..30055b7032 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/svelte/src/components/ToggleSwitch/ToggleSwitch.svelte @@ -0,0 +1,27 @@ + + + + + \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/svelte/src/components/TreeviewList/TreeviewList.css b/pkgs/experimental/swarmakit/libs/svelte/src/components/TreeviewList/TreeviewList.css new file mode 100644 index 0000000000..93afce63ab --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/svelte/src/components/TreeviewList/TreeviewList.css @@ -0,0 +1,30 @@ +.treeview { + list-style: none; + padding: 0; + margin: 0; +} + +.tree-node { + margin-bottom: 10px; + position: relative; +} + +.node-content { + background-color: #f9f9f9; + padding: 10px; + border-radius: 5px; + transition: background-color 0.3s; + cursor: pointer; +} + +.node-content.expanded { + background-color: #e0f7fa; +} + +.node-content.selected { + background-color: #c8e6c9; +} + +.node-content:hover { + background-color: #eee; +} \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/svelte/src/components/TreeviewList/TreeviewList.stories.ts b/pkgs/experimental/swarmakit/libs/svelte/src/components/TreeviewList/TreeviewList.stories.ts new file mode 100644 index 0000000000..5ca4f30c2c --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/svelte/src/components/TreeviewList/TreeviewList.stories.ts @@ -0,0 +1,56 @@ +import TreeviewList from './TreeviewList.svelte'; +import type { Meta, StoryFn } from '@storybook/svelte'; + +const meta: Meta = { + title: 'Components/Lists/TreeviewList', + component: TreeviewList, + tags: ['autodocs'], + argTypes: { + nodes: { + control: { type: 'object' }, + }, + }, +}; + +export default meta; + +const Template: StoryFn = (args) => ({ + Component: TreeviewList, + props: args, +}); + +const sampleNodes = [ + { label: 'Node 1', expanded: true, selected: false, children: [ + { label: 'Child 1.1', expanded: false, selected: false }, + { label: 'Child 1.2', expanded: false, selected: false } + ]}, + { label: 'Node 2', expanded: false, selected: false, children: [ + { label: 'Child 2.1', expanded: false, selected: false } + ]}, + { label: 'Node 3', expanded: false, selected: false } +]; + +export const Default = Template.bind({}); +Default.args = { + nodes: sampleNodes, +}; + +export const NodeExpanded = Template.bind({}); +NodeExpanded.args = { + nodes: sampleNodes.map((node, i) => ({ ...node, expanded: i === 0 })), +}; + +export const NodeCollapsed = Template.bind({}); +NodeCollapsed.args = { + nodes: sampleNodes.map((node) => ({ ...node, expanded: false })), +}; + +export const Hover = Template.bind({}); +Hover.args = { + nodes: sampleNodes, +}; + +export const Selected = Template.bind({}); +Selected.args = { + nodes: sampleNodes.map((node, i) => ({ ...node, selected: i === 1 })), +}; \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/svelte/src/components/TreeviewList/TreeviewList.svelte b/pkgs/experimental/swarmakit/libs/svelte/src/components/TreeviewList/TreeviewList.svelte new file mode 100644 index 0000000000..05167b091d --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/svelte/src/components/TreeviewList/TreeviewList.svelte @@ -0,0 +1,50 @@ + + +
      + {#each nodes as { label, children, expanded, selected }, index} +
    • +
      toggleNode(index)} + on:keydown={(event) => event.key === 'Enter' && toggleNode(index)} + > + selectNode(index)} on:keydown={(event) => event.key === ' ' && selectNode(index)}>{label} +
      + {#if expanded && children} +
        + {#each children as child} + + {/each} +
      + {/if} +
    • + {/each} +
    + + \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/svelte/src/components/Upload/Upload.css b/pkgs/experimental/swarmakit/libs/svelte/src/components/Upload/Upload.css new file mode 100644 index 0000000000..6889e182d2 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/svelte/src/components/Upload/Upload.css @@ -0,0 +1,42 @@ +:root { + --upload-padding: 1rem; + --upload-margin: 0.5rem; + --upload-border-radius: 4px; + --upload-font-size: 1rem; + --upload-bg: #f7f7f7; + --upload-color: #333; + --upload-progress-bg: #e0e0e0; + --upload-progress-color: #76c7c0; +} + +.upload { + padding: var(--upload-padding); + margin: var(--upload-margin); + border-radius: var(--upload-border-radius); + font-size: var(--upload-font-size); + background-color: var(--upload-bg); + color: var(--upload-color); + width: 100%; + max-width: 500px; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); +} + +.upload p { + margin: 0 0 0.5rem 0; +} + +.upload progress { + width: 100%; + height: 20px; + appearance: none; +} + +.upload progress::-webkit-progress-bar { + background-color: var(--upload-progress-bg); + border-radius: var(--upload-border-radius); +} + +.upload progress::-webkit-progress-value { + background-color: var(--upload-progress-color); + border-radius: var(--upload-border-radius); +} \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/svelte/src/components/Upload/Upload.stories.ts b/pkgs/experimental/swarmakit/libs/svelte/src/components/Upload/Upload.stories.ts new file mode 100644 index 0000000000..1039002873 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/svelte/src/components/Upload/Upload.stories.ts @@ -0,0 +1,73 @@ +import Upload from './Upload.svelte'; +import type { Meta, StoryFn } from '@storybook/svelte'; + +const meta: Meta = { + title: 'component/Indicators/Upload', + component: Upload, + tags: ['autodocs'], + argTypes: { + status: { control: 'select', options: ['uploading', 'downloading', 'completed', 'paused', 'failed'] }, + fileName: { control: 'text' }, + progress: { control: 'number' } + }, + parameters: { + layout: 'centered', + viewport: { + viewports: { + smallMobile: { name: 'Small Mobile', styles: { width: '320px', height: '568px' } }, + largeMobile: { name: 'Large Mobile', styles: { width: '414px', height: '896px' } }, + tablet: { name: 'Tablet', styles: { width: '768px', height: '1024px' } }, + desktop: { name: 'Desktop', styles: { width: '1024px', height: '768px' } }, + } + } + } +}; + +export default meta; + +const Template:StoryFn = (args) => ({ + Component: Upload, + props:args, +}); + +export const Default = Template.bind({}); +Default.args = { + fileName: 'example.txt', + status: 'uploading', + progress: 50 +}; + +export const Uploading = Template.bind({}); +Uploading.args = { + fileName: 'example.txt', + status: 'uploading', + progress: 50 +}; + +export const Downloading = Template.bind({}); +Downloading.args = { + fileName: 'downloading_file.txt', + status: 'downloading', + progress: 60 +}; + +export const Completed = Template.bind({}); +Completed.args = { + fileName: 'completed_file.txt', + status: 'completed', + progress: 100 +}; + +export const Paused = Template.bind({}); +Paused.args = { + fileName: 'paused_file.txt', + status: 'paused', + progress: 70 +}; + +export const Failed = Template.bind({}); +Failed.args = { + fileName: 'paused_file.txt', + status: 'paused', + progress: 70 +}; \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/svelte/src/components/Upload/Upload.svelte b/pkgs/experimental/swarmakit/libs/svelte/src/components/Upload/Upload.svelte new file mode 100644 index 0000000000..78382cf27a --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/svelte/src/components/Upload/Upload.svelte @@ -0,0 +1,25 @@ + + +
    +

    {fileName}

    +

    {statuses[status]}

    + {#if status === 'uploading' || status === 'downloading'} + + {/if} +
    + + \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/svelte/src/components/ValidationMessages/ValidationMessages.css b/pkgs/experimental/swarmakit/libs/svelte/src/components/ValidationMessages/ValidationMessages.css new file mode 100644 index 0000000000..ad6ca35131 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/svelte/src/components/ValidationMessages/ValidationMessages.css @@ -0,0 +1,31 @@ +:root { + --success-bg-color: #d4edda; + --success-text-color: #155724; + --error-bg-color: #f8d7da; + --error-text-color: #721c24; + --warning-bg-color: #fff3cd; + --warning-text-color: #856404; + --padding: 1rem; + --border-radius: 4px; +} + +.validation-message { + padding: var(--padding); + border-radius: var(--border-radius); + margin: 0.5rem 0; +} + +.validation-message.success { + background-color: var(--success-bg-color); + color: var(--success-text-color); +} + +.validation-message.error { + background-color: var(--error-bg-color); + color: var(--error-text-color); +} + +.validation-message.warning { + background-color: var(--warning-bg-color); + color: var(--warning-text-color); +} \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/svelte/src/components/ValidationMessages/ValidationMessages.stories.ts b/pkgs/experimental/swarmakit/libs/svelte/src/components/ValidationMessages/ValidationMessages.stories.ts new file mode 100644 index 0000000000..ecd73d0b23 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/svelte/src/components/ValidationMessages/ValidationMessages.stories.ts @@ -0,0 +1,57 @@ +import ValidationMessages from './ValidationMessages.svelte'; +import type { Meta, StoryFn } from '@storybook/svelte'; + +const meta: Meta = { + title: 'component/Forms/ValidationMessages', + component: ValidationMessages, + tags: ['autodocs'], + argTypes: { + message: { control: 'text' }, + type: { + control: { type: 'select' }, + options: ['success', 'error', 'warning'] + }, + }, + parameters: { + layout: 'fullscreen', + viewport: { + viewports: { + smallMobile: { name: 'Small Mobile', styles: { width: '320px', height: '568px' } }, + largeMobile: { name: 'Large Mobile', styles: { width: '414px', height: '896px' } }, + tablet: { name: 'Tablet', styles: { width: '768px', height: '1024px' } }, + desktop: { name: 'Desktop', styles: { width: '1024px', height: '768px' } }, + } + } + } +}; + +export default meta; + +const Template:StoryFn = (args) => ({ + Component: ValidationMessages, + props:args, +}); + +export const Default = Template.bind({}); +Default.args = { + message: 'This is a validation message.', + type: 'success', +}; + +export const Success = Template.bind({}); +Success.args = { + message: 'Operation was successful!', + type: 'success', +}; + +export const Error = Template.bind({}); +Error.args = { + message: 'There was an error processing your request.', + type: 'error', +}; + +export const Warning = Template.bind({}); +Warning.args = { + message: 'This is a warning. Please check your input.', + type: 'warning', +}; \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/svelte/src/components/ValidationMessages/ValidationMessages.svelte b/pkgs/experimental/swarmakit/libs/svelte/src/components/ValidationMessages/ValidationMessages.svelte new file mode 100644 index 0000000000..3fcb0da7f2 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/svelte/src/components/ValidationMessages/ValidationMessages.svelte @@ -0,0 +1,14 @@ + + + + + \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/svelte/src/components/VirtualizedList/VirtualizedList.css b/pkgs/experimental/swarmakit/libs/svelte/src/components/VirtualizedList/VirtualizedList.css new file mode 100644 index 0000000000..e13c43a1e8 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/svelte/src/components/VirtualizedList/VirtualizedList.css @@ -0,0 +1,20 @@ +.virtualized-list { + list-style: none; + padding: 0; + margin: 0; + max-height: 400px; + overflow-y: auto; +} + +.list-item { + padding: 10px; + border-bottom: 1px solid #ddd; +} + +.list-item:last-child { + border-bottom: none; +} + +.list-end { + height: 1px; +} \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/svelte/src/components/VirtualizedList/VirtualizedList.stories.ts b/pkgs/experimental/swarmakit/libs/svelte/src/components/VirtualizedList/VirtualizedList.stories.ts new file mode 100644 index 0000000000..e6b81796ae --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/svelte/src/components/VirtualizedList/VirtualizedList.stories.ts @@ -0,0 +1,53 @@ +import VirtualizedList from './VirtualizedList.svelte'; +import type { Meta, StoryFn } from '@storybook/svelte'; + +const meta: Meta = { + title: 'Components/Lists/VirtualizedList', + component: VirtualizedList, + tags: ['autodocs'], + argTypes: { + items: { + control: { type: 'object' }, + }, + isLoading: { + control: { type: 'boolean' }, + }, + hasMore: { + control: { type: 'boolean' }, + }, + loadMore: { action: 'loadMore' }, + }, +}; + +export default meta; + +const Template: StoryFn = (args) => ({ + Component: VirtualizedList, + props: args, +}); + +const sampleItems = Array.from({ length: 20 }, (_, i) => `Item ${i + 1}`); + +export const Default = Template.bind({}); +Default.args = { + items: sampleItems, + isLoading: false, + hasMore: true, + loadMore: () => console.log('Loading more items...'), +}; + +export const LoadingMore = Template.bind({}); +LoadingMore.args = { + items: sampleItems, + isLoading: true, + hasMore: true, + loadMore: () => console.log('Loading more items...'), +}; + +export const EndOfList = Template.bind({}); +EndOfList.args = { + items: sampleItems, + isLoading: false, + hasMore: false, + loadMore: () => console.log('Loading more items...'), +}; \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/svelte/src/components/VirtualizedList/VirtualizedList.svelte b/pkgs/experimental/swarmakit/libs/svelte/src/components/VirtualizedList/VirtualizedList.svelte new file mode 100644 index 0000000000..8aa50a7d5c --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/svelte/src/components/VirtualizedList/VirtualizedList.svelte @@ -0,0 +1,47 @@ + + +
      + {#each items as item} +
    • {item}
    • + {/each} + + {#if isLoading} +
    • Loading...
    • + {/if} + {#if !hasMore && !isLoading} +
    • End of List
    • + {/if} +
    + + \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/svelte/src/components/VisualCueForAccessibilityFocusIndicator/VisualCueForAccessibilityFocusIndicator.css b/pkgs/experimental/swarmakit/libs/svelte/src/components/VisualCueForAccessibilityFocusIndicator/VisualCueForAccessibilityFocusIndicator.css new file mode 100644 index 0000000000..7eac18111f --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/svelte/src/components/VisualCueForAccessibilityFocusIndicator/VisualCueForAccessibilityFocusIndicator.css @@ -0,0 +1,25 @@ +:root { + --focus-indicator-padding: 1rem; + --focus-indicator-margin: 0.5rem; + --focus-indicator-border-radius: 4px; + --focus-indicator-font-size: 1rem; + --focus-indicator-bg: #f2f2f2; + --focus-indicator-color: #333; + --focus-indicator-outline-focused: 2px solid #007bff; + --focus-indicator-outline-unfocused: 2px solid transparent; +} + +.focus-indicator { + padding: var(--focus-indicator-padding); + margin: var(--focus-indicator-margin); + border-radius: var(--focus-indicator-border-radius); + font-size: var(--focus-indicator-font-size); + background-color: var(--focus-indicator-bg); + color: var(--focus-indicator-color); + outline: var(--focus-indicator-outline-unfocused); + transition: outline 0.3s ease; +} + +.focus-indicator.is-focused { + outline: var(--focus-indicator-outline-focused); +} \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/svelte/src/components/VisualCueForAccessibilityFocusIndicator/VisualCueForAccessibilityFocusIndicator.stories.ts b/pkgs/experimental/swarmakit/libs/svelte/src/components/VisualCueForAccessibilityFocusIndicator/VisualCueForAccessibilityFocusIndicator.stories.ts new file mode 100644 index 0000000000..0312905c12 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/svelte/src/components/VisualCueForAccessibilityFocusIndicator/VisualCueForAccessibilityFocusIndicator.stories.ts @@ -0,0 +1,65 @@ +import VisualCueForAccessibilityFocusIndicator from './VisualCueForAccessibilityFocusIndicator.svelte'; +import type { Meta, StoryFn, StoryObj } from '@storybook/svelte'; + +const meta: Meta = { + title: 'component/Indicators/VisualCueForAccessibilityFocusIndicator', + component: VisualCueForAccessibilityFocusIndicator, + tags: ['autodocs'], + argTypes: { + isFocused: { control: 'boolean' } + }, + parameters: { + layout: 'centered', + viewport: { + viewports: { + smallMobile: { name: 'Small Mobile', styles: { width: '320px', height: '568px' } }, + largeMobile: { name: 'Large Mobile', styles: { width: '414px', height: '896px' } }, + tablet: { name: 'Tablet', styles: { width: '768px', height: '1024px' } }, + desktop: { name: 'Desktop', styles: { width: '1024px', height: '768px' } }, + } + } + } +}; + +export default meta; + +const Template:StoryFn = (args) => ({ + Component: VisualCueForAccessibilityFocusIndicator, + props:args, +}); + + +export const Default = Template.bind({}); +Default.args = { + isFocused: false +} + +export const Focused = Template.bind({}); +Focused.args = { + isFocused: true +} + +export const Unfocused = Template.bind({}); +Unfocused.args = { + isFocused: false +} + +// type Story = StoryObj; + +// export const Default: Story = { +// args: { + // isFocused: false +// } +// }; + +// export const Focused: Story = { +// args: { +// isFocused: true +// } +// }; + +// export const Unfocused: Story = { +// args: { +// isFocused: false +// } +// }; \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/svelte/src/components/VisualCueForAccessibilityFocusIndicator/VisualCueForAccessibilityFocusIndicator.svelte b/pkgs/experimental/swarmakit/libs/svelte/src/components/VisualCueForAccessibilityFocusIndicator/VisualCueForAccessibilityFocusIndicator.svelte new file mode 100644 index 0000000000..e86af73db7 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/svelte/src/components/VisualCueForAccessibilityFocusIndicator/VisualCueForAccessibilityFocusIndicator.svelte @@ -0,0 +1,11 @@ + + +
    +

    Focus Indicator

    +
    + + \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/svelte/src/index.ts b/pkgs/experimental/swarmakit/libs/svelte/src/index.ts new file mode 100644 index 0000000000..0873566fb9 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/svelte/src/index.ts @@ -0,0 +1,73 @@ +export { default as CheckList } from './components/CheckList/CheckList.svelte'; +export { default as Accordion } from './components/Accordion/Accordion.svelte'; +export { default as ActionableList } from './components/ActionableList/ActionableList.svelte'; +export { default as ActivityIndicators } from './components/ActivityIndicators/ActivityIndicators.svelte'; +export { default as AudioPlayer } from './components/AudioPlayer/AudioPlayer.svelte'; +export { default as AudioPlayerAdvanced } from './components/AudioPlayerAdvanced/AudioPlayerAdvanced.svelte'; +export { default as AudioWaveformDisplay } from './components/AudioWaveformDisplay/AudioWaveformDisplay.svelte'; +export { default as Badge } from './components/Badge/Badge.svelte'; +export { default as BadgeWithCounts } from './components/BadgeWithCounts/BadgeWithCounts.svelte'; +export { default as BatteryLevelIndicator } from './components/BatteryLevelIndicator/BatteryLevelIndicator.svelte'; +export { default as Button } from './components/Button/Button.svelte'; +export { default as Captcha } from './components/Captcha/Captcha.svelte'; +export { default as CardbasedList } from './components/CardbasedList/CardbasedList.svelte'; +export { default as Carousel } from './components/Carousel/Carousel.svelte'; +export { default as Checkbox } from './components/Checkbox/Checkbox.svelte'; +export { default as CollapsibleMenuList } from './components/CollapsibleMenuList/CollapsibleMenuList.svelte'; +export { default as ColorPicker } from './components/ColorPicker/ColorPicker.svelte'; +export { default as ContextualList } from './components/ContextualList/ContextualList.svelte'; +export { default as CountdownTimer } from './components/CountdownTimer/CountdownTimer.svelte'; +export { default as DataGrid } from './components/DataGrid/DataGrid.svelte'; +export { default as DateAndTimePicker } from './components/DateAndTimePicker/DateAndTimePicker.svelte'; +export { default as DatePicker } from './components/DatePicker/DatePicker.svelte'; +export { default as ThreeSixtyDegreeImageViewer } from './components/360-DegreeImageViewer/ThreeSixtyDegreeImageViewer.svelte'; +export { default as DragAndDropFileArea } from './components/DragAndDropFileArea/DragAndDropFileArea.svelte'; +export { default as EmbeddedMediaIframe } from './components/EmbeddedMediaIframe/EmbeddedMediaIframe.svelte'; +export { default as ExpandableList } from './components/ExpandableList/ExpandableList.svelte'; +export { default as FavoritesList } from './components/FavoritesList/FavoritesList.svelte'; +export { default as FileInputWithPreview } from './components/FileInputWithPreview/FileInputWithPreview.svelte'; +export { default as FileUpload } from './components/FileUpload/FileUpload.svelte'; +export { default as FilterableList } from './components/FilterableList/FilterableList.svelte'; +export { default as GroupedList } from './components/GroupedList/GroupedList.svelte'; +export { default as IconButton } from './components/IconButton/IconButton.svelte'; +export { default as ImageSlider } from './components/ImageSlider/ImageSlider.svelte'; +export { default as InteractivePollResults } from './components/InteractivePollResults/InteractivePollResults.svelte'; +export { default as LoadingBarsWithSteps } from './components/LoadingBarsWithSteps/LoadingBarsWithSteps.svelte'; +export { default as LoadingSpinner } from './components/LoadingSpinner/LoadingSpinner.svelte'; +export { default as LoadmorebuttoninList } from './components/LoadmorebuttoninList/LoadmorebuttoninList.svelte'; + +export { default as MultiselectList } from './components/MultiselectList/MultiselectList.svelte'; +export { default as NotificationBellIcon } from './components/NotificationBellIcon/NotificationBellIcon.svelte'; +export { default as NumberedList } from './components/NumberedList/NumberedList.svelte'; +export { default as NumberInputWithIncrement } from './components/NumberInputWithIncrement/NumberInputWithIncrement.svelte'; +export { default as Pagination } from './components/Pagination/Pagination.svelte'; +export { default as PasswordConfirmationField } from './components/PasswordConfirmationField/PasswordConfirmationField.svelte'; +export { default as PinnedList } from './components/PinnedList/PinnedList.svelte'; +export { default as ProgressBar } from './components/ProgressBar/ProgressBar.svelte'; +export { default as ProgressCircle } from './components/ProgressCircle/ProgressCircle.svelte'; +export { default as RadioButton } from './components/RadioButton/RadioButton.svelte'; +export { default as RangeSlider } from './components/RangeSlider/RangeSlider.svelte'; +export { default as RatingStars } from './components/RatingStars/RatingStars.svelte'; +//export { default as RichTextEditor } from './components/RichTextEditor/RichTextEditor.svelte'; +export { default as ScrollableList } from './components/ScrollableList/ScrollableList.svelte'; +export { default as SearchBar } from './components/SearchBar/SearchBar.svelte'; +export { default as SearchInputWithFilterOptions } from './components/SearchInputWithFilterOptions/SearchInputWithFilterOptions.svelte'; +export { default as SelectableListWithItemDetails } from './components/SelectableListWithItemDetails/SelectableListWithItemDetails.svelte'; +export { default as SignalStrengthIndicator } from './components/SignalStrengthIndicator/SignalStrengthIndicator.svelte'; +export { default as Slider } from './components/Slider/Slider.svelte'; +export { default as SortableList } from './components/SortableList/SortableList.svelte'; +export { default as SortableTable } from './components/SortableTable/SortableTable.svelte'; +export { default as StatusDots } from './components/StatusDots/StatusDots.svelte'; +export { default as Stepper } from './components/Stepper/Stepper.svelte'; +export { default as SystemAlertGlobalNotificationBar } from './components/SystemAlertGlobalNotificationBar/SystemAlertGlobalNotificationBar.svelte'; +export { default as Tabs } from './components/Tabs/Tabs.svelte'; +export { default as TaskCompletionCheckList } from './components/TaskCompletionCheckList/TaskCompletionCheckList.svelte'; +export { default as Textarea } from './components/Textarea/Textarea.svelte'; +export { default as TimelineList } from './components/TimelineList/TimelineList.svelte'; +export { default as Toast } from './components/Toast/Toast.svelte'; +export { default as ToggleSwitch } from './components/ToggleSwitch/ToggleSwitch.svelte'; +export { default as TreeviewList } from './components/TreeviewList/TreeviewList.svelte'; +export { default as Upload } from './components/Upload/Upload.svelte'; +export { default as ValidationMessages } from './components/ValidationMessages/ValidationMessages.svelte'; +export { default as VirtualizedList } from './components/VirtualizedList/VirtualizedList.svelte'; +export { default as VisualCueForAccessibilityFocusIndicator } from './components/VisualCueForAccessibilityFocusIndicator/VisualCueForAccessibilityFocusIndicator.svelte'; \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/svelte/src/lib/Counter.svelte b/pkgs/experimental/swarmakit/libs/svelte/src/lib/Counter.svelte new file mode 100644 index 0000000000..37d75ce76f --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/svelte/src/lib/Counter.svelte @@ -0,0 +1,10 @@ + + + diff --git a/pkgs/experimental/swarmakit/libs/svelte/src/vite-env.d.ts b/pkgs/experimental/swarmakit/libs/svelte/src/vite-env.d.ts new file mode 100644 index 0000000000..4078e7476a --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/svelte/src/vite-env.d.ts @@ -0,0 +1,2 @@ +/// +/// diff --git a/pkgs/experimental/swarmakit/libs/svelte/svelte.config.js b/pkgs/experimental/swarmakit/libs/svelte/svelte.config.js new file mode 100644 index 0000000000..b0683fd24d --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/svelte/svelte.config.js @@ -0,0 +1,7 @@ +import { vitePreprocess } from '@sveltejs/vite-plugin-svelte' + +export default { + // Consult https://svelte.dev/docs#compile-time-svelte-preprocess + // for more information about preprocessors + preprocess: vitePreprocess(), +} diff --git a/pkgs/experimental/swarmakit/libs/svelte/tsconfig.json b/pkgs/experimental/swarmakit/libs/svelte/tsconfig.json new file mode 100644 index 0000000000..df56300cc6 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/svelte/tsconfig.json @@ -0,0 +1,21 @@ +{ + "extends": "@tsconfig/svelte/tsconfig.json", + "compilerOptions": { + "target": "ESNext", + "useDefineForClassFields": true, + "module": "ESNext", + "resolveJsonModule": true, + /** + * Typecheck JS in `.svelte` and `.js` files by default. + * Disable checkJs if you'd like to use dynamic types in JS. + * Note that setting allowJs false does not prevent the use + * of JS in `.svelte` files. + */ + "allowJs": true, + "checkJs": true, + "isolatedModules": true, + "moduleDetection": "force" + }, + "include": ["src/**/*.ts", "src/**/*.js", "src/**/*.svelte"], + "references": [{ "path": "./tsconfig.node.json" }] +} diff --git a/pkgs/experimental/swarmakit/libs/svelte/tsconfig.node.json b/pkgs/experimental/swarmakit/libs/svelte/tsconfig.node.json new file mode 100644 index 0000000000..852f890f9a --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/svelte/tsconfig.node.json @@ -0,0 +1,12 @@ +{ + "compilerOptions": { + "composite": true, + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo", + "skipLibCheck": true, + "module": "ESNext", + "moduleResolution": "Bundler", + "strict": true, + "noUncheckedSideEffectImports": true + }, + "include": ["vite.config.ts"] +} diff --git a/pkgs/experimental/swarmakit/libs/svelte/vite.config.ts b/pkgs/experimental/swarmakit/libs/svelte/vite.config.ts new file mode 100644 index 0000000000..cfd0aea804 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/svelte/vite.config.ts @@ -0,0 +1,29 @@ +import { defineConfig } from 'vite'; +import { svelte } from '@sveltejs/vite-plugin-svelte'; +import { resolve } from 'path'; + +export default defineConfig({ + plugins: [svelte()], + resolve: { + alias: { + '@components': '/src/components', // Example alias + }, + }, + build: { + lib: { + entry: resolve(__dirname, 'src/index.ts'), + name: 'SwarmaKitSvelte', + formats: ['es', 'cjs'], + fileName: (format) => `index.${format === 'es' ? 'esm' : 'cjs'}.js` + }, + rollupOptions: { + // Keep svelte external to avoid dual runtime instances + external: ['svelte', 'svelte/internal'], + output: { + globals: { + svelte: 'svelte' + } + } + } + } +}); diff --git a/pkgs/experimental/swarmakit/libs/sveltekit/.gitignore b/pkgs/experimental/swarmakit/libs/sveltekit/.gitignore new file mode 100644 index 0000000000..152a0a4366 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/sveltekit/.gitignore @@ -0,0 +1,23 @@ +node_modules + +# Output +.output +.vercel +/.svelte-kit +/build + +# OS +.DS_Store +Thumbs.db + +# Env +.env +.env.* +!.env.example +!.env.test + +# Vite +vite.config.js.timestamp-* +vite.config.ts.timestamp-* + +*storybook.log \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/sveltekit/.npmrc b/pkgs/experimental/swarmakit/libs/sveltekit/.npmrc new file mode 100644 index 0000000000..b6f27f1359 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/sveltekit/.npmrc @@ -0,0 +1 @@ +engine-strict=true diff --git a/pkgs/experimental/swarmakit/libs/sveltekit/.storybook/main.ts b/pkgs/experimental/swarmakit/libs/sveltekit/.storybook/main.ts new file mode 100644 index 0000000000..f46e04adbb --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/sveltekit/.storybook/main.ts @@ -0,0 +1,17 @@ +import type { StorybookConfig } from "@storybook/sveltekit"; + +const config: StorybookConfig = { + stories: ["../src/**/*.mdx", "../src/**/*.stories.@(js|ts|svelte)"], + addons: [ + "@storybook/addon-svelte-csf", + "@storybook/addon-links", + "@storybook/addon-essentials", + "@chromatic-com/storybook", + "@storybook/addon-interactions", + ], + framework: { + name: "@storybook/sveltekit", + options: {}, + }, +}; +export default config; diff --git a/pkgs/experimental/swarmakit/libs/sveltekit/.storybook/preview.ts b/pkgs/experimental/swarmakit/libs/sveltekit/.storybook/preview.ts new file mode 100644 index 0000000000..b09bb06ffd --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/sveltekit/.storybook/preview.ts @@ -0,0 +1,14 @@ +import type { Preview } from "@storybook/svelte"; + +const preview: Preview = { + parameters: { + controls: { + matchers: { + color: /(background|color)$/i, + date: /Date$/i, + }, + }, + }, +}; + +export default preview; diff --git a/pkgs/experimental/swarmakit/libs/sveltekit/README.md b/pkgs/experimental/swarmakit/libs/sveltekit/README.md new file mode 100644 index 0000000000..bdb99f9f0a --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/sveltekit/README.md @@ -0,0 +1,97 @@ +# create-svelte + +
    + +[![Hits](https://hits.seeyoufarm.com/api/count/incr/badge.svg?url=https%3A%2F%2Fgithub.com%2Fswarmauri%2Fswarmakit%2Ftree%2Fmaster%2Flibs%2Fsveltekit&count_bg=%2379C83D&title_bg=%23555555&icon=&icon_color=%23E7E7E7&title=hits&edge_flat=false)](https://hits.seeyoufarm.com) +![NPM Version](https://img.shields.io/npm/v/%40swarmakit%2Fsveltekit) +![npm downloads](https://img.shields.io/npm/dt/@swarmakit/sveltekit.svg) +[![License](https://img.shields.io/badge/License-Apache_2.0-blue.svg)](https://opensource.org/licenses/Apache-2.0) +[![Build and Publish Monorepo](https://github.com/swarmauri/swarmakit/actions/workflows/publish.yml/badge.svg)](https://github.com/swarmauri/swarmakit/actions/workflows/publish.yml) +
    + +
    + +![Static Badge](https://img.shields.io/badge/Svelte-FF3E00?style=for-the-badge&logo=svelte&labelColor=black) +![Static Badge](https://img.shields.io/badge/TypeScript-1D4ED8?style=for-the-badge&logo=typescript&labelColor=black) +
    + +Everything you need to build a Svelte project, powered by [`create-svelte`](https://github.com/sveltejs/kit/tree/main/packages/create-svelte). + +## Creating a project + +If you're seeing this, you've probably already done this step. Congrats! + +```bash +# create a new project in the current directory +npm create svelte@latest + +# create a new project in my-app +npm create svelte@latest my-app +``` + +## Developing + +Once you've created a project and installed dependencies with `npm install` (or `pnpm install` or `yarn`), start a development server: + +```bash +npm run dev + +# or start the server and open the app in a new browser tab +npm run dev -- --open +``` + +## Building + +To create a production version of your app: + +```bash +npm run build +``` + +You can preview the production build with `npm run preview`. + +> To deploy your app, you may need to install an [adapter](https://kit.svelte.dev/docs/adapters) for your target environment. + +## Installation + +Install the `@swarmakit/sveltekit` library through npm: + +```bash +npm install @swarmakit/sveltekit +``` + +### Prerequisites + +Node.js and npm should be installed. You can verify installation with: + +```bash +node -v +npm -v +``` + +### Importing Components and Basic Usage in Svelte + +1. **Import Components:** To use a component in your Svelte files, import it from the `@swarmakit/sveltekit` library as shown below: + + ```html + + ``` + +2. **Example Usage in a Svelte File:** Use the imported component within your Svelte file: + + ```html + + +
    + +
    + ``` + +> **Available Components:** Swarmakit Sveltekit includes a vast library of components. See the full list in the [components folder on GitHub](https://github.com/swarmauri/swarmakit/tree/master/libs/sveltekit/src/components). + diff --git a/pkgs/experimental/swarmakit/libs/sveltekit/package.json b/pkgs/experimental/swarmakit/libs/sveltekit/package.json new file mode 100644 index 0000000000..0adead7d8e --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/sveltekit/package.json @@ -0,0 +1,37 @@ +{ + "name": "@swarmakit/sveltekit", + "version": "0.0.8", + "private": false, + "type": "module", + "scripts": { + "dev": "vite dev", + "build": "vite build", + "preview": "vite preview", + "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json", + "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch", + "storybook": "storybook dev -p 6006", + "build-storybook": "storybook build" + }, + "devDependencies": { + "@chromatic-com/storybook": "^1.9.0", + "@fontsource/fira-mono": "^5.0.0", + "@neoconfetti/svelte": "^2.0.0", + "@storybook/addon-essentials": "^8.3.5", + "@storybook/addon-interactions": "^8.3.5", + "@storybook/addon-links": "^8.3.5", + "@storybook/addon-svelte-csf": "^4.1.7", + "@storybook/blocks": "^8.3.5", + "@storybook/svelte": "^8.3.5", + "@storybook/sveltekit": "^8.3.5", + "@storybook/test": "^8.3.5", + "@types/react": "^18.3.0", + "@sveltejs/adapter-auto": "^3.0.0", + "@sveltejs/kit": "^2.0.0", + "@sveltejs/vite-plugin-svelte": "^3.0.0", + "storybook": "^8.3.5", + "svelte": "^4.2.7", + "svelte-check": "^4.0.0", + "typescript": "^5.0.0", + "vite": "^5.0.3" + } +} diff --git a/pkgs/experimental/swarmakit/libs/sveltekit/src/app.html b/pkgs/experimental/swarmakit/libs/sveltekit/src/app.html new file mode 100644 index 0000000000..e5e6d7e31b --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/sveltekit/src/app.html @@ -0,0 +1,17 @@ + + + + + + SvelteKit App + + %sveltekit.head% + + + +
    + + %sveltekit.body% +
    + + diff --git a/pkgs/experimental/swarmakit/libs/sveltekit/src/components/360-DegreeImageViewer/360-DegreeImageViewer.css b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/360-DegreeImageViewer/360-DegreeImageViewer.css new file mode 100644 index 0000000000..ea5444fd0e --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/360-DegreeImageViewer/360-DegreeImageViewer.css @@ -0,0 +1,21 @@ +.viewer-container { + width: 100%; + height: 400px; + display: flex; + align-items: center; + justify-content: center; + background-color: #f3f3f3; + overflow: hidden; + position: relative; +} + +.viewer-container img { + max-width: 100%; + max-height: 100%; + transition: transform 0.2s ease-in-out; +} + +.loading { + font-size: 1.5em; + color: #888; +} \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/sveltekit/src/components/360-DegreeImageViewer/360-DegreeImageViewer.stories.ts b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/360-DegreeImageViewer/360-DegreeImageViewer.stories.ts new file mode 100644 index 0000000000..b61addad54 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/360-DegreeImageViewer/360-DegreeImageViewer.stories.ts @@ -0,0 +1,71 @@ +import DegreeImageViewer from './360-DegreeImageViewer.svelte'; +import type { Meta, StoryFn } from '@storybook/svelte'; + +const meta: Meta = { + title: 'Components/Media/360-DegreeImageViewer', + component: DegreeImageViewer, + tags: ['autodocs'], + argTypes: { + imageUrls: { + control: { type: 'object' }, + }, + isLoading: { + control: { type: 'boolean' }, + }, + isRotating: { + control: { type: 'boolean' }, + }, + isZoomed: { + control: { type: 'boolean' }, + }, + }, +}; + +export default meta; + +const Template: StoryFn = (args) => ({ + Component: DegreeImageViewer, + props: args, +}); + +const sampleImages = Array.from({ length: 36 }, (_, i) => `path/to/image_${i + 1}.jpg`); + +export const Default = Template.bind({}); +Default.args = { + imageUrls: sampleImages, + isLoading: false, + isRotating: false, + isZoomed: false, +}; + +export const Rotating = Template.bind({}); +Rotating.args = { + imageUrls: sampleImages, + isLoading: false, + isRotating: true, + isZoomed: false, +}; + +export const Paused = Template.bind({}); +Paused.args = { + imageUrls: sampleImages, + isLoading: false, + isRotating: false, + isZoomed: false, +}; + +export const ZoomInOut = Template.bind({}); +ZoomInOut.args = { + imageUrls: sampleImages, + isLoading: false, + isRotating: false, + isZoomed: true, +}; + +export const Loading = Template.bind({}); +Loading.args = { + imageUrls: sampleImages, + isLoading: true, + isRotating: false, + isZoomed: false, +}; \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/sveltekit/src/components/360-DegreeImageViewer/360-DegreeImageViewer.svelte b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/360-DegreeImageViewer/360-DegreeImageViewer.svelte new file mode 100644 index 0000000000..8f26091822 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/360-DegreeImageViewer/360-DegreeImageViewer.svelte @@ -0,0 +1,46 @@ + + +
    e.key === 'Enter' && toggleZoom()}> + {#if isLoading} +
    Loading...
    + {:else} + 360-degree view + {/if} +
    + + \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/sveltekit/src/components/Accordion/Accordion.css b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/Accordion/Accordion.css new file mode 100644 index 0000000000..98332d12fc --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/Accordion/Accordion.css @@ -0,0 +1,36 @@ +:root { + --accordion-header-bg: #f5f5f5; + --accordion-header-color: #333; + --accordion-header-hover-bg: #e0e0e0; + --accordion-content-bg: #fff; + --accordion-border-radius: 4px; + --accordion-transition-duration: 0.3s; +} + +.accordion { + border: 1px solid #ccc; + border-radius: var(--accordion-border-radius); + overflow: hidden; +} + +.accordion-header { + width: 100%; + background: var(--accordion-header-bg); + color: var(--accordion-header-color); + padding: 10px; + cursor: pointer; + border: none; + text-align: left; + transition: background var(--accordion-transition-duration); +} + +.accordion-header:hover { + background: var(--accordion-header-hover-bg); +} + +.accordion-content { + background: var(--accordion-content-bg); + padding: 10px; + display: block; + transition: max-height var(--accordion-transition-duration); +} \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/sveltekit/src/components/Accordion/Accordion.stories.ts b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/Accordion/Accordion.stories.ts new file mode 100644 index 0000000000..bde7909fa1 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/Accordion/Accordion.stories.ts @@ -0,0 +1,62 @@ +import Accordion from './Accordion.svelte'; +import type { Meta,StoryFn } from '@storybook/svelte'; + +const meta: Meta = { + title: 'component/Lists/Accordion', + component: Accordion, + tags: ['autodocs'], + argTypes: { + isOpen: { control: 'boolean' }, + title: { control: 'text' }, + content: { control: 'text' } + }, + parameters: { + layout: 'centered', + viewport: { + viewports: { + smallMobile: { name: 'Small Mobile', styles: { width: '320px', height: '568px' } }, + largeMobile: { name: 'Large Mobile', styles: { width: '414px', height: '896px' } }, + tablet: { name: 'Tablet', styles: { width: '768px', height: '1024px' } }, + desktop: { name: 'Desktop', styles: { width: '1024px', height: '768px' } }, + } + } + } +}; + +export default meta; + +const Template:StoryFn = (args) => ({ + Component: Accordion, + props:args, +}) + +export const Default = Template.bind({}); +Default.args = { + isOpen:false, + title:'Accordion Title', + content:'Accordion content goes here.' +} + +export const Open = Template.bind({}); +Open.args = { + isOpen:true, + title:'Accordion Title', + content:'Accordion content goes here.' +} + +export const Closed = Template.bind({}); +Closed.args = { + isOpen:false, + title:'Accordion Title', + content:'Accordion content goes here.' +} + +export const Hover = Template.bind({}); +Hover.args = { + isOpen:false, + title: 'Accordion Title', + content: 'Accordion content goes here.' +}; +Hover.parameters = { + pseudo:{hover:true} +} \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/sveltekit/src/components/Accordion/Accordion.svelte b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/Accordion/Accordion.svelte new file mode 100644 index 0000000000..111c023103 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/Accordion/Accordion.svelte @@ -0,0 +1,29 @@ + + +
    + + {#if isOpen} +
    + {content} +
    + {/if} +
    + + \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/sveltekit/src/components/ActionableList/ActionableList.css b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/ActionableList/ActionableList.css new file mode 100644 index 0000000000..7fb38d49a9 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/ActionableList/ActionableList.css @@ -0,0 +1,45 @@ +:root { + --list-bg: #f9f9f9; + --list-item-hover-bg: #e0e0e0; + --button-bg: #ffffff; + --button-disabled-bg: #cccccc; + --loading-text: #666666; +} + +.actionable-list { + background: var(--list-bg); + border-radius: 4px; + padding: 10px; +} + +ul { + list-style-type: none; + padding: 0; +} + +li { + margin-bottom: 5px; +} + +button { + background: var(--button-bg); + border: 1px solid #ccc; + padding: 10px; + width: 100%; + cursor: pointer; + border-radius: 4px; +} + +button:hover { + background: var(--list-item-hover-bg); +} + +button:disabled { + background: var(--button-disabled-bg); + cursor: not-allowed; +} + +.loading { + text-align: center; + color: var(--loading-text); +} \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/sveltekit/src/components/ActionableList/ActionableList.stories.ts b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/ActionableList/ActionableList.stories.ts new file mode 100644 index 0000000000..a4ae351beb --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/ActionableList/ActionableList.stories.ts @@ -0,0 +1,83 @@ +import ActionableList from './ActionableList.svelte'; +import type { Meta,StoryFn } from '@storybook/svelte'; + +const meta: Meta = { + title: 'component/Lists/ActionableList', + component: ActionableList, + tags: ['autodocs'], + argTypes: { + items: { control: 'object' }, + loading: { control: 'boolean' } + }, + parameters: { + layout: 'centered', + viewport: { + viewports: { + smallMobile: { name: 'Small Mobile', styles: { width: '320px', height: '568px' } }, + largeMobile: { name: 'Large Mobile', styles: { width: '414px', height: '896px' } }, + tablet: { name: 'Tablet', styles: { width: '768px', height: '1024px' } }, + desktop: { name: 'Desktop', styles: { width: '1024px', height: '768px' } }, + } + } + } +}; + +export default meta; + +const Template:StoryFn = (args) => ({ + Component:ActionableList, + props:args, +}); + +export const Default = Template.bind({}); +Default.args = { + items:[ + {id:1, text:'Item 1', action:()=> alert('Action 1 triggered')}, + {id:2, text:'Item 2', action:()=> alert('Action 2 triggered')}, + {id:3, text:'Item 3', action:()=> alert('Action 3 triggered')}, + ], + loading:false, +}; + +export const Hover = Template.bind({}); +Hover.args = { + items:[ + {id:1, text:'Item 1', action:()=> alert('Action 1 triggered')}, + {id:2, text:'Item 2', action:()=> alert('Action 2 triggered')}, + {id:3, text:'Item 3', action:()=> alert('Action 3 triggered')}, + ], + loading:false, +}; +Hover.parameters = { + pseudo: {hover:true}, +} + +export const ActionTriggered = Template.bind({}); +ActionTriggered.args = { + items:[ + {id:1, text:'Item 1', action:()=> alert('Action 1 triggered')}, + {id:2, text:'Item 2', action:()=> alert('Action 2 triggered')}, + {id:3, text:'Item 3', action:()=> alert('Action 3 triggered')}, + ], + loading:false, +} + +export const Disabled = Template.bind({}); +Disabled.args = { + items:[ + {id:1, text:'Item 1', action:()=> alert('Action 1 triggered'),disabled:true}, + {id:2, text:'Item 2', action:()=> alert('Action 2 triggered'),disabled:true}, + {id:3, text:'Item 3', action:()=> alert('Action 3 triggered'),disabled:true}, + ], + loading:false, +}; + +export const Loading = Template.bind({}); +Loading.args = { + items:[ + {id:1, text:'Item 1', action:()=> alert('Action 1 triggered')}, + {id:2, text:'Item 2', action:()=> alert('Action 2 triggered')}, + {id:3, text:'Item 3', action:()=> alert('Action 3 triggered')}, + ], + loading:false, +} \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/sveltekit/src/components/ActionableList/ActionableList.svelte b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/ActionableList/ActionableList.svelte new file mode 100644 index 0000000000..2f73c6f26a --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/ActionableList/ActionableList.svelte @@ -0,0 +1,34 @@ + + +
    + {#if loading} +
    Loading...
    + {:else} +
      + {#each items as item (item.id)} +
    • + +
    • + {/each} +
    + {/if} +
    + + \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/sveltekit/src/components/ActivityIndicators/ActivityIndicators.css b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/ActivityIndicators/ActivityIndicators.css new file mode 100644 index 0000000000..0339875516 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/ActivityIndicators/ActivityIndicators.css @@ -0,0 +1,32 @@ +:root { + --loading-bg-color: #e0e0e0; + --success-bg-color: #d4edda; + --error-bg-color: #f8d7da; + --loading-text-color: #6c757d; + --success-text-color: #155724; + --error-text-color: #721c24; + --padding: 1rem; + --border-radius: 4px; +} + +.activity-indicator { + padding: var(--padding); + border-radius: var(--border-radius); + margin: 0.5rem 0; + display: inline-block; +} + +.activity-indicator.loading { + background-color: var(--loading-bg-color); + color: var(--loading-text-color); +} + +.activity-indicator.success { + background-color: var(--success-bg-color); + color: var(--success-text-color); +} + +.activity-indicator.error { + background-color: var(--error-bg-color); + color: var(--error-text-color); +} \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/sveltekit/src/components/ActivityIndicators/ActivityIndicators.stories.ts b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/ActivityIndicators/ActivityIndicators.stories.ts new file mode 100644 index 0000000000..2e35ff8e09 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/ActivityIndicators/ActivityIndicators.stories.ts @@ -0,0 +1,52 @@ +import ActivityIndicators from './ActivityIndicators.svelte'; +import type { Meta,StoryFn } from '@storybook/svelte'; + +const meta: Meta = { + title: 'component/Indicators/ActivityIndicators', + component: ActivityIndicators, + tags: ['autodocs'], + argTypes: { + state: { + control: { type: 'select' }, + options: ['loading', 'success', 'error'] + }, + }, + parameters: { + layout: 'fullscreen', + viewport: { + viewports: { + smallMobile: { name: 'Small Mobile', styles: { width: '320px', height: '568px' } }, + largeMobile: { name: 'Large Mobile', styles: { width: '414px', height: '896px' } }, + tablet: { name: 'Tablet', styles: { width: '768px', height: '1024px' } }, + desktop: { name: 'Desktop', styles: { width: '1024px', height: '768px' } }, + } + } + } +}; + +export default meta; + +const Template:StoryFn = (args) => ({ + Component: ActivityIndicators, + props:args, +}); + +export const Default = Template.bind({}); +Default.args = { + state: 'loading', +}; + +export const Loading = Template.bind({}); +Loading.args = { + state:'loading', +}; + +export const Success = Template.bind({}); +Success.args = { + state:'success', +}; + +export const Error = Template.bind({}); +Error.args = { + state:'error', +}; \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/sveltekit/src/components/ActivityIndicators/ActivityIndicators.svelte b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/ActivityIndicators/ActivityIndicators.svelte new file mode 100644 index 0000000000..048f572f75 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/ActivityIndicators/ActivityIndicators.svelte @@ -0,0 +1,19 @@ + + +
    + {#if state === 'loading'} + Loading... + {:else if state === 'success'} + Success! + {:else if state === 'error'} + Error occurred! + {/if} +
    + + \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/sveltekit/src/components/AudioPlayer/AudioPlayer.css b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/AudioPlayer/AudioPlayer.css new file mode 100644 index 0000000000..8d9ee0c740 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/AudioPlayer/AudioPlayer.css @@ -0,0 +1,14 @@ +.audio-player { + display: flex; + align-items: center; + gap: 10px; +} + +button { + padding: 5px 10px; + cursor: pointer; +} + +input[type="range"] { + width: 100px; +} \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/sveltekit/src/components/AudioPlayer/AudioPlayer.stories.ts b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/AudioPlayer/AudioPlayer.stories.ts new file mode 100644 index 0000000000..0dc75e5720 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/AudioPlayer/AudioPlayer.stories.ts @@ -0,0 +1,69 @@ +import AudioPlayer from './AudioPlayer.svelte'; +import type { Meta, StoryFn } from '@storybook/svelte'; + +const meta: Meta = { + title: 'Components/Media/AudioPlayer', + component: AudioPlayer, + tags: ['autodocs'], + argTypes: { + src: { + control: { type: 'text' }, + }, + isPlaying: { + control: { type: 'boolean' }, + }, + isMuted: { + control: { type: 'boolean' }, + }, + volume: { + control: { type: 'number' }, + }, + }, +}; + +export default meta; + +const Template: StoryFn = (args) => ({ + Component: AudioPlayer, + props: args, +}); + +export const Default = Template.bind({}); +Default.args = { + src: 'path/to/audio.mp3', + isPlaying: false, + isMuted: false, + volume: 1, +}; + +export const Play = Template.bind({}); +Play.args = { + src: 'path/to/audio.mp3', + isPlaying: true, + isMuted: false, + volume: 1, +}; + +export const Pause = Template.bind({}); +Pause.args = { + src: 'path/to/audio.mp3', + isPlaying: false, + isMuted: false, + volume: 1, +}; + +export const Mute = Template.bind({}); +Mute.args = { + src: 'path/to/audio.mp3', + isPlaying: false, + isMuted: true, + volume: 1, +}; + +export const VolumeControl = Template.bind({}); +VolumeControl.args = { + src: 'path/to/audio.mp3', + isPlaying: false, + isMuted: false, + volume: 0.5, +}; \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/sveltekit/src/components/AudioPlayer/AudioPlayer.svelte b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/AudioPlayer/AudioPlayer.svelte new file mode 100644 index 0000000000..b7f6b0e778 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/AudioPlayer/AudioPlayer.svelte @@ -0,0 +1,48 @@ + + +
    + + + + +
    + + \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/sveltekit/src/components/AudioPlayerAdvanced/AudioPlayerAdvanced.css b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/AudioPlayerAdvanced/AudioPlayerAdvanced.css new file mode 100644 index 0000000000..1c5f547297 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/AudioPlayerAdvanced/AudioPlayerAdvanced.css @@ -0,0 +1,14 @@ +.audio-player-advanced { + display: flex; + align-items: center; + gap: 10px; +} + +button { + padding: 5px 10px; + cursor: pointer; +} + +input[type="range"] { + width: 100px; +} \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/sveltekit/src/components/AudioPlayerAdvanced/AudioPlayerAdvanced.stories.ts b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/AudioPlayerAdvanced/AudioPlayerAdvanced.stories.ts new file mode 100644 index 0000000000..9da6bbc915 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/AudioPlayerAdvanced/AudioPlayerAdvanced.stories.ts @@ -0,0 +1,95 @@ +import AudioPlayerAdvanced from './AudioPlayerAdvanced.svelte'; +import type { Meta, StoryFn } from '@storybook/svelte'; + +const meta: Meta = { + title: 'Components/Media/AudioPlayerAdvanced', + component: AudioPlayerAdvanced, + tags: ['autodocs'], + argTypes: { + src: { + control: { type: 'text' }, + }, + isPlaying: { + control: { type: 'boolean' }, + }, + isMuted: { + control: { type: 'boolean' }, + }, + volume: { + control: { type: 'number' }, + }, + playbackRate: { + control: { type: 'number' }, + }, + }, +}; + +export default meta; + +const Template: StoryFn = (args) => ({ + Component: AudioPlayerAdvanced, + props: args, +}); + +export const Default = Template.bind({}); +Default.args = { + src: 'path/to/audio.mp3', + isPlaying: false, + isMuted: false, + volume: 1, + playbackRate: 1, +}; + +export const Play = Template.bind({}); +Play.args = { + src: 'path/to/audio.mp3', + isPlaying: true, + isMuted: false, + volume: 1, + playbackRate: 1, +}; + +export const Pause = Template.bind({}); +Pause.args = { + src: 'path/to/audio.mp3', + isPlaying: false, + isMuted: false, + volume: 1, + playbackRate: 1, +}; + +export const Seek = Template.bind({}); +Seek.args = { + src: 'path/to/audio.mp3', + isPlaying: false, + isMuted: false, + volume: 1, + playbackRate: 1, +}; + +export const Mute = Template.bind({}); +Mute.args = { + src: 'path/to/audio.mp3', + isPlaying: false, + isMuted: true, + volume: 1, + playbackRate: 1, +}; + +export const VolumeControl = Template.bind({}); +VolumeControl.args = { + src: 'path/to/audio.mp3', + isPlaying: false, + isMuted: false, + volume: 0.5, + playbackRate: 1, +}; + +export const SpeedControl = Template.bind({}); +SpeedControl.args = { + src: 'path/to/audio.mp3', + isPlaying: false, + isMuted: false, + volume: 1, + playbackRate: 1.5, +}; \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/sveltekit/src/components/AudioPlayerAdvanced/AudioPlayerAdvanced.svelte b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/AudioPlayerAdvanced/AudioPlayerAdvanced.svelte new file mode 100644 index 0000000000..99c880f67e --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/AudioPlayerAdvanced/AudioPlayerAdvanced.svelte @@ -0,0 +1,67 @@ + + +
    + + + + + + +
    + + \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/sveltekit/src/components/AudioWaveformDisplay/AudioWaveformDisplay.css b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/AudioWaveformDisplay/AudioWaveformDisplay.css new file mode 100644 index 0000000000..23b6a6cd6c --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/AudioWaveformDisplay/AudioWaveformDisplay.css @@ -0,0 +1,15 @@ +.audio-waveform-display { + display: flex; + align-items: center; + gap: 10px; +} + +canvas { + background-color: #f0f0f0; + border: 1px solid #ccc; +} + +button { + padding: 5px 10px; + cursor: pointer; +} \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/sveltekit/src/components/AudioWaveformDisplay/AudioWaveformDisplay.stories.ts b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/AudioWaveformDisplay/AudioWaveformDisplay.stories.ts new file mode 100644 index 0000000000..e0f097d69d --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/AudioWaveformDisplay/AudioWaveformDisplay.stories.ts @@ -0,0 +1,77 @@ +import AudioWaveformDisplay from './AudioWaveformDisplay.svelte'; +import type { Meta, StoryFn } from '@storybook/svelte'; + +const meta: Meta = { + title: 'Components/Media/AudioWaveformDisplay', + component: AudioWaveformDisplay, + tags: ['autodocs'], + argTypes: { + src: { + control: { type: 'text' }, + }, + isPlaying: { + control: { type: 'boolean' }, + }, + isLoading: { + control: { type: 'boolean' }, + }, + currentTime: { + control: { type: 'number' }, + }, + duration: { + control: { type: 'number' }, + }, + }, +}; + +export default meta; + +const Template: StoryFn = (args) => ({ + Component: AudioWaveformDisplay, + props: args, +}); + +export const Default = Template.bind({}); +Default.args = { + src: 'path/to/audio.mp3', + isPlaying: false, + isLoading: false, + currentTime: 0, + duration: 0, +}; + +export const Playing = Template.bind({}); +Playing.args = { + src: 'path/to/audio.mp3', + isPlaying: true, + isLoading: false, + currentTime: 10, + duration: 100, +}; + +export const Paused = Template.bind({}); +Paused.args = { + src: 'path/to/audio.mp3', + isPlaying: false, + isLoading: false, + currentTime: 10, + duration: 100, +}; + +export const Loading = Template.bind({}); +Loading.args = { + src: 'path/to/audio.mp3', + isPlaying: false, + isLoading: true, + currentTime: 0, + duration: 0, +}; + +export const Scrubbing = Template.bind({}); +Scrubbing.args = { + src: 'path/to/audio.mp3', + isPlaying: false, + isLoading: false, + currentTime: 50, + duration: 100, +}; \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/sveltekit/src/components/AudioWaveformDisplay/AudioWaveformDisplay.svelte b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/AudioWaveformDisplay/AudioWaveformDisplay.svelte new file mode 100644 index 0000000000..216ca77173 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/AudioWaveformDisplay/AudioWaveformDisplay.svelte @@ -0,0 +1,92 @@ + + +
    + + + +
    + + \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/sveltekit/src/components/Badge/Badge.css b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/Badge/Badge.css new file mode 100644 index 0000000000..ec66c30e43 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/Badge/Badge.css @@ -0,0 +1,32 @@ +:root { + --default-bg-color: #e0e0e0; + --notification-bg-color: #ff5733; + --status-bg-color: #28a745; + --text-color: #ffffff; + --padding: 0.5rem; + --border-radius: 12px; + --font-size: 0.875rem; +} + +.badge { + padding: var(--padding); + border-radius: var(--border-radius); + font-size: var(--font-size); + color: var(--text-color); + display: inline-block; + text-align: center; + min-width: 2rem; + line-height: 1.5; +} + +.badge.default { + background-color: var(--default-bg-color); +} + +.badge.notification { + background-color: var(--notification-bg-color); +} + +.badge.status { + background-color: var(--status-bg-color); +} \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/sveltekit/src/components/Badge/Badge.stories.ts b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/Badge/Badge.stories.ts new file mode 100644 index 0000000000..a2f81b09fb --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/Badge/Badge.stories.ts @@ -0,0 +1,51 @@ +import Badge from './Badge.svelte'; +import type { Meta,StoryFn } from '@storybook/svelte'; + +const meta: Meta = { + title: 'component/Indicators/Badge', + component: Badge, + tags: ['autodocs'], + argTypes: { + type: { + control: { type: 'select' }, + options: ['default', 'notification', 'status'] + }, + label: { control: 'text' }, + }, + parameters: { + layout: 'centered', + viewport: { + viewports: { + smallMobile: { name: 'Small Mobile', styles: { width: '320px', height: '568px' } }, + largeMobile: { name: 'Large Mobile', styles: { width: '414px', height: '896px' } }, + tablet: { name: 'Tablet', styles: { width: '768px', height: '1024px' } }, + desktop: { name: 'Desktop', styles: { width: '1024px', height: '768px' } }, + } + } + } +}; + +export default meta; + +const Template:StoryFn = (args)=>({ + Component: Badge, + props:args, +}); + +export const Default = Template.bind({}); +Default.args = { + type:'default', + label:'Default Badge.', +}; + +export const Notification = Template.bind({}); +Notification.args = { + type:'notification', + label:'3', +}; + +export const StatusIndicator = Template.bind({}); +StatusIndicator.args = { + type:'status', + label:'Online', +}; \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/sveltekit/src/components/Badge/Badge.svelte b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/Badge/Badge.svelte new file mode 100644 index 0000000000..0860a3e33a --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/Badge/Badge.svelte @@ -0,0 +1,15 @@ + + + + {label} + + + \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/sveltekit/src/components/BadgeWithCounts/BadgeWithCounts.css b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/BadgeWithCounts/BadgeWithCounts.css new file mode 100644 index 0000000000..806c55c8d7 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/BadgeWithCounts/BadgeWithCounts.css @@ -0,0 +1,27 @@ +:root { + --zero-bg-color: #e0e0e0; + --active-bg-color: #ff5733; + --text-color: #ffffff; + --padding: 0.5rem; + --border-radius: 12px; + --font-size: 0.875rem; +} + +.badge { + padding: var(--padding); + border-radius: var(--border-radius); + font-size: var(--font-size); + color: var(--text-color); + display: inline-block; + text-align: center; + min-width: 2rem; + line-height: 1.5; +} + +.badge.zero { + background-color: var(--zero-bg-color); +} + +.badge.active { + background-color: var(--active-bg-color); +} \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/sveltekit/src/components/BadgeWithCounts/BadgeWithCounts.stories.ts b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/BadgeWithCounts/BadgeWithCounts.stories.ts new file mode 100644 index 0000000000..1808675182 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/BadgeWithCounts/BadgeWithCounts.stories.ts @@ -0,0 +1,54 @@ +import BadgeWithCounts from './BadgeWithCounts.svelte'; +import type { Meta,StoryFn } from '@storybook/svelte'; + +const meta: Meta = { + title: 'component/Indicators/BadgeWithCounts', + component: BadgeWithCounts, + tags: ['autodocs'], + argTypes: { + count: { control: 'number' }, + maxCount: { control: 'number' }, + }, + parameters: { + layout: 'centered', + viewport: { + viewports: { + smallMobile: { name: 'Small Mobile', styles: { width: '320px', height: '568px' } }, + largeMobile: { name: 'Large Mobile', styles: { width: '414px', height: '896px' } }, + tablet: { name: 'Tablet', styles: { width: '768px', height: '1024px' } }, + desktop: { name: 'Desktop', styles: { width: '1024px', height: '768px' } }, + } + } + } +}; + +export default meta; + +const Template:StoryFn = (args) => ({ + Component: BadgeWithCounts, + props:args, +}); + +export const Default = Template.bind({}); +Default.args = { + count: 0, + maxCount: 99, +}; + +export const Zero = Template.bind({}); +Zero.args = { + count: 0, + maxCount: 99, +}; + +export const Active = Template.bind({}); +Active.args = { + count: 43, + maxCount: 99, +} + +export const Overflow = Template.bind({}); +Overflow.args = { + count: 150, + maxCount: 99, +} \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/sveltekit/src/components/BadgeWithCounts/BadgeWithCounts.svelte b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/BadgeWithCounts/BadgeWithCounts.svelte new file mode 100644 index 0000000000..a2d884bd41 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/BadgeWithCounts/BadgeWithCounts.svelte @@ -0,0 +1,16 @@ + + + + {displayCount} + + + \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/sveltekit/src/components/BatteryLevelIndicator/BatteryLevelIndicator.css b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/BatteryLevelIndicator/BatteryLevelIndicator.css new file mode 100644 index 0000000000..59b26081a8 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/BatteryLevelIndicator/BatteryLevelIndicator.css @@ -0,0 +1,47 @@ +:root { + --battery-border-color: #333333; + --full-color: #4caf50; + --low-color: #ffeb3b; + --critical-color: #f44336; + --charging-color: #2196f3; + --battery-height: 20px; + --battery-width: 100px; + --border-radius: 4px; +} + +.battery { + border: 2px solid var(--battery-border-color); + border-radius: var(--border-radius); + width: var(--battery-width); + height: var(--battery-height); + position: relative; +} + +.battery::after { + content: ''; + position: absolute; + top: 25%; + right: -6px; + width: 4px; + height: 50%; + background: var(--battery-border-color); + border-radius: 2px; +} + +.level { + height: 100%; + background-color: var(--full-color); + border-radius: calc(var(--border-radius) - 2px); +} + +.battery.low .level { + background-color: var(--low-color); +} + +.battery.critical .level { + background-color: var(--critical-color); +} + +.battery.charging .level { + background-color: var(--charging-color); +} \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/sveltekit/src/components/BatteryLevelIndicator/BatteryLevelIndicator.stories.ts b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/BatteryLevelIndicator/BatteryLevelIndicator.stories.ts new file mode 100644 index 0000000000..b05a167dc9 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/BatteryLevelIndicator/BatteryLevelIndicator.stories.ts @@ -0,0 +1,60 @@ +import BatteryLevelIndicator from './BatteryLevelIndicator.svelte'; +import type { Meta, StoryFn } from '@storybook/svelte'; + +const meta: Meta = { + title: 'component/Indicators/BatteryLevelIndicator', + component: BatteryLevelIndicator, + tags: ['autodocs'], + argTypes: { + level: { control: 'number' }, + isCharging: { control: 'boolean' }, + }, + parameters: { + layout: 'centered', + viewport: { + viewports: { + smallMobile: { name: 'Small Mobile', styles: { width: '320px', height: '568px' } }, + largeMobile: { name: 'Large Mobile', styles: { width: '414px', height: '896px' } }, + tablet: { name: 'Tablet', styles: { width: '768px', height: '1024px' } }, + desktop: { name: 'Desktop', styles: { width: '1024px', height: '768px' } }, + } + } + } +}; + +export default meta; + +const Template:StoryFn = (args) => ({ + Component: BatteryLevelIndicator, + props:args, +}); + +export const Default = Template.bind({}); +Default.args = { + level:50, + isCharging:false, +}; + +export const Charging = Template.bind({}); +Charging.args = { + level:50, + isCharging:true, +}; + +export const Full = Template.bind({}); +Full.args = { + level:100, + isCharging:false, +}; + +export const LowBattery = Template.bind({}); +LowBattery.args = { + level:20, + isCharging:false, +}; + +export const Critical = Template.bind({}); +Critical.args = { + level:5, + isCharging:false, +}; \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/sveltekit/src/components/BatteryLevelIndicator/BatteryLevelIndicator.svelte b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/BatteryLevelIndicator/BatteryLevelIndicator.svelte new file mode 100644 index 0000000000..92a9d70f82 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/BatteryLevelIndicator/BatteryLevelIndicator.svelte @@ -0,0 +1,16 @@ + + +
    +
    +
    + + \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/sveltekit/src/components/Button/Button.css b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/Button/Button.css new file mode 100644 index 0000000000..7c8ae6a422 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/Button/Button.css @@ -0,0 +1,38 @@ +:root { + --button-primary-bg: #007bff; + --button-secondary-bg: #6c757d; + --button-disabled-bg: #d6d6d6; + --button-text-color: #ffffff; + --button-hover-opacity: 0.8; + --button-active-opacity: 0.6; +} + +.button { + padding: 0.5rem 1rem; + font-size: 1rem; + color: var(--button-text-color); + border: none; + cursor: pointer; + transition: opacity 0.3s; +} + +.button.primary { + background-color: var(--button-primary-bg); +} + +.button.secondary { + background-color: var(--button-secondary-bg); +} + +.button:disabled { + background-color: var(--button-disabled-bg); + cursor: not-allowed; +} + +.button:hover:not(:disabled) { + opacity: var(--button-hover-opacity); +} + +.button:active:not(:disabled) { + opacity: var(--button-active-opacity); +} \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/sveltekit/src/components/Button/Button.stories.ts b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/Button/Button.stories.ts new file mode 100644 index 0000000000..d4beae0047 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/Button/Button.stories.ts @@ -0,0 +1,69 @@ +import Button from './Button.svelte'; +import type { Meta, StoryFn } from '@storybook/svelte'; + +const meta: Meta + + \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/sveltekit/src/components/Captcha/Captcha.css b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/Captcha/Captcha.css new file mode 100644 index 0000000000..b693aa3cc4 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/Captcha/Captcha.css @@ -0,0 +1,48 @@ +:root { + --captcha-bg: #f9f9f9; + --captcha-border: #ccc; + --captcha-error: #ff4d4d; + --captcha-success: #4caf50; +} + +.captcha-container { + background-color: var(--captcha-bg); + border: 1px solid var(--captcha-border); + padding: 1rem; + border-radius: 4px; + max-width: 300px; + margin: 0 auto; +} + +input[type="text"] { + width: 100%; + padding: 0.5rem; + margin-bottom: 0.5rem; + border: 1px solid var(--captcha-border); + border-radius: 4px; +} + +button { + width: 100%; + padding: 0.5rem; + background-color: var(--captcha-border); + border: none; + border-radius: 4px; + cursor: pointer; + transition: background-color 0.3s; +} + +button:disabled { + background-color: var(--captcha-success); + cursor: not-allowed; +} + +.error-message { + color: var(--captcha-error); + margin-top: 0.5rem; +} + +.solved-message { + color: var(--captcha-success); + margin-top: 0.5rem; +} \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/sveltekit/src/components/Captcha/Captcha.stories.ts b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/Captcha/Captcha.stories.ts new file mode 100644 index 0000000000..e6a20676ef --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/Captcha/Captcha.stories.ts @@ -0,0 +1,52 @@ +import Captcha from './Captcha.svelte'; +import type { Meta, StoryFn } from '@storybook/svelte'; + +const meta: Meta = { + title: 'component/Forms/Captcha', + component: Captcha, + tags: ['autodocs'], + argTypes: { + question: { control: 'text' }, + errorMessage: { control: 'text' }, + solved: { control: 'boolean' }, + onSolve: { action: 'solved' }, + }, + parameters: { + layout: 'centered', + viewport: { + viewports: { + smallMobile: { name: 'Small Mobile', styles: { width: '320px', height: '568px' } }, + largeMobile: { name: 'Large Mobile', styles: { width: '414px', height: '896px' } }, + tablet: { name: 'Tablet', styles: { width: '768px', height: '1024px' } }, + desktop: { name: 'Desktop', styles: { width: '1024px', height: '768px' } }, + } + } + } +}; + +export default meta; + +const Template:StoryFn = (args) => ({ + Component: Captcha, + props:args +}); + +export const Default = Template.bind({}); +Default.args = { + question: 'What is 2 + 2 ?', + errorMessage: '', + solved:false, +}; + +export const Solved = Template.bind({}); +Solved.args = { + question: 'What is 2 + 2 ?', + solved:true, +}; + +export const Error = Template.bind({}); +Error.args = { + question: 'What is 2 + 2 ?', + errorMessage: 'Incorrect answer, please try again', + solved:false, +}; \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/sveltekit/src/components/Captcha/Captcha.svelte b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/Captcha/Captcha.svelte new file mode 100644 index 0000000000..5a46bd8937 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/Captcha/Captcha.svelte @@ -0,0 +1,34 @@ + + +
    +

    {question}

    + + + {#if errorMessage} + + {/if} + {#if solved} +

    Captcha Solved!

    + {/if} +
    + + \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/sveltekit/src/components/CardbasedList/CardbasedList.css b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/CardbasedList/CardbasedList.css new file mode 100644 index 0000000000..4dda9fbd10 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/CardbasedList/CardbasedList.css @@ -0,0 +1,36 @@ +:root { + --card-bg: #ffffff; + --card-hover-bg: #f0f0f0; + --card-selected-bg: #d0f0d0; + --card-disabled-bg: #e0e0e0; + --card-border: #ccc; + --card-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); +} + +.cardbased-list { + display: grid; + gap: 10px; +} + +.card { + background: var(--card-bg); + border: 1px solid var(--card-border); + border-radius: 4px; + box-shadow: var(--card-shadow); + padding: 15px; + cursor: pointer; + transition: background 0.3s; +} + +.card:hover { + background: var(--card-hover-bg); +} + +.card.selected { + background: var(--card-selected-bg); +} + +.card.disabled { + background: var(--card-disabled-bg); + cursor: not-allowed; +} \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/sveltekit/src/components/CardbasedList/CardbasedList.stories.ts b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/CardbasedList/CardbasedList.stories.ts new file mode 100644 index 0000000000..df4ea3ea7a --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/CardbasedList/CardbasedList.stories.ts @@ -0,0 +1,68 @@ +import CardbasedList from './CardbasedList.svelte'; +import type { Meta, StoryFn } from '@storybook/svelte'; + +const meta: Meta = { + title: 'component/Lists/CardbasedList', + component: CardbasedList, + tags: ['autodocs'], + argTypes: { + cards: { control: 'object' } + }, + parameters: { + layout: 'centered', + viewport: { + viewports: { + smallMobile: { name: 'Small Mobile', styles: { width: '320px', height: '568px' } }, + largeMobile: { name: 'Large Mobile', styles: { width: '414px', height: '896px' } }, + tablet: { name: 'Tablet', styles: { width: '768px', height: '1024px' } }, + desktop: { name: 'Desktop', styles: { width: '1024px', height: '768px' } }, + } + } + } +}; + +export default meta; + +const Template:StoryFn = (args) => ({ + Component:CardbasedList, + props:args, +}); + +export const Default = Template.bind({}); +Default.args = { + cards: [ + { id: 1, title: 'Card 1', description: 'This is card 1 description' }, + { id: 2, title: 'Card 2', description: 'This is card 2 description' }, + { id: 3, title: 'Card 3', description: 'This is card 3 description' }, + ] +}; + +export const Hover = Template.bind({}); +Hover.args = { + cards: [ + { id: 1, title: 'Card 1', description: 'This is card 1 description' }, + { id: 2, title: 'Card 2', description: 'This is card 2 description' }, + { id: 3, title: 'Card 3', description: 'This is card 3 description' }, + ] +}; +Hover.parameters = { + pseudo: {hover:true}, +}; + +export const Selected = Template.bind({}); +Selected.args = { + cards: [ + { id: 1, title: 'Card 1', description: 'This is card 1 description', selected:true, }, + { id: 2, title: 'Card 2', description: 'This is card 2 description', selected:false,}, + { id: 3, title: 'Card 3', description: 'This is card 3 description', selected:true, }, + ] +}; + +export const Disabled = Template.bind({}); +Disabled.args = { + cards: [ + { id: 1, title: 'Card 1', description: 'This is card 1 description', disabled:true, }, + { id: 2, title: 'Card 2', description: 'This is card 2 description', disabled:false,}, + { id: 3, title: 'Card 3', description: 'This is card 3 description', disabled:true, }, + ] +}; \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/sveltekit/src/components/CardbasedList/CardbasedList.svelte b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/CardbasedList/CardbasedList.svelte new file mode 100644 index 0000000000..ede4227a3e --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/CardbasedList/CardbasedList.svelte @@ -0,0 +1,29 @@ + + +
    + {#each cards as card (card.id)} +
    selectCard(card)} + aria-disabled={card.disabled} + role ='button' + tabindex = "0" + on:keydown={(e)=> e.key === 'Enter' && selectCard(card)} + > +

    {card.title}

    +

    {card.description}

    +
    + {/each} +
    + + \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/sveltekit/src/components/Carousel/Carousel.css b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/Carousel/Carousel.css new file mode 100644 index 0000000000..26e184b4b5 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/Carousel/Carousel.css @@ -0,0 +1,44 @@ +.carousel { + position: relative; + width: 100%; + max-width: 600px; + margin: auto; + overflow: hidden; + display: flex; + align-items: center; +} + +img { + width: 100%; + display: none; + transition: opacity 0.5s ease; +} + +img.selected { + display: block; + opacity: 1; +} + +button { + position: absolute; + top: 50%; + transform: translateY(-50%); + background-color: rgba(0, 0, 0, 0.5); + color: white; + border: none; + padding: 10px; + cursor: pointer; + z-index: 1; +} + +button:focus { + outline: 2px solid #fff; +} + +button[aria-label="Previous slide"] { + left: 10px; +} + +button[aria-label="Next slide"] { + right: 10px; +} \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/sveltekit/src/components/Carousel/Carousel.stories.ts b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/Carousel/Carousel.stories.ts new file mode 100644 index 0000000000..0a7cd1176c --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/Carousel/Carousel.stories.ts @@ -0,0 +1,61 @@ +import Carousel from './Carousel.svelte'; +import type { Meta, StoryFn } from '@storybook/svelte'; + +const meta: Meta = { + title: 'Components/Media/Carousel', + component: Carousel, + tags: ['autodocs'], + argTypes: { + images: { + control: { type: 'object' }, + }, + autoPlay: { + control: { type: 'boolean' }, + }, + autoPlayInterval: { + control: { type: 'number' }, + }, + }, +}; + +export default meta; + +const Template: StoryFn = (args) => ({ + Component: Carousel, + props: args, +}); + +export const Default = Template.bind({}); +Default.args = { + images: ['image1.jpg', 'image2.jpg', 'image3.jpg'], + autoPlay: false, + autoPlayInterval: 3000, +}; + +export const AutoPlay = Template.bind({}); +AutoPlay.args = { + images: ['image1.jpg', 'image2.jpg', 'image3.jpg'], + autoPlay: true, + autoPlayInterval: 3000, +}; + +export const Paused = Template.bind({}); +Paused.args = { + images: ['image1.jpg', 'image2.jpg', 'image3.jpg'], + autoPlay: false, + autoPlayInterval: 3000, +}; + +export const Hover = Template.bind({}); +Hover.args = { + images: ['image1.jpg', 'image2.jpg', 'image3.jpg'], + autoPlay: true, + autoPlayInterval: 3000, +}; + +export const Active = Template.bind({}); +Active.args = { + images: ['image1.jpg', 'image2.jpg', 'image3.jpg'], + autoPlay: true, + autoPlayInterval: 1000, +}; \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/sveltekit/src/components/Carousel/Carousel.svelte b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/Carousel/Carousel.svelte new file mode 100644 index 0000000000..47bb8fd81e --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/Carousel/Carousel.svelte @@ -0,0 +1,53 @@ + + + + + \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/sveltekit/src/components/CheckList/CheckList.css b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/CheckList/CheckList.css new file mode 100644 index 0000000000..daefbc7e25 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/CheckList/CheckList.css @@ -0,0 +1,42 @@ +:root { + --checklist-bg: #ffffff; + --checklist-border: #ccc; + --checklist-checked-bg: #d0f0d0; + --checklist-partially-checked-bg: #f0d0d0; + --checklist-disabled-bg: #e0e0e0; + --label-color: #333; +} + +.checklist { + list-style: none; + padding: 0; +} + +.checklist-item { + display: flex; + align-items: center; + padding: 10px; + background: var(--checklist-bg); + border: 1px solid var(--checklist-border); + border-radius: 4px; + margin-bottom: 5px; + transition: background 0.3s; +} + +.checklist-item.checked { + background: var(--checklist-checked-bg); +} + +.checklist-item.partially-checked { + background: var(--checklist-partially-checked-bg); +} + +.checklist-item.disabled { + background: var(--checklist-disabled-bg); + cursor: not-allowed; +} + +label { + margin-left: 10px; + color: var(--label-color); +} \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/sveltekit/src/components/CheckList/CheckList.stories.ts b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/CheckList/CheckList.stories.ts new file mode 100644 index 0000000000..eee24ac056 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/CheckList/CheckList.stories.ts @@ -0,0 +1,74 @@ +import CheckList from './CheckList.svelte'; +import type { Meta, StoryFn } from '@storybook/svelte'; + +const meta: Meta = { + title: 'component/Lists/CheckList', + component: CheckList, + tags: ['autodocs'], + argTypes: { + items: { control: 'object' } + }, + parameters: { + layout: 'centered', + viewport: { + viewports: { + smallMobile: { name: 'Small Mobile', styles: { width: '320px', height: '568px' } }, + largeMobile: { name: 'Large Mobile', styles: { width: '414px', height: '896px' } }, + tablet: { name: 'Tablet', styles: { width: '768px', height: '1024px' } }, + desktop: { name: 'Desktop', styles: { width: '1024px', height: '768px' } }, + } + } + } +}; + +export default meta; + +const Template:StoryFn = (args) => ({ + Component: CheckList, + props: args, +}); + +export const Default = Template.bind({}); +Default.args = { + items : [ + { id: 1, label: 'Item 1' }, + { id: 2, label: 'Item 2' }, + { id: 3, label: 'Item 3' }, + ], +}; + +export const Checked = Template.bind({}); +Checked.args = { + items : [ + { id: 1, label: 'Item 1', checked:true, }, + { id: 2, label: 'Item 2', checked:false, }, + { id: 3, label: 'Item 3' , checked:true, }, + ], +}; + +export const UnChecked = Template.bind({}); +UnChecked.args = { + items : [ + { id: 1, label: 'Item 1', checked:false, }, + { id: 2, label: 'Item 2', checked:false, }, + { id: 3, label: 'Item 3' , checked:false, }, + ], +}; + +export const PartiallyChecked = Template.bind({}); +PartiallyChecked.args = { + items : [ + { id: 1, label: 'Item 1', partiallyChecked:true, }, + { id: 2, label: 'Item 2', partiallyChecked:false, }, + { id: 3, label: 'Item 3' , partiallyChecked:true, }, + ], +}; + +export const Disabled = Template.bind({}); +Disabled.args = { + items : [ + { id: 1, label: 'Item 1', disabled:true, }, + { id: 2, label: 'Item 2', disabled:false, }, + { id: 3, label: 'Item 3' , disabled:true, }, + ], +}; \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/sveltekit/src/components/CheckList/CheckList.svelte b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/CheckList/CheckList.svelte new file mode 100644 index 0000000000..7c35cf60fd --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/CheckList/CheckList.svelte @@ -0,0 +1,36 @@ + + +
      + {#each items as item (item.id)} +
    • + toggleCheck(item)} + disabled={item.disabled} + aria-checked={item.partiallyChecked ? 'mixed' : item.checked} + id={`checkbox-${item.id}`} + /> + +
    • + {/each} +
    + + \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/sveltekit/src/components/Checkbox/Checkbox.css b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/Checkbox/Checkbox.css new file mode 100644 index 0000000000..483cee116f --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/Checkbox/Checkbox.css @@ -0,0 +1,37 @@ +:root { + --checkbox-bg: #fff; + --checkbox-border: #ccc; + --checkbox-checked: #4caf50; + --checkbox-disabled: #e0e0e0; + --checkbox-text: #000; +} + +.checkbox-container { + display: flex; + align-items: center; +} + +input[type='checkbox'] { + appearance: none; + background-color: var(--checkbox-bg); + border: 1px solid var(--checkbox-border); + width: 18px; + height: 18px; + margin-right: 0.5rem; + display: inline-block; + position: relative; +} + +input[type='checkbox']:checked { + background-color: var(--checkbox-checked); +} + +input[type='checkbox']:disabled { + background-color: var(--checkbox-disabled); + border-color: var(--checkbox-disabled); +} + +label { + color: var(--checkbox-text); + font-size: 1rem; +} \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/sveltekit/src/components/Checkbox/Checkbox.stories.ts b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/Checkbox/Checkbox.stories.ts new file mode 100644 index 0000000000..9247c1ede1 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/Checkbox/Checkbox.stories.ts @@ -0,0 +1,59 @@ +import Checkbox from './Checkbox.svelte'; +import type { Meta, StoryFn } from '@storybook/svelte'; + +const meta: Meta = { + title: 'component/Forms/Checkbox', + component: Checkbox, + tags: ['autodocs'], + argTypes: { + label: { control: 'text' }, + checked: { control: 'boolean' }, + disabled: { control: 'boolean' }, + }, + parameters: { + layout: 'centered', + viewport: { + viewports: { + smallMobile: { name: 'Small Mobile', styles: { width: '320px', height: '568px' } }, + largeMobile: { name: 'Large Mobile', styles: { width: '414px', height: '896px' } }, + tablet: { name: 'Tablet', styles: { width: '768px', height: '1024px' } }, + desktop: { name: 'Desktop', styles: { width: '1024px', height: '768px' } }, + } + } + } +}; + +export default meta; + +const Template:StoryFn = (args) => ({ + Component:Checkbox, + props:args, +}); + +export const Default = Template.bind({}); +Default.args = { + label: 'Accept terms and conditions', + checked: false, + disabled: false, +}; + +export const Checked = Template.bind({}); +Checked.args = { + label: 'Accept terms and conditions', + checked: true, + disabled: false, +}; + +export const Unchecked = Template.bind({}); +Unchecked.args = { + label: 'Accept terms and conditions', + checked: false, + disabled: false, +}; + +export const Disabled = Template.bind({}); +Disabled.args = { + label: 'Accept terms and conditions', + checked: false, + disabled: true, +} \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/sveltekit/src/components/Checkbox/Checkbox.svelte b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/Checkbox/Checkbox.svelte new file mode 100644 index 0000000000..104ac19560 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/Checkbox/Checkbox.svelte @@ -0,0 +1,28 @@ + + +
    + + +
    + + \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/sveltekit/src/components/CollapsibleMenuList/CollapsibleMenuList.css b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/CollapsibleMenuList/CollapsibleMenuList.css new file mode 100644 index 0000000000..6d60ad7f91 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/CollapsibleMenuList/CollapsibleMenuList.css @@ -0,0 +1,49 @@ +:root { + --menu-bg: #ffffff; + --menu-hover-bg: #f0f0f0; + --menu-expanded-bg: #d0e0f0; + --menu-active-bg: #a0c0f0; + --menu-border: #ccc; + --menu-color: #333; +} + +.collapsible-menu { + list-style: none; + padding: 0; +} + +.menu-item { + cursor: pointer; + background: var(--menu-bg); + border: 1px solid var(--menu-border); + margin-bottom: 5px; + transition: background 0.3s; +} + +.menu-item:hover { + background: var(--menu-hover-bg); +} + +.menu-item.expanded { + background: var(--menu-expanded-bg); +} + +.menu-item.active { + background: var(--menu-active-bg); +} + +.menu-label { + padding: 10px; + color: var(--menu-color); +} + +.submenu { + list-style: none; + padding: 0 10px; + background: var(--menu-expanded-bg); +} + +.submenu li { + padding: 5px 0; + color: var(--menu-color); +} \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/sveltekit/src/components/CollapsibleMenuList/CollapsibleMenuList.stories.ts b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/CollapsibleMenuList/CollapsibleMenuList.stories.ts new file mode 100644 index 0000000000..fcdd091027 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/CollapsibleMenuList/CollapsibleMenuList.stories.ts @@ -0,0 +1,75 @@ +import CollapsibleMenuList from './CollapsibleMenuList.svelte'; +import type { Meta, StoryFn } from '@storybook/svelte'; + +const meta: Meta = { + title: 'component/Lists/CollapsibleMenuList', + component: CollapsibleMenuList, + tags: ['autodocs'], + argTypes: { + items: { control: 'object' } + }, + parameters: { + layout: 'centered', + viewport: { + viewports: { + smallMobile: { name: 'Small Mobile', styles: { width: '320px', height: '568px' } }, + largeMobile: { name: 'Large Mobile', styles: { width: '414px', height: '896px' } }, + tablet: { name: 'Tablet', styles: { width: '768px', height: '1024px' } }, + desktop: { name: 'Desktop', styles: { width: '1024px', height: '768px' } }, + } + } + } +}; + +export default meta; + +const Template:StoryFn = (args) => ({ + Component: CollapsibleMenuList, + props: args, +}); + +export const Default = Template.bind({}); +Default.args = { + items: [ + { id: 1, label: 'Menu 1' }, + { id: 2, label: 'Menu 2' }, + { id: 3, label: 'Menu 3' } + ], +}; + +export const Expanded = Template.bind({}); +Expanded.args = { + items: [ + { id: 1, label: 'Menu 1' , expanded:true , }, + { id: 2, label: 'Menu 2'}, + { id: 3, label: 'Menu 3', expanded:true ,}, + ], +}; + +export const Collapsed = Template.bind({}); +Collapsed.args = { + items: [ + { id: 1, label: 'Menu 1', expanded:false }, + { id: 2, label: 'Menu 2', expanded:false }, + { id: 3, label: 'Menu 3', expanded:false }, + ], +}; + +export const Hover = Template.bind({}); +Hover.args = { + items: [ + { id: 1, label: 'Menu 1' }, + { id: 2, label: 'Menu 2',expanded: true,}, + { id: 3, label: 'Menu 3' } + ], +}; + + +export const Active = Template.bind({}); +Active.args = { + items: [ + { id: 1, label: 'Menu 1', active:true,}, + { id: 2, label: 'Menu 2'}, + { id: 3, label: 'Menu 3'}, + ], +}; \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/sveltekit/src/components/CollapsibleMenuList/CollapsibleMenuList.svelte b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/CollapsibleMenuList/CollapsibleMenuList.svelte new file mode 100644 index 0000000000..a63f29096c --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/CollapsibleMenuList/CollapsibleMenuList.svelte @@ -0,0 +1,48 @@ + + +
      + {#each items as item (item.id)} + + {/each} +
    + + \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/sveltekit/src/components/ColorPicker/ColorPicker.css b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/ColorPicker/ColorPicker.css new file mode 100644 index 0000000000..c0e678a257 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/ColorPicker/ColorPicker.css @@ -0,0 +1,27 @@ +:root { + --color-picker-border: #ccc; + --color-picker-disabled-bg: #e0e0e0; + --color-picker-value-text: #333; +} + +.color-picker-container { + display: flex; + align-items: center; +} + +input[type='color'] { + border: 1px solid var(--color-picker-border); + width: 36px; + height: 36px; + margin-right: 0.5rem; + padding: 0; +} + +input[type='color']:disabled { + background-color: var(--color-picker-disabled-bg); +} + +.color-value { + color: var(--color-picker-value-text); + font-size: 1rem; +} \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/sveltekit/src/components/ColorPicker/ColorPicker.stories.ts b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/ColorPicker/ColorPicker.stories.ts new file mode 100644 index 0000000000..90cba50f6c --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/ColorPicker/ColorPicker.stories.ts @@ -0,0 +1,48 @@ +import ColorPicker from './ColorPicker.svelte'; +import type { Meta, StoryFn } from '@storybook/svelte'; + +const meta: Meta = { + title: 'component/Forms/ColorPicker', + component: ColorPicker, + tags: ['autodocs'], + argTypes: { + color: { control: 'color' }, + disabled: { control: 'boolean' }, + }, + parameters: { + layout: 'centered', + viewport: { + viewports: { + smallMobile: { name: 'Small Mobile', styles: { width: '320px', height: '568px' } }, + largeMobile: { name: 'Large Mobile', styles: { width: '414px', height: '896px' } }, + tablet: { name: 'Tablet', styles: { width: '768px', height: '1024px' } }, + desktop: { name: 'Desktop', styles: { width: '1024px', height: '768px' } }, + } + } + } +}; + +export default meta; + +const Template:StoryFn = (args) => ({ + Component: ColorPicker, + props:args, +}); + +export const Default = Template.bind({}); +Default.args = { + 'color': '#000000', + disabled:false, +}; + +export const Selected = Template.bind({}); +Selected.args = { + color:'#ff5733', + disabled:false, +}; + +export const Disabled = Template.bind({}); +Disabled.args = { + color:'#000000', + disabled:true, +}; \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/sveltekit/src/components/ColorPicker/ColorPicker.svelte b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/ColorPicker/ColorPicker.svelte new file mode 100644 index 0000000000..a6f83ae513 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/ColorPicker/ColorPicker.svelte @@ -0,0 +1,24 @@ + + +
    + + {color} +
    + + \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/sveltekit/src/components/ContextualList/ContextualList.css b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/ContextualList/ContextualList.css new file mode 100644 index 0000000000..7184135177 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/ContextualList/ContextualList.css @@ -0,0 +1,43 @@ +:root { + --list-bg: #ffffff; + --list-border: #ccc; + --list-item-hover-bg: #f0f0f0; + --list-action-bg: #d0f0c0; + --list-color: #333; +} + +.contextual-list { + list-style: none; + padding: 0; + border: 1px solid var(--list-border); + background: var(--list-bg); +} + +.list-item { + padding: 10px; + cursor: pointer; + transition: background 0.3s; + display: flex; + justify-content: space-between; + align-items: center; +} + +.list-item:hover { + background: var(--list-item-hover-bg); +} + +.list-item.action-triggered { + background: var(--list-action-bg); +} + +button { + background: none; + border: none; + cursor: pointer; + color: var(--list-color); + padding: 5px 10px; +} + +button:hover { + text-decoration: underline; +} \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/sveltekit/src/components/ContextualList/ContextualList.stories.ts b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/ContextualList/ContextualList.stories.ts new file mode 100644 index 0000000000..dccd19a479 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/ContextualList/ContextualList.stories.ts @@ -0,0 +1,60 @@ +import ContextualList from './ContextualList.svelte'; +import type { Meta, StoryFn } from '@storybook/svelte'; + +const meta: Meta = { + title: 'component/Lists/ContextualList', + component: ContextualList, + tags: ['autodocs'], + argTypes: { + items: { control: 'object' }, + visible: { control: 'boolean' } + }, + parameters: { + layout: 'centered', + viewport: { + viewports: { + smallMobile: { name: 'Small Mobile', styles: { width: '320px', height: '568px' } }, + largeMobile: { name: 'Large Mobile', styles: { width: '414px', height: '896px' } }, + tablet: { name: 'Tablet', styles: { width: '768px', height: '1024px' } }, + desktop: { name: 'Desktop', styles: { width: '1024px', height: '768px' } }, + } + } + } +}; + +export default meta; + +const Template:StoryFn = (args) => ({ + Component: ContextualList, + props:args, +}); + +export const Default = Template.bind({}); +Default.args = { + items: [ + { id: 1, label: 'Item 1' }, + { id: 2, label: 'Item 2' }, + { id: 3, label: 'Item 3' } + ], + visible: true, +}; + +export const ActionTriggered = Template.bind({}); +ActionTriggered.args = { + items: [ + { id: 1, label: 'Item 1',actionTriggered:true, }, + { id: 2, label: 'Item 2' }, + { id: 3, label: 'Item 3' } + ], + visible:true, +}; + +export const Dismissed = Template.bind({}); +Dismissed.args = { + items: [ + { id: 1, label: 'Item 1' }, + { id: 2, label: 'Item 2' }, + { id: 3, label: 'Item 3' } + ], + visible:false, +}; \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/sveltekit/src/components/ContextualList/ContextualList.svelte b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/ContextualList/ContextualList.svelte new file mode 100644 index 0000000000..4b78288ddf --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/ContextualList/ContextualList.svelte @@ -0,0 +1,36 @@ + + +{#if visible} +
      + {#each items as item (item.id)} +
    • + {item.label} + +
    • + {/each} +
    • + +
    • +
    +{/if} + + \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/sveltekit/src/components/CountdownTimer/CountdownTimer.css b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/CountdownTimer/CountdownTimer.css new file mode 100644 index 0000000000..bb92bfd3b1 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/CountdownTimer/CountdownTimer.css @@ -0,0 +1,42 @@ +:root { + --timer-border-color: #333333; + --background-color: #ffffff; + --text-color: #000000; + --button-background: #4caf50; + --button-text-color: #ffffff; + --button-border-radius: 4px; + --timer-font-size: 24px; + --button-font-size: 16px; + --button-padding: 8px 16px; +} + +.countdown-timer { + display: flex; + flex-direction: column; + align-items: center; + border: 2px solid var(--timer-border-color); + background-color: var(--background-color); + padding: 16px; + border-radius: 8px; +} + +.time-display { + font-size: var(--timer-font-size); + color: var(--text-color); + margin-bottom: 16px; +} + +button { + background-color: var(--button-background); + color: var(--button-text-color); + border: none; + border-radius: var(--button-border-radius); + padding: var(--button-padding); + margin: 4px; + cursor: pointer; + font-size: var(--button-font-size); +} + +button:hover { + background-color: darken(var(--button-background), 10%); +} \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/sveltekit/src/components/CountdownTimer/CountdownTimer.stories.ts b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/CountdownTimer/CountdownTimer.stories.ts new file mode 100644 index 0000000000..6065881e14 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/CountdownTimer/CountdownTimer.stories.ts @@ -0,0 +1,54 @@ +import CountdownTimer from './CountdownTimer.svelte'; +import type { Meta, StoryFn } from '@storybook/svelte'; + +const meta: Meta = { + title: 'component/Indicators/CountdownTimer', + component: CountdownTimer, + tags: ['autodocs'], + argTypes: { + duration: { control: 'number' }, + autoStart: { control: 'boolean' }, + }, + parameters: { + layout: 'centered', + viewport: { + viewports: { + smallMobile: { name: 'Small Mobile', styles: { width: '320px', height: '568px' } }, + largeMobile: { name: 'Large Mobile', styles: { width: '414px', height: '896px' } }, + tablet: { name: 'Tablet', styles: { width: '768px', height: '1024px' } }, + desktop: { name: 'Desktop', styles: { width: '1024px', height: '768px' } }, + } + } + } +}; + +export default meta; + +const Template:StoryFn = (args) => ({ + Component: CountdownTimer, + props: args, +}); + +export const Default = Template.bind({}); +Default.args = { + duration:60, + autoStart:false, +}; + +export const Running = Template.bind({}); +Running.args = { + duration:60, + autoStart:true, +}; + +export const Paused = Template.bind({}); +Default.args = { + duration:60, + autoStart:false, +}; + +export const Completed = Template.bind({}); +Completed.args = { + duration:0, + autoStart:false, +}; \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/sveltekit/src/components/CountdownTimer/CountdownTimer.svelte b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/CountdownTimer/CountdownTimer.svelte new file mode 100644 index 0000000000..1678778f12 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/CountdownTimer/CountdownTimer.svelte @@ -0,0 +1,52 @@ + + +
    +
    {timeLeft}s
    + + + +
    + + \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/sveltekit/src/components/DataGrid/DataGrid.css b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/DataGrid/DataGrid.css new file mode 100644 index 0000000000..eaa50811cc --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/DataGrid/DataGrid.css @@ -0,0 +1,55 @@ +:root { + --grid-border: #ccc; + --grid-header-bg: #f9f9f9; + --grid-row-hover-bg: #f0f0f0; + --grid-color: #333; + --pagination-bg: #ddd; +} + +.data-grid { + width: 100%; + overflow-x: auto; +} + +table { + width: 100%; + border-collapse: collapse; +} + +th, td { + padding: 8px; + border: 1px solid var(--grid-border); + text-align: left; +} + +th { + background-color: var(--grid-header-bg); +} + +tr:hover { + background-color: var(--grid-row-hover-bg); +} + +.pagination { + display: flex; + justify-content: space-between; + background: var(--pagination-bg); + padding: 10px; +} + +button { + background: none; + border: 1px solid var(--grid-border); + padding: 5px 10px; + cursor: pointer; +} + +button:disabled { + cursor: not-allowed; + opacity: 0.5; +} + +.resizable th { + resize: horizontal; + overflow: hidden; +} \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/sveltekit/src/components/DataGrid/DataGrid.stories.ts b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/DataGrid/DataGrid.stories.ts new file mode 100644 index 0000000000..3098741a41 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/DataGrid/DataGrid.stories.ts @@ -0,0 +1,97 @@ +import DataGrid from './DataGrid.svelte'; +import type { Meta, StoryFn } from '@storybook/svelte'; + +const meta: Meta = { + title: 'component/Lists/DataGrid', + component: DataGrid, + tags: ['autodocs'], + argTypes: { + columns: { control: 'object' }, + rows: { control: 'object' }, + pageSize: { control: 'number' }, + searchQuery: { control: 'text' }, + resizable: { control: 'boolean' } + }, + parameters: { + layout: 'centered', + viewport: { + viewports: { + smallMobile: { name: 'Small Mobile', styles: { width: '320px', height: '568px' } }, + largeMobile: { name: 'Large Mobile', styles: { width: '414px', height: '896px' } }, + tablet: { name: 'Tablet', styles: { width: '768px', height: '1024px' } }, + desktop: { name: 'Desktop', styles: { width: '1024px', height: '768px' } }, + } + } + } +}; + +export default meta; + +const Template:StoryFn = (args) => ({ + Component: DataGrid, + props: args, +}); + +export const Default = Template.bind({}); +Default.args = { + columns: [ + { id: 'id', label: 'ID' }, + { id: 'name', label: 'Name' }, + { id: 'age', label: 'Age' } + ], + rows: [ + { id: 1, name: 'John Doe', age: 28 }, + { id: 2, name: 'Jane Smith', age: 34 }, + { id: 3, name: 'Alice Johnson', age: 45 } + ], + pageSize:10, + searchQuery:'', + resizable:false, +}; + +export const Paginated = Template.bind({}); +Paginated.args = { + columns: [ + { id: 'id', label: 'ID' }, + { id: 'name', label: 'Name' }, + { id: 'age', label: 'Age' } + ], + rows: Array.from({ length: 30 }, (_, i) => ({ id: i + 1, name: `User ${i + 1}`, age: 20 + i })), + pageSize:5, + searchQuery:'', + resizable:false, +}; + +export const Search = Template.bind({}); +Search.args = { + columns: [ + { id: 'id', label: 'ID' }, + { id: 'name', label: 'Name' }, + { id: 'age', label: 'Age' } + ], + rows: [ + { id: 1, name: 'John Doe', age: 28 }, + { id: 2, name: 'Jane Smith', age: 34 }, + { id: 3, name: 'Alice Johnson', age: 45 } + ], + pageSize:5, + searchQuery:'Jane', + resizable:false, +}; + +export const Resizable = Template.bind({}); +Resizable.args = { + columns: [ + { id: 'id', label: 'ID' }, + { id: 'name', label: 'Name' }, + { id: 'age', label: 'Age' } + ], + rows: [ + { id: 1, name: 'John Doe', age: 28 }, + { id: 2, name: 'Jane Smith', age: 34 }, + { id: 3, name: 'Alice Johnson', age: 45 } + ], + pageSize:5, + searchQuery:'', + resizable:true, +}; \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/sveltekit/src/components/DataGrid/DataGrid.svelte b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/DataGrid/DataGrid.svelte new file mode 100644 index 0000000000..7810c0091b --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/DataGrid/DataGrid.svelte @@ -0,0 +1,68 @@ + + +
    + {#if searchQuery} + + {/if} + + + + + {#each columns as column} + + {/each} + + + + {#each filteredRows.slice(currentPage * pageSize, (currentPage + 1) * pageSize) as row} + + {#each columns as column} + + {/each} + + {/each} + +
    {column.label}
    {row[column.id]}
    + + {#if pageSize < rows.length} + + {/if} +
    + + \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/sveltekit/src/components/DateAndTimePicker/DateAndTimePicker.css b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/DateAndTimePicker/DateAndTimePicker.css new file mode 100644 index 0000000000..3bcfb53d29 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/DateAndTimePicker/DateAndTimePicker.css @@ -0,0 +1,24 @@ +:root { + --date-time-picker-border: #ccc; + --date-time-picker-disabled-bg: #e0e0e0; + --date-time-picker-font-color: #333; +} + +.date-time-picker-container { + display: flex; + flex-direction: column; + gap: 0.5rem; +} + +input[type='date'], +input[type='time'] { + border: 1px solid var(--date-time-picker-border); + padding: 0.5rem; + font-size: 1rem; + color: var(--date-time-picker-font-color); +} + +input[type='date']:disabled, +input[type='time']:disabled { + background-color: var(--date-time-picker-disabled-bg); +} \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/sveltekit/src/components/DateAndTimePicker/DateAndTimePicker.stories.ts b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/DateAndTimePicker/DateAndTimePicker.stories.ts new file mode 100644 index 0000000000..02117997f4 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/DateAndTimePicker/DateAndTimePicker.stories.ts @@ -0,0 +1,59 @@ +import DateAndTimePicker from './DateAndTimePicker.svelte'; +import type { Meta, StoryFn } from '@storybook/svelte'; + +const meta: Meta = { + title: 'component/Forms/DateAndTimePicker', + component: DateAndTimePicker, + tags: ['autodocs'], + argTypes: { + date: { control: 'date' }, + time: { control: 'text' }, + disabled: { control: 'boolean' }, + }, + parameters: { + layout: 'centered', + viewport: { + viewports: { + smallMobile: { name: 'Small Mobile', styles: { width: '320px', height: '568px' } }, + largeMobile: { name: 'Large Mobile', styles: { width: '414px', height: '896px' } }, + tablet: { name: 'Tablet', styles: { width: '768px', height: '1024px' } }, + desktop: { name: 'Desktop', styles: { width: '1024px', height: '768px' } }, + } + } + } +}; + +export default meta; + +const Template:StoryFn = (args) => ({ + Component: DateAndTimePicker, + props: args, +}); + +export const Default = Template.bind({}); +Default.args = { + date: '', + time: '', + disabled:false, +}; + +export const DateSelected = Template.bind({}); +DateSelected.args = { + date: '2023-10-01', + time: '', + disabled:false, +}; + +export const TimeSelected = Template.bind({}); +TimeSelected.args = { + date: '', + time: '12:00', + disabled:false, +}; + +export const Disabled = Template.bind({}); +Disabled.args = { + date: '', + time: '', + disabled:true, +}; \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/sveltekit/src/components/DateAndTimePicker/DateAndTimePicker.svelte b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/DateAndTimePicker/DateAndTimePicker.svelte new file mode 100644 index 0000000000..21ec2c27b2 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/DateAndTimePicker/DateAndTimePicker.svelte @@ -0,0 +1,34 @@ + + +
    + + +
    + + \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/sveltekit/src/components/DatePicker/DatePicker.css b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/DatePicker/DatePicker.css new file mode 100644 index 0000000000..e252eddcf6 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/DatePicker/DatePicker.css @@ -0,0 +1,24 @@ +:root { + --date-picker-border: #ccc; + --date-picker-disabled-bg: #e0e0e0; + --date-picker-font-color: #333; +} + +.date-picker-container { + display: flex; + flex-direction: column; + gap: 0.5rem; +} + +input[type='date'], +input[type='time'] { + border: 1px solid var(--date-picker-border); + padding: 0.5rem; + font-size: 1rem; + color: var(--date-picker-font-color); +} + +input[type='date']:disabled, +input[type='time']:disabled { + background-color: var(--date-picker-disabled-bg); +} \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/sveltekit/src/components/DatePicker/DatePicker.stories.ts b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/DatePicker/DatePicker.stories.ts new file mode 100644 index 0000000000..7699fce3b2 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/DatePicker/DatePicker.stories.ts @@ -0,0 +1,52 @@ +import DatePicker from './DatePicker.svelte'; +import type { Meta, StoryFn } from '@storybook/svelte'; + +const meta: Meta = { + title: 'component/Forms/DatePicker', + component: DatePicker, + tags: ['autodocs'], + argTypes: { + date: { control: 'date' }, + startDate: { control: 'date' }, + endDate: { control: 'date' }, + time: { control: 'text' }, + mode: { control: { type: 'select', options: ['single', 'range', 'time'] } }, + }, + parameters: { + layout: 'centered', + viewport: { + viewports: { + smallMobile: { name: 'Small Mobile', styles: { width: '320px', height: '568px' } }, + largeMobile: { name: 'Large Mobile', styles: { width: '414px', height: '896px' } }, + tablet: { name: 'Tablet', styles: { width: '768px', height: '1024px' } }, + desktop: { name: 'Desktop', styles: { width: '1024px', height: '768px' } }, + } + } + } +}; + +export default meta; + +const Template:StoryFn = (args) => ({ + Component: DatePicker, + props:args, +}); + +export const SingleDate = Template.bind({}); +SingleDate.args = { + date:'', + mode:'single', +}; + +export const DateRange = Template.bind({}); +DateRange.args = { + startDate:'', + endDate:'', + mode:'range', +}; + +export const TimePicker = Template.bind({}); +TimePicker.args = { + time:'', + mode:'time', +}; \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/sveltekit/src/components/DatePicker/DatePicker.svelte b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/DatePicker/DatePicker.svelte new file mode 100644 index 0000000000..3421f7505a --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/DatePicker/DatePicker.svelte @@ -0,0 +1,58 @@ + + +
    + {#if mode === 'single'} + + {:else if mode === 'range'} + + + {:else if mode === 'time'} + + {/if} +
    + + \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/sveltekit/src/components/DragAndDropFileArea/DragAndDropFileArea.css b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/DragAndDropFileArea/DragAndDropFileArea.css new file mode 100644 index 0000000000..91e0f8f430 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/DragAndDropFileArea/DragAndDropFileArea.css @@ -0,0 +1,31 @@ +:root { + --drop-area-border: #ccc; + --drop-area-hover-border: #007bff; + --drop-area-disabled-bg: #f5f5f5; + --error-color: #d9534f; +} + +.drop-area { + border: 2px dashed var(--drop-area-border); + padding: 1rem; + text-align: center; + transition: border-color 0.3s; + cursor: pointer; +} + +.drop-area.dragging { + border-color: var(--drop-area-hover-border); +} + +.drop-area[aria-disabled='true'] { + background-color: var(--drop-area-disabled-bg); + cursor: not-allowed; +} + +.file-input { + display: none; +} + +.error { + color: var(--error-color); +} \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/sveltekit/src/components/DragAndDropFileArea/DragAndDropFileArea.stories.ts b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/DragAndDropFileArea/DragAndDropFileArea.stories.ts new file mode 100644 index 0000000000..95bc96e6ab --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/DragAndDropFileArea/DragAndDropFileArea.stories.ts @@ -0,0 +1,88 @@ +import DragAndDropFileArea from './DragAndDropFileArea.svelte'; +import type { Meta, StoryFn } from '@storybook/svelte'; + +const meta: Meta = { + title: 'component/Forms/DragAndDropFileArea', + component: DragAndDropFileArea, + tags: ['autodocs'], + argTypes: { + disabled: { control: 'boolean' }, + multiple: { control: 'boolean' }, + acceptedTypes: { control: 'text' }, + errorMessage: { control: 'text' }, + }, + parameters: { + layout: 'centered', + viewport: { + viewports: { + smallMobile: { name: 'Small Mobile', styles: { width: '320px', height: '568px' } }, + largeMobile: { name: 'Large Mobile', styles: { width: '414px', height: '896px' } }, + tablet: { name: 'Tablet', styles: { width: '768px', height: '1024px' } }, + desktop: { name: 'Desktop', styles: { width: '1024px', height: '768px' } }, + } + } + } +}; + +export default meta; + +const Template:StoryFn = (args) => ({ + Component: DragAndDropFileArea, + props: args, +}); + +export const Default = Template.bind({}); +Default.args = { + disabled: false, + multiple: true, + acceptedTypes:'', + errorMessage:'', +}; + +export const Dragging = Template.bind({}); +Dragging.args = { + disabled: false, + multiple: true, + acceptedTypes:'', + errorMessage:'', +}; + +export const FileHover = Template.bind({}); +FileHover.args = { + disabled: false, + multiple: true, + acceptedTypes:'', + errorMessage:'', +}; + +export const FileDropped = Template.bind({}); +FileDropped.args = { + disabled: false, + multiple: true, + acceptedTypes:'', + errorMessage:'', +}; + +export const FileUploading = Template.bind({}); +FileUploading.args = { + disabled: true, + multiple: true, + acceptedTypes:'', + errorMessage:'', +}; + +export const Error = Template.bind({}); +Error.args = { + disabled: false, + multiple: true, + acceptedTypes:'', + errorMessage:'Invalid file type.', +}; + +export const Disabled = Template.bind({}); +Disabled.args = { + disabled: true, + multiple: true, + acceptedTypes:'', + errorMessage:'', +}; \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/sveltekit/src/components/DragAndDropFileArea/DragAndDropFileArea.svelte b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/DragAndDropFileArea/DragAndDropFileArea.svelte new file mode 100644 index 0000000000..9169151b69 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/DragAndDropFileArea/DragAndDropFileArea.svelte @@ -0,0 +1,73 @@ + + +
    + +

    {dragging ? 'Drop files here...' : 'Drag and drop files here or click to browse'}

    + {#if errorMessage} +

    {errorMessage}

    + {/if} +
    + + \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/sveltekit/src/components/EmbeddedMediaIframe/EmbeddedMediaIframe.css b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/EmbeddedMediaIframe/EmbeddedMediaIframe.css new file mode 100644 index 0000000000..cbd0a24848 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/EmbeddedMediaIframe/EmbeddedMediaIframe.css @@ -0,0 +1,27 @@ +.embedded-media-iframe { + position: relative; + width: 100%; + max-width: 800px; + margin: auto; +} + +iframe { + width: 100%; + height: 450px; + border: none; +} + +div[role="button"] { + position: absolute; + bottom: 10px; + right: 10px; + background-color: rgba(0, 0, 0, 0.5); + color: white; + padding: 8px; + cursor: pointer; + border-radius: 4px; +} + +div[role="button"]:focus { + outline: 2px solid #fff; +} \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/sveltekit/src/components/EmbeddedMediaIframe/EmbeddedMediaIframe.stories.ts b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/EmbeddedMediaIframe/EmbeddedMediaIframe.stories.ts new file mode 100644 index 0000000000..6230ed3707 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/EmbeddedMediaIframe/EmbeddedMediaIframe.stories.ts @@ -0,0 +1,47 @@ +import EmbeddedMediaIframe from './EmbeddedMediaIframe.svelte'; +import type { Meta, StoryFn } from '@storybook/svelte'; + +const meta: Meta = { + title: 'Components/Media/EmbeddedMediaIframe', + component: EmbeddedMediaIframe, + tags: ['autodocs'], + argTypes: { + src: { + control: { type: 'text' }, + }, + title: { + control: { type: 'text' }, + }, + allowFullscreen: { + control: { type: 'boolean' }, + }, + }, +}; + +export default meta; + +const Template: StoryFn = (args) => ({ + Component: EmbeddedMediaIframe, + props: args, +}); + +export const Default = Template.bind({}); +Default.args = { + src: 'https://www.youtube.com/embed/dQw4w9WgXcQ', + title: 'Default Media', + allowFullscreen: false, +}; + +export const Fullscreen = Template.bind({}); +Fullscreen.args = { + src: 'https://www.youtube.com/embed/dQw4w9WgXcQ', + title: 'Fullscreen Enabled Media', + allowFullscreen: true, +}; + +export const Buffering = Template.bind({}); +Buffering.args = { + src: 'https://www.youtube.com/embed/dQw4w9WgXcQ?autoplay=1', + title: 'Buffering Media', + allowFullscreen: false, +}; \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/sveltekit/src/components/EmbeddedMediaIframe/EmbeddedMediaIframe.svelte b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/EmbeddedMediaIframe/EmbeddedMediaIframe.svelte new file mode 100644 index 0000000000..b90e828212 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/EmbeddedMediaIframe/EmbeddedMediaIframe.svelte @@ -0,0 +1,38 @@ + + +
    + + {#if allowFullscreen} +
    + Toggle Fullscreen +
    + {/if} +
    + + \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/sveltekit/src/components/ExpandableList/ExpandableList.css b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/ExpandableList/ExpandableList.css new file mode 100644 index 0000000000..06ca3316c6 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/ExpandableList/ExpandableList.css @@ -0,0 +1,42 @@ +:root { + --list-border: #ccc; + --list-item-bg: #fff; + --list-item-hover-bg: #f0f0f0; + --list-item-selected-bg: #e0e0e0; + --list-item-expanded-bg: #f9f9f9; + --list-color: #333; +} + +.expandable-list { + list-style: none; + padding: 0; + margin: 0; +} + +.list-item { + border-bottom: 1px solid var(--list-border); + padding: 10px; + background-color: var(--list-item-bg); + cursor: pointer; +} + +.list-item:hover { + background-color: var(--list-item-hover-bg); +} + +.item-label { + font-weight: bold; +} + +.item-content { + margin-top: 5px; + padding-left: 15px; +} + +.expanded { + background-color: var(--list-item-expanded-bg) !important; +} + +.selected { + background-color: var(--list-item-selected-bg) !important; +} \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/sveltekit/src/components/ExpandableList/ExpandableList.stories.ts b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/ExpandableList/ExpandableList.stories.ts new file mode 100644 index 0000000000..84b8285db6 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/ExpandableList/ExpandableList.stories.ts @@ -0,0 +1,91 @@ +import ExpandableList from './ExpandableList.svelte'; +import type { Meta, StoryFn } from '@storybook/svelte'; +import { userEvent, within } from '@storybook/test'; + +const meta: Meta = { + title: 'component/Lists/ExpandableList', + component: ExpandableList, + tags: ['autodocs'], + argTypes: { + items: { control: 'object' } + }, + parameters: { + layout: 'centered', + viewport: { + viewports: { + smallMobile: { name: 'Small Mobile', styles: { width: '320px', height: '568px' } }, + largeMobile: { name: 'Large Mobile', styles: { width: '414px', height: '896px' } }, + tablet: { name: 'Tablet', styles: { width: '768px', height: '1024px' } }, + desktop: { name: 'Desktop', styles: { width: '1024px', height: '768px' } }, + } + } + } +}; + +export default meta; + +const Template:StoryFn = (args) => ({ + Component:ExpandableList, + props:args, +}); + +export const Default = Template.bind({}); +Default.args = { + items: [ + { id: '1', label: 'Item 1', content: 'Content of Item 1' }, + { id: '2', label: 'Item 2', content: 'Content of Item 2' }, + { id: '3', label: 'Item 3', content: 'Content of Item 3' } + ] +}; + +export const ItemExpanded = Template.bind({}); +ItemExpanded.args = { + items: [ + { id: '1', label: 'Item 1', content: 'Content of Item 1' }, + { id: '2', label: 'Item 2', content: 'Content of Item 2' }, + { id: '3', label: 'Item 3', content: 'Content of Item 3' } + ] +}; +ItemExpanded.play = async({canvasElement}) => { + const canvas = within(canvasElement); + const firstItem = await canvas.getByText('Item 1'); + await userEvent.click(firstItem); + +}; + +export const ItemCollapsed = Template.bind({}); +ItemCollapsed.args = { + items:[ + { id: '1', label: 'Item 1', content: 'Content of Item 1' }, + { id: '2', label: 'Item 2', content: 'Content of Item 2' }, + { id: '3', label: 'Item 3', content: 'Content of Item 3' }, + ], +}; + +export const Hover = Template.bind({}); +Hover.args = { + items: [ + { id: '1', label: 'Item 1', content: 'Content of Item 1' }, + { id: '2', label: 'Item 2', content: 'Content of Item 2' }, + { id: '3', label: 'Item 3', content: 'Content of Item 3' } + ] +} +Hover.play = async({canvasElement}) => { + const canvas = within(canvasElement); + const firstItem = canvas.getByText('Item 1') + await userEvent.hover(firstItem); +}; + +export const Selected = Template.bind({}); +Selected.args = { + items: [ + { id: '1', label: 'Item 1', content: 'Content of Item 1' }, + { id: '2', label: 'Item 2', content: 'Content of Item 2' }, + { id: '3', label: 'Item 3', content: 'Content of Item 3' } + ], +}; +Selected.play = async({canvasElement}) => { + const canvas = within(canvasElement); + const secondItem = await canvas.getByText('Item 2'); + await userEvent.click(secondItem); +}; \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/sveltekit/src/components/ExpandableList/ExpandableList.svelte b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/ExpandableList/ExpandableList.svelte new file mode 100644 index 0000000000..70bf2df687 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/ExpandableList/ExpandableList.svelte @@ -0,0 +1,42 @@ + + +
      + {#each items as item (item.id)} +
    • toggleExpand(item.id)} role='menuitem' on:keydown={(e) => { if (e.key === 'Enter' || e.key === ' ') { toggleExpand(item.id); }}} + tabindex="0"> +
      selectItem(item.id)} + on:keydown ={(e)=>{if(e.key === 'Enter' || e.key === ' '){selectItem(item.id)}}} + role='menuitem' + tabindex="0" + > + {item.label} +
      + {#if item.id === $expandedItemId} +
      {item.content}
      + {/if} +
    • + {/each} +
    + + \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/sveltekit/src/components/FavoritesList/FavoritesList.css b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/FavoritesList/FavoritesList.css new file mode 100644 index 0000000000..dae39ec589 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/FavoritesList/FavoritesList.css @@ -0,0 +1,45 @@ +:root { + --list-border: #ccc; + --list-item-bg: #fff; + --list-item-hover-bg: #f0f0f0; + --list-item-selected-bg: #e0e0e0; + --favorite-color: gold; + --non-favorite-color: #888; + --list-color: #333; +} + +.favorites-list { + list-style: none; + padding: 0; + margin: 0; +} + +.list-item { + border-bottom: 1px solid var(--list-border); + padding: 10px; + display: flex; + justify-content: space-between; + align-items: center; + background-color: var(--list-item-bg); + cursor: pointer; +} + +.list-item:hover { + background-color: var(--list-item-hover-bg); +} + +.favorite-toggle { + background: none; + border: none; + cursor: pointer; + font-size: 1.5em; + color: var(--non-favorite-color); +} + +.favorite-toggle:hover { + color: var(--favorite-color); +} + +.list-item.selected { + background-color: var(--list-item-selected-bg); +} \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/sveltekit/src/components/FavoritesList/FavoritesList.stories.ts b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/FavoritesList/FavoritesList.stories.ts new file mode 100644 index 0000000000..e98a227f6c --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/FavoritesList/FavoritesList.stories.ts @@ -0,0 +1,84 @@ +import { userEvent } from '@storybook/test'; +import { within } from '@storybook/test'; +import FavoritesList from './FavoritesList.svelte'; +import type { Meta, StoryFn } from '@storybook/svelte'; + +const meta: Meta = { + title: 'component/Lists/FavoritesList', + component: FavoritesList, + tags: ['autodocs'], + argTypes: { + items: { control: 'object' } + }, + parameters: { + layout: 'centered', + viewport: { + viewports: { + smallMobile: { name: 'Small Mobile', styles: { width: '320px', height: '568px' } }, + largeMobile: { name: 'Large Mobile', styles: { width: '414px', height: '896px' } }, + tablet: { name: 'Tablet', styles: { width: '768px', height: '1024px' } }, + desktop: { name: 'Desktop', styles: { width: '1024px', height: '768px' } }, + } + } + } +}; + +export default meta; + +const Template:StoryFn = (args) => ({ + Component:FavoritesList, + props:args, +}); + +export const Default = Template.bind({}); +Default.args = { + items: [ + { id: '1', label: 'Item 1', isFavorite: false }, + { id: '2', label: 'Item 2', isFavorite: true }, + { id: '3', label: 'Item 3', isFavorite: false }, + ] +}; + +export const Starred = Template.bind({}); +Starred.args = { + items: [ + { id: '1', label: 'Item 1', isFavorite: true }, + { id: '2', label: 'Item 2', isFavorite: true }, + { id: '3', label: 'Item 3', isFavorite: true }, + ] +}; + +export const Unstarred = Template.bind({}); +Unstarred.args = { + items: [ + { id: '1', label: 'Item 1', isFavorite: false }, + { id: '2', label: 'Item 2', isFavorite: false }, + { id: '3', label: 'Item 3', isFavorite: false } + ] +}; + +export const Hover = Template.bind({}); +Hover.args = { + items: [ + { id: '1', label: 'Item 1', isFavorite: false }, + { id: '2', label: 'Item 2', isFavorite: true }, + { id: '3', label: 'Item 3', isFavorite: false }, + ] +}; +Hover.play = async({canvasElement}) => { + const canvas = within(canvasElement); + await userEvent.hover(canvas.getByText('Item 1')); +}; + +export const Selected = Template.bind({}); +Selected.args = { + items: [ + { id: '1', label: 'Item 1', isFavorite: false }, + { id: '2', label: 'Item 2', isFavorite: true }, + { id: '3', label: 'Item 3', isFavorite: false }, + ] +}; +Selected.play = async({canvasElement}) => { + const canvas = within(canvasElement); + await userEvent.click(canvas.getByText('Item 2')); +}; \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/sveltekit/src/components/FavoritesList/FavoritesList.svelte b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/FavoritesList/FavoritesList.svelte new file mode 100644 index 0000000000..440a183a1c --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/FavoritesList/FavoritesList.svelte @@ -0,0 +1,41 @@ + + +
      + {#each items as item (item.id)} +
    • selectItem(item.id)} + on:keydown={(e)=>{if(e.key === 'Enter' || e.key === ' ') {selectItem(item.id)}}} + role="menuitem" + tabindex='0' + > + {item.label} + +
    • + {/each} +
    + + \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/sveltekit/src/components/FileInputWithPreview/FileInputWithPreview.css b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/FileInputWithPreview/FileInputWithPreview.css new file mode 100644 index 0000000000..6ff69691c5 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/FileInputWithPreview/FileInputWithPreview.css @@ -0,0 +1,37 @@ +.file-input-with-preview input[type="file"] { + margin: 5px; + padding: 8px; + border: 1px solid #ccc; + border-radius: 4px; + font-size: 14px; +} + +.file-input-with-preview .preview { + margin-top: 10px; + display: flex; + align-items: center; +} + +.file-input-with-preview .preview img { + max-width: 100px; + margin-right: 10px; +} + +.file-input-with-preview .preview button { + padding: 5px 10px; + background-color: #007bff; + color: #fff; + border: none; + border-radius: 4px; + cursor: pointer; +} + +.file-input-with-preview .preview button:disabled { + background-color: #e9ecef; + cursor: not-allowed; +} + +.file-input-with-preview .error { + color: red; + margin-top: 5px; +} \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/sveltekit/src/components/FileInputWithPreview/FileInputWithPreview.stories.ts b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/FileInputWithPreview/FileInputWithPreview.stories.ts new file mode 100644 index 0000000000..896f59c14a --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/FileInputWithPreview/FileInputWithPreview.stories.ts @@ -0,0 +1,35 @@ +import FileInputWithPreview from './FileInputWithPreview.svelte'; + +export default { + title: 'Forms/FileInputWithPreview', + component: FileInputWithPreview, + tags: ['autodocs'], +}; + +export const FileUploaded = { + args: { + error: '', + disabled: false, + }, +}; + +export const PreviewDisplayed = { + args: { + error: '', + disabled: false, + }, +}; + +export const Error = { + args: { + error: 'File format not supported', + disabled: false, + }, +}; + +export const Disabled = { + args: { + error: '', + disabled: true, + }, +}; \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/sveltekit/src/components/FileInputWithPreview/FileInputWithPreview.svelte b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/FileInputWithPreview/FileInputWithPreview.svelte new file mode 100644 index 0000000000..3ed8db5f32 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/FileInputWithPreview/FileInputWithPreview.svelte @@ -0,0 +1,89 @@ + + +
    + + {#if previewUrl} +
    + File preview + +
    + {/if} + {#if error} +
    {error}
    + {/if} +
    + + \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/sveltekit/src/components/FileUpload/FileUpload.css b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/FileUpload/FileUpload.css new file mode 100644 index 0000000000..2a7730852f --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/FileUpload/FileUpload.css @@ -0,0 +1,38 @@ +:root { + --progress-bar-bg: #f3f3f3; + --progress-bar-fill: #4caf50; + --drop-area-border: #ccc; + --drop-area-hover-border: #007bff; +} + +.file-upload { + border: 1px solid var(--drop-area-border); + padding: 1rem; + text-align: center; + cursor: pointer; + transition: border-color 0.3s; +} + +.drop-area { + padding: 1rem; + border: 2px dashed var(--drop-area-border); + margin-bottom: 1rem; + transition: border-color 0.3s; +} + +.file-input { + display: none; +} + +.progress-bar { + background-color: var(--progress-bar-bg); + border-radius: 5px; + overflow: hidden; + margin-top: 1rem; +} + +.progress { + height: 10px; + background-color: var(--progress-bar-fill); + transition: width 0.3s; +} \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/sveltekit/src/components/FileUpload/FileUpload.stories.ts b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/FileUpload/FileUpload.stories.ts new file mode 100644 index 0000000000..2cff233152 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/FileUpload/FileUpload.stories.ts @@ -0,0 +1,108 @@ +import FileUpload from './FileUpload.svelte'; +import type { Meta, StoryFn } from '@storybook/svelte'; + +const meta: Meta = { + title: 'component/Forms/FileUpload', + component: FileUpload, + tags: ['autodocs'], + argTypes: { + multiple: { control: 'boolean' }, + uploadProgress: { control: 'number', min: 0, max: 100 }, + isDragAndDrop: { control: 'boolean' }, + }, + parameters: { + layout: 'centered', + viewport: { + viewports: { + smallMobile: { name: 'Small Mobile', styles: { width: '320px', height: '568px' } }, + largeMobile: { name: 'Large Mobile', styles: { width: '414px', height: '896px' } }, + tablet: { name: 'Tablet', styles: { width: '768px', height: '1024px' } }, + desktop: { name: 'Desktop', styles: { width: '1024px', height: '768px' } }, + } + } + } +}; + +export default meta; + +const Template:StoryFn = (args) => ({ + Component:FileUpload, + props:args, +}); + +export const Default = Template.bind({}); +Default.args = { + multiple: false, + uploadProgress: 0, + isDragAndDrop: false, +}; + +export const SingleFile = Template.bind({}); +SingleFile.args = { + multiple: false, + uploadProgress: 0, + isDragAndDrop: false, +}; + +export const MultipleFiles = Template.bind({}); +MultipleFiles.args = { + multiple: true, + uploadProgress: 0, + isDragAndDrop: false, +}; + +export const DragAndDrop = Template.bind({}); +DragAndDrop.args = { + multiple: false, + uploadProgress: 0, + isDragAndDrop: true, +}; + +export const Progress = Template.bind({}); +Progress.args = { + multiple: false, + uploadProgress: 50, + isDragAndDrop: false, +}; + +// type Story = StoryObj; + +// export const Default: Story = { +// args: { +// multiple: false, +// uploadProgress: 0, +// isDragAndDrop: false, +// } +// }; + +// export const SingleFile: Story = { +// args: { +// multiple: false, +// uploadProgress: 0, +// isDragAndDrop: false, +// } +// }; + +// export const MultipleFiles: Story = { +// args: { +// multiple: true, +// uploadProgress: 0, +// isDragAndDrop: false, +// } +// }; + +// export const DragAndDrop: Story = { +// args: { +// multiple: false, +// uploadProgress: 0, +// isDragAndDrop: true, +// } +// }; + +// export const Progress: Story = { +// args: { +// multiple: false, +// uploadProgress: 50, +// isDragAndDrop: false, +// } +// }; \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/sveltekit/src/components/FileUpload/FileUpload.svelte b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/FileUpload/FileUpload.svelte new file mode 100644 index 0000000000..08f027ff4e --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/FileUpload/FileUpload.svelte @@ -0,0 +1,52 @@ + + +
    + {#if isDragAndDrop} +
    + Drag and drop files here or click to browse +
    + {/if} + + {#if uploadProgress > 0} +
    +
    +
    + {/if} +
    + + \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/sveltekit/src/components/FilterableList/FilterableList.css b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/FilterableList/FilterableList.css new file mode 100644 index 0000000000..4c6324fe1b --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/FilterableList/FilterableList.css @@ -0,0 +1,63 @@ +:root { + --input-border: #ccc; + --input-focus-border: #007bff; + --button-bg: #007bff; + --button-color: #fff; + --button-hover-bg: #0056b3; + --list-border: #ccc; + --list-item-bg: #fff; + --list-color: #333; +} + +.filterable-list { + display: flex; + flex-direction: column; + gap: 10px; +} + +input[type="text"] { + padding: 8px; + border: 1px solid var(--input-border); + border-radius: 4px; + outline: none; +} + +input[type="text"]:focus { + border-color: var(--input-focus-border); +} + +button { + padding: 8px 12px; + background-color: var(--button-bg); + color: var(--button-color); + border: none; + border-radius: 4px; + cursor: pointer; +} + +button:hover { + background-color: var(--button-hover-bg); +} + +ul { + list-style: none; + padding: 0; + margin: 0; + border: 1px solid var(--list-border); + border-radius: 4px; +} + +li { + padding: 10px; + border-bottom: 1px solid var(--list-border); + background-color: var(--list-item-bg); +} + +li:last-child { + border-bottom: none; +} + +li.no-results { + text-align: center; + color: var(--list-color); +} \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/sveltekit/src/components/FilterableList/FilterableList.stories.ts b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/FilterableList/FilterableList.stories.ts new file mode 100644 index 0000000000..f602172a4c --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/FilterableList/FilterableList.stories.ts @@ -0,0 +1,67 @@ +import FilterableList from './FilterableList.svelte'; +import type { Meta, StoryFn } from '@storybook/svelte'; +import { userEvent, within } from '@storybook/test' + +const meta: Meta = { + title: 'component/Lists/FilterableList', + component: FilterableList, + tags: ['autodocs'], + argTypes: { + items: { control: 'object' } + }, + parameters: { + layout: 'centered', + viewport: { + viewports: { + smallMobile: { name: 'Small Mobile', styles: { width: '320px', height: '568px' } }, + largeMobile: { name: 'Large Mobile', styles: { width: '414px', height: '896px' } }, + tablet: { name: 'Tablet', styles: { width: '768px', height: '1024px' } }, + desktop: { name: 'Desktop', styles: { width: '1024px', height: '768px' } }, + } + } + } +}; + +export default meta; + +const Template:StoryFn = (args) => ({ + Component:FilterableList, + props:args, +}); + +export const Default = Template.bind({}); +Default.args = { + items: ['Apple', 'Banana', 'Cherry', 'Date', 'Elderberry'], +}; + +export const FilterApplied = Template.bind({}); +FilterApplied.args = { + items: ['Apple', 'Banana', 'Cherry', 'Date', 'Elderberry'], +}; +FilterApplied.play = async({canvasElement}) => { + const canvas = within(canvasElement); + const input = canvas.getByPlaceholderText('Filter items...'); + await userEvent.type(input, 'Ba'); +}; + +export const NoResults = Template.bind({}); +NoResults.args = { + items: ['Apple', 'Banana', 'Cherry', 'Date', 'Elderberry'], +}; +NoResults.play = async({canvasElement}) => { + const canvas = within(canvasElement); + const input = canvas.getByPlaceholderText('Filter items...'); + await userEvent.type(input, 'Zucchini'); +}; + +export const ClearFilter = Template.bind({}); +ClearFilter.args = { + items: ['Apple', 'Banana', 'Cherry', 'Date', 'Elderberry'], +}; +ClearFilter.play = async({canvasElement}) => { + const canvas = within(canvasElement); + const input = canvas.getByPlaceholderText('Filter items...'); + await userEvent.type(input, 'Ch'); + const clearButton = canvas.getByRole('button', { name: 'Clear filter' }); + await userEvent.click(clearButton); +}; \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/sveltekit/src/components/FilterableList/FilterableList.svelte b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/FilterableList/FilterableList.svelte new file mode 100644 index 0000000000..e60bd6e9b6 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/FilterableList/FilterableList.svelte @@ -0,0 +1,42 @@ + + +
    + + +
      + {#if $filteredItems.length > 0} + {#each $filteredItems as item (item)} +
    • {item}
    • + {/each} + {:else} +
    • No results found
    • + {/if} +
    +
    + + \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/sveltekit/src/components/GroupedList/GroupedList.css b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/GroupedList/GroupedList.css new file mode 100644 index 0000000000..ce4a6ae2c0 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/GroupedList/GroupedList.css @@ -0,0 +1,55 @@ +:root { + --group-button-bg: #f0f0f0; + --group-button-hover-bg: #e0e0e0; + --group-border: #ccc; + --item-bg: #fff; + --item-hover-bg: #f8f8f8; + --item-selected-bg: #d0eaff; + --item-color: #333; +} + +.grouped-list { + display: flex; + flex-direction: column; + gap: 10px; +} + +.group { + border: 1px solid var(--group-border); + border-radius: 4px; + overflow: hidden; +} + +button { + width: 100%; + padding: 10px; + background-color: var(--group-button-bg); + border: none; + cursor: pointer; + text-align: left; +} + +button:hover { + background-color: var(--group-button-hover-bg); +} + +ul { + list-style: none; + padding: 0; + margin: 0; +} + +li { + padding: 10px; + background-color: var(--item-bg); + cursor: pointer; +} + +li:hover { + background-color: var(--item-hover-bg); +} + +li.selected { + background-color: var(--item-selected-bg); + color: var(--item-color); +} \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/sveltekit/src/components/GroupedList/GroupedList.stories.ts b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/GroupedList/GroupedList.stories.ts new file mode 100644 index 0000000000..d39ea6a5d7 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/GroupedList/GroupedList.stories.ts @@ -0,0 +1,80 @@ +import GroupedList from './GroupedList.svelte'; +import type { Meta, StoryFn } from '@storybook/svelte'; +import {userEvent, within} from '@storybook/test'; + +const meta: Meta = { + title: 'component/Lists/GroupedList', + component: GroupedList, + tags: ['autodocs'], + argTypes: { + groups: { control: 'object' } + }, + parameters: { + layout: 'centered', + viewport: { + viewports: { + smallMobile: { name: 'Small Mobile', styles: { width: '320px', height: '568px' } }, + largeMobile: { name: 'Large Mobile', styles: { width: '414px', height: '896px' } }, + tablet: { name: 'Tablet', styles: { width: '768px', height: '1024px' } }, + desktop: { name: 'Desktop', styles: { width: '1024px', height: '768px' } }, + } + } + } +}; + +export default meta; + +const Template:StoryFn = (args) => ({ + Component: GroupedList, + props:args, +}); + +export const Default = Template.bind({}); +Default.args = { + groups: [ + { title: 'Fruits', items: ['Apple', 'Banana', 'Cherry'], expanded: true }, + { title: 'Vegetables', items: ['Carrot', 'Lettuce', 'Peas'], expanded: false }, + ] +}; + +export const GroupExpanded = Template.bind({}); +GroupExpanded.args = { + groups: [ + { title: 'Fruits', items: ['Apple', 'Banana', 'Cherry'], expanded: true }, + { title: 'Vegetables', items: ['Carrot', 'Lettuce', 'Peas'], expanded: false }, + ] +}; + +export const GroupCollapsed = Template.bind({}); +GroupCollapsed.args = { + groups: [ + { title: 'Fruits', items: ['Apple', 'Banana', 'Cherry'], expanded: false }, + { title: 'Vegetables', items: ['Carrot', 'Lettuce', 'Peas'], expanded: false }, + ] +}; + +export const ItemHover = Template.bind({}); +ItemHover.args = { + groups: [ + { title: 'Fruits', items: ['Apple', 'Banana', 'Cherry'], expanded: true }, + { title: 'Vegetables', items: ['Carrot', 'Lettuce', 'Peas'], expanded: false }, + ] +}; +ItemHover.play = async({canvasElement}) => { + const canvas = within(canvasElement); + const listItem = canvas.getByText('Banana'); + await userEvent.hover(listItem); +}; + +export const ItemSelected = Template.bind({}); +ItemSelected.args = { + groups: [ + { title: 'Fruits', items: ['Apple', 'Banana', 'Cherry'], expanded: true }, + { title: 'Vegetables', items: ['Carrot', 'Lettuce', 'Peas'], expanded: false }, + ] +}; +ItemSelected.play = async({canvasElement}) => { + const canvas = within(canvasElement); + const listItem = canvas.getByText('Banana'); + await userEvent.click(listItem); +}; \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/sveltekit/src/components/GroupedList/GroupedList.svelte b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/GroupedList/GroupedList.svelte new file mode 100644 index 0000000000..e7fc3e38a4 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/GroupedList/GroupedList.svelte @@ -0,0 +1,49 @@ + + +
    + {#each groups as group (group.title)} +
    + + {#if group.expanded} +
      + {#each group.items as item (item)} +
    • selectItem(item)} + on:keydown={(e)=>{if(e.key === 'Enter' || e.key === ' '){selectItem(item)}}} + on:mouseover={() => selectItem(item)} + on:focus= {()=> selectItem(item)} + role='menuitem'> + {item} +
    • + {/each} +
    + {/if} +
    + {/each} +
    + + \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/sveltekit/src/components/IconButton/IconButton.css b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/IconButton/IconButton.css new file mode 100644 index 0000000000..41066adf94 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/IconButton/IconButton.css @@ -0,0 +1,34 @@ +:root { + --icon-button-bg: transparent; + --icon-button-hover-bg: rgba(0, 0, 0, 0.1); + --icon-button-active-bg: rgba(0, 0, 0, 0.2); + --icon-button-disabled-bg: rgba(0, 0, 0, 0.05); + --icon-size: 24px; +} + +.icon-button { + background-color: var(--icon-button-bg); + border: none; + cursor: pointer; + padding: 0.5rem; + border-radius: 50%; + transition: background-color 0.3s; +} + +.icon-button img { + width: var(--icon-size); + height: var(--icon-size); +} + +.icon-button:hover:not(:disabled) { + background-color: var(--icon-button-hover-bg); +} + +.icon-button:active:not(:disabled) { + background-color: var(--icon-button-active-bg); +} + +.icon-button:disabled { + background-color: var(--icon-button-disabled-bg); + cursor: not-allowed; +} \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/sveltekit/src/components/IconButton/IconButton.stories.ts b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/IconButton/IconButton.stories.ts new file mode 100644 index 0000000000..b9dbe8c886 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/IconButton/IconButton.stories.ts @@ -0,0 +1,57 @@ +import IconButton from './IconButton.svelte'; +import type { Meta, StoryFn } from '@storybook/svelte'; + +const meta: Meta = { + title: 'component/Buttons/IconButton', + component: IconButton, + tags: ['autodocs'], + argTypes: { + icon: { control: 'text' }, + disabled: { control: 'boolean' }, + onClick: { action: 'clicked' }, + }, + parameters: { + layout: 'centered', + viewport: { + viewports: { + smallMobile: { name: 'Small Mobile', styles: { width: '320px', height: '568px' } }, + largeMobile: { name: 'Large Mobile', styles: { width: '414px', height: '896px' } }, + tablet: { name: 'Tablet', styles: { width: '768px', height: '1024px' } }, + desktop: { name: 'Desktop', styles: { width: '1024px', height: '768px' } }, + } + } + } +}; + +export default meta; + +const Template:StoryFn = (args) => ({ + Component:IconButton, + props:args, +}); + +export const Default = Template.bind({}); +Default.args = { + icon: 'path/to/icon.svg', + disabled: false, + ariaLabel: 'Default Icon Button', +}; + +export const Active = Template.bind({}); +Active.args = { + icon: 'path/to/icon.svg', + ariaLabel: 'Active Icon Button', +}; + +export const Hover = Template.bind({}); +Hover.args = { + icon: 'path/to/icon.svg', + ariaLabel: 'Hover Icon Button', +}; + +export const Disabled = Template.bind({}); +Disabled.args = { + icon: 'path/to/icon.svg', + disabled: true, + ariaLabel: 'Disabled Icon Button', +}; \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/sveltekit/src/components/IconButton/IconButton.svelte b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/IconButton/IconButton.svelte new file mode 100644 index 0000000000..8614db4e4e --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/IconButton/IconButton.svelte @@ -0,0 +1,19 @@ + + + + + \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/sveltekit/src/components/ImageSlider/ImageSlider.css b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/ImageSlider/ImageSlider.css new file mode 100644 index 0000000000..b4c3097e6c --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/ImageSlider/ImageSlider.css @@ -0,0 +1,35 @@ +.image-slider { + position: relative; + width: 100%; + max-width: 800px; + margin: auto; + outline: none; +} + +.slider-image { + width: 100%; + height: auto; + display: block; +} + +.controls { + position: absolute; + top: 50%; + width: 100%; + display: flex; + justify-content: space-between; + transform: translateY(-50%); +} + +button { + background-color: rgba(0, 0, 0, 0.5); + color: white; + border: none; + padding: 10px; + cursor: pointer; + border-radius: 4px; +} + +button:hover { + background-color: rgba(0, 0, 0, 0.7); +} \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/sveltekit/src/components/ImageSlider/ImageSlider.stories.ts b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/ImageSlider/ImageSlider.stories.ts new file mode 100644 index 0000000000..e2f05e0baf --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/ImageSlider/ImageSlider.stories.ts @@ -0,0 +1,53 @@ +import ImageSlider from './ImageSlider.svelte'; +import type { Meta, StoryFn } from '@storybook/svelte'; + +const meta: Meta = { + title: 'Components/Media/ImageSlider', + component: ImageSlider, + tags: ['autodocs'], + argTypes: { + images: { + control: { type: 'object' }, + }, + activeIndex: { + control: { type: 'number' }, + }, + }, +}; + +export default meta; + +const Template: StoryFn = (args) => ({ + Component: ImageSlider, + props: args, +}); + +export const Default = Template.bind({}); +Default.args = { + images: [ + 'https://via.placeholder.com/800x450?text=Image+1', + 'https://via.placeholder.com/800x450?text=Image+2', + 'https://via.placeholder.com/800x450?text=Image+3', + ], + activeIndex: 0, +}; + +export const Active = Template.bind({}); +Active.args = { + images: [ + 'https://via.placeholder.com/800x450?text=Image+1', + 'https://via.placeholder.com/800x450?text=Image+2', + 'https://via.placeholder.com/800x450?text=Image+3', + ], + activeIndex: 1, +}; + +export const Hover = Template.bind({}); +Hover.args = { + images: [ + 'https://via.placeholder.com/800x450?text=Image+1', + 'https://via.placeholder.com/800x450?text=Image+2', + 'https://via.placeholder.com/800x450?text=Image+3', + ], + activeIndex: 0, +}; \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/sveltekit/src/components/ImageSlider/ImageSlider.svelte b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/ImageSlider/ImageSlider.svelte new file mode 100644 index 0000000000..ba71f93034 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/ImageSlider/ImageSlider.svelte @@ -0,0 +1,34 @@ + + + + + \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/sveltekit/src/components/InteractivePollResults/InteractivePollResults.css b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/InteractivePollResults/InteractivePollResults.css new file mode 100644 index 0000000000..b71fffe470 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/InteractivePollResults/InteractivePollResults.css @@ -0,0 +1,55 @@ +:root { + --poll-bar-background: #4caf50; + --poll-option-background: #f1f1f1; + --poll-label-color: #333333; + --total-votes-color: #666666; + --poll-font-size: 16px; + --poll-bar-height: 24px; + --poll-bar-border-radius: 4px; + --poll-padding: 8px; +} + +.poll-results { + display: flex; + flex-direction: column; + gap: 8px; + padding: var(--poll-padding); +} + +.poll-option { + display: flex; + align-items: center; + gap: 8px; + background-color: var(--poll-option-background); + padding: var(--poll-padding); + border-radius: var(--poll-bar-border-radius); +} + +.poll-label { + flex: 1; + color: var(--poll-label-color); + font-size: var(--poll-font-size); +} + +.poll-bar-container { + flex: 2; + background-color: #e0e0e0; + border-radius: var(--poll-bar-border-radius); + overflow: hidden; +} + +.poll-bar { + height: var(--poll-bar-height); + background-color: var(--poll-bar-background); +} + +.poll-votes { + font-size: var(--poll-font-size); + color: var(--poll-label-color); +} + +.total-votes { + margin-top: 8px; + font-size: var(--poll-font-size); + color: var(--total-votes-color); +} \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/sveltekit/src/components/InteractivePollResults/InteractivePollResults.stories.ts b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/InteractivePollResults/InteractivePollResults.stories.ts new file mode 100644 index 0000000000..7bb239471b --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/InteractivePollResults/InteractivePollResults.stories.ts @@ -0,0 +1,67 @@ +import InteractivePollResults from './InteractivePollResults.svelte'; +import type { Meta, StoryFn } from '@storybook/svelte'; + +const meta: Meta = { + title: 'component/Indicators/InteractivePollResults', + component: InteractivePollResults, + tags: ['autodocs'], + argTypes: { + options: { control: 'object' }, + totalVotes: { control: 'number' }, + }, + parameters: { + layout: 'centered', + viewport: { + viewports: { + smallMobile: { name: 'Small Mobile', styles: { width: '320px', height: '568px' } }, + largeMobile: { name: 'Large Mobile', styles: { width: '414px', height: '896px' } }, + tablet: { name: 'Tablet', styles: { width: '768px', height: '1024px' } }, + desktop: { name: 'Desktop', styles: { width: '1024px', height: '768px' } }, + } + } + } +}; + +export default meta; + +const Template:StoryFn = (args) => ({ + Component: InteractivePollResults, + props:args, +}); + +export const Default = Template.bind({}); +Default.args = { + options: [ + { label: 'Option B', votes: 30 }, + { label: 'Option A', votes: 50 }, + ], + totalVotes: 80, +}; + + +export const LiveResults = Template.bind({}); +LiveResults.args = { + options: [ + { label: 'Option B', votes: 70 }, + { label: 'Option A', votes: 30 }, + ], + totalVotes: 100, +}; + +export const Completed = Template.bind({}); +Completed.args = { + options: [ + { label: 'Option B', votes: 120 }, + { label: 'Option A', votes: 80 }, + ], + totalVotes: 200, +}; + +export const Closed = Template.bind({}); +Closed.args = { + options: [ + { label: 'Option B', votes: 50 }, + { label: 'Option A', votes: 50 }, + ], + totalVotes: 100, +}; \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/sveltekit/src/components/InteractivePollResults/InteractivePollResults.svelte b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/InteractivePollResults/InteractivePollResults.svelte new file mode 100644 index 0000000000..dacd491e34 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/InteractivePollResults/InteractivePollResults.svelte @@ -0,0 +1,35 @@ + + +
    + {#each options as { label, votes }} +
    + {label} +
    +
    +
    + {votes} votes +
    + {/each} +
    Total Votes: {totalVotes}
    +
    + + \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/sveltekit/src/components/LoadingBarsWithSteps/LoadingBarsWithSteps.css b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/LoadingBarsWithSteps/LoadingBarsWithSteps.css new file mode 100644 index 0000000000..ae8854d45c --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/LoadingBarsWithSteps/LoadingBarsWithSteps.css @@ -0,0 +1,45 @@ +:root { + --step-active-background: #4caf50; + --step-completed-background: #8bc34a; + --step-inactive-background: #e0e0e0; + --step-label-color: #333333; + --step-font-size: 16px; + --loading-bar-height: 8px; + --step-padding: 8px; + --step-bar-border-radius: 4px; +} + +.loading-bars-with-steps { + display: flex; + flex-direction: column; + gap: 16px; + padding: var(--step-padding); +} + +.step { + display: flex; + flex-direction: column; + gap: 4px; +} + +.step-label { + font-size: var(--step-font-size); + color: var(--step-label-color); +} + +.loading-bar { + height: var(--loading-bar-height); + border-radius: var(--step-bar-border-radius); +} + +.step.active .loading-bar { + background-color: var(--step-active-background); +} + +.step.completed .loading-bar { + background-color: var(--step-completed-background); +} + +.step.inactive .loading-bar { + background-color: var(--step-inactive-background); +} \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/sveltekit/src/components/LoadingBarsWithSteps/LoadingBarsWithSteps.stories.ts b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/LoadingBarsWithSteps/LoadingBarsWithSteps.stories.ts new file mode 100644 index 0000000000..57b5305e27 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/LoadingBarsWithSteps/LoadingBarsWithSteps.stories.ts @@ -0,0 +1,65 @@ +import LoadingBarsWithSteps from './LoadingBarsWithSteps.svelte'; +import type { Meta, StoryFn } from '@storybook/svelte'; + +const meta: Meta = { + title: 'component/Indicators/LoadingBarsWithSteps', + component: LoadingBarsWithSteps, + tags: ['autodocs'], + argTypes: { + steps: { control: 'object' }, + }, + parameters: { + layout: 'centered', + viewport: { + viewports: { + smallMobile: { name: 'Small Mobile', styles: { width: '320px', height: '568px' } }, + largeMobile: { name: 'Large Mobile', styles: { width: '414px', height: '896px' } }, + tablet: { name: 'Tablet', styles: { width: '768px', height: '1024px' } }, + desktop: { name: 'Desktop', styles: { width: '1024px', height: '768px' } }, + } + } + } +}; + +export default meta; + +const Template:StoryFn = (args) => ({ + Component:LoadingBarsWithSteps, + props:args, +}); + +export const Default = Template.bind({}); +Default.args = { + steps: [ + { label: 'Step 1', status: 'active' }, + { label: 'Step 2', status: 'inactive' }, + { label: 'Step 3', status: 'inactive' }, + ], +}; + +export const StepActive = Template.bind({}); +StepActive.args = { + steps: [ + { label: 'Step 1', status: 'completed' }, + { label: 'Step 2', status: 'active' }, + { label: 'Step 3', status: 'inactive' }, + ], +}; + +export const StepCompleted = Template.bind({}); +StepCompleted.args = { + steps: [ + { label: 'Step 1', status: 'completed' }, + { label: 'Step 2', status: 'completed' }, + { label: 'Step 3', status: 'active' }, + ], +}; + +export const StepInActive = Template.bind({}); +StepInActive.args = { + steps: [ + { label: 'Step 1', status: 'completed' }, + { label: 'Step 2', status: 'completed' }, + { label: 'Step 3', status: 'inactive' }, + ], +}; \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/sveltekit/src/components/LoadingBarsWithSteps/LoadingBarsWithSteps.svelte b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/LoadingBarsWithSteps/LoadingBarsWithSteps.svelte new file mode 100644 index 0000000000..fca853daf3 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/LoadingBarsWithSteps/LoadingBarsWithSteps.svelte @@ -0,0 +1,22 @@ + + +
    + {#each steps as { label, status }} +
    +
    {label}
    +
    +
    + {/each} +
    + + \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/sveltekit/src/components/LoadingSpinner/LoadingSpinner.css b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/LoadingSpinner/LoadingSpinner.css new file mode 100644 index 0000000000..4546066703 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/LoadingSpinner/LoadingSpinner.css @@ -0,0 +1,28 @@ +:root { + --spinner-size: 50px; + --spinner-border-width: 5px; + --spinner-color: #3498db; + --spinner-background-color: #f3f3f3; +} + +.loading-spinner { + display: inline-flex; + align-items: center; + justify-content: center; + width: var(--spinner-size); + height: var(--spinner-size); +} + +.spinner { + border: var(--spinner-border-width) solid var(--spinner-background-color); + border-top: var(--spinner-border-width) solid var(--spinner-color); + border-radius: 50%; + width: 100%; + height: 100%; + animation: spin 1s linear infinite; +} + +@keyframes spin { + 0% { transform: rotate(0deg); } + 100% { transform: rotate(360deg); } +} \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/sveltekit/src/components/LoadingSpinner/LoadingSpinner.stories.ts b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/LoadingSpinner/LoadingSpinner.stories.ts new file mode 100644 index 0000000000..0d0e834b33 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/LoadingSpinner/LoadingSpinner.stories.ts @@ -0,0 +1,44 @@ +import LoadingSpinner from './LoadingSpinner.svelte'; +import type { Meta, StoryFn } from '@storybook/svelte'; + +const meta: Meta = { + title: 'component/Indicators/LoadingSpinner', + component: LoadingSpinner, + tags: ['autodocs'], + argTypes: { + active: { control: 'boolean' }, + }, + parameters: { + layout: 'centered', + viewport: { + viewports: { + smallMobile: { name: 'Small Mobile', styles: { width: '320px', height: '568px' } }, + largeMobile: { name: 'Large Mobile', styles: { width: '414px', height: '896px' } }, + tablet: { name: 'Tablet', styles: { width: '768px', height: '1024px' } }, + desktop: { name: 'Desktop', styles: { width: '1024px', height: '768px' } }, + } + } + } +}; + +export default meta; + +const Template:StoryFn = (args) => ({ + Component: LoadingSpinner, + props:args, +}); + +export const Default = Template.bind({}); +Default.args = { + active:true, +}; + +export const Active = Template.bind({}); +Active.args = { + active:true, +}; + +export const Inactive = Template.bind({}); +Inactive.args = { + active:false, +}; \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/sveltekit/src/components/LoadingSpinner/LoadingSpinner.svelte b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/LoadingSpinner/LoadingSpinner.svelte new file mode 100644 index 0000000000..821b593271 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/LoadingSpinner/LoadingSpinner.svelte @@ -0,0 +1,14 @@ + + +
    + {#if active} +
    + {/if} +
    + + \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/sveltekit/src/components/LoadmorebuttoninList/LoadmorebuttoninList.css b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/LoadmorebuttoninList/LoadmorebuttoninList.css new file mode 100644 index 0000000000..75ad984990 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/LoadmorebuttoninList/LoadmorebuttoninList.css @@ -0,0 +1,57 @@ +:root { + --button-bg: #007bff; + --button-hover-bg: #0056b3; + --button-disabled-bg: #c0c0c0; + --text-color: #fff; + --list-border: #ccc; + --end-message-color: #888; +} + +.list-container { + display: flex; + flex-direction: column; + gap: 10px; + max-width: 300px; + margin: 0 auto; +} + +ul { + list-style: none; + padding: 0; + margin: 0; + border: 1px solid var(--list-border); + border-radius: 4px; +} + +li { + padding: 10px; + border-bottom: 1px solid var(--list-border); +} + +li:last-child { + border-bottom: none; +} + +button { + padding: 10px; + background-color: var(--button-bg); + color: var(--text-color); + border: none; + border-radius: 4px; + cursor: pointer; + transition: background-color 0.2s; +} + +button:hover { + background-color: var(--button-hover-bg); +} + +button:disabled { + background-color: var(--button-disabled-bg); + cursor: not-allowed; +} + +.end-message { + color: var(--end-message-color); + text-align: center; +} \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/sveltekit/src/components/LoadmorebuttoninList/LoadmorebuttoninList.stories.ts b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/LoadmorebuttoninList/LoadmorebuttoninList.stories.ts new file mode 100644 index 0000000000..a53bd716a3 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/LoadmorebuttoninList/LoadmorebuttoninList.stories.ts @@ -0,0 +1,53 @@ +import LoadMoreButtonInList from './LoadmorebuttoninList.svelte' +import type { Meta, StoryFn } from '@storybook/svelte'; + +const meta: Meta = { + title: 'component/Lists/LoadMoreButtonInList', + component: LoadMoreButtonInList, + tags: ['autodocs'], + argTypes: { + items: { control: 'object' }, + loadMore: { action: 'loadMore' }, + isLoading: { control: 'boolean' }, + isEndOfList: { control: 'boolean' } + }, + parameters: { + layout: 'centered', + viewport: { + viewports: { + smallMobile: { name: 'Small Mobile', styles: { width: '320px', height: '568px' } }, + largeMobile: { name: 'Large Mobile', styles: { width: '414px', height: '896px' } }, + tablet: { name: 'Tablet', styles: { width: '768px', height: '1024px' } }, + desktop: { name: 'Desktop', styles: { width: '1024px', height: '768px' } }, + } + } + } +}; + +export default meta; + +const Template:StoryFn = (args) => ({ + Component: LoadMoreButtonInList, + props: args, +}); + +export const Default = Template.bind({}); +Default.args = { + items: ['Item 1', 'Item 2', 'Item 3'], + isLoading: false, + isEndOfList: false +}; + +export const Loading = Template.bind({}); +Loading.args = { + items: ['Item 1', 'Item 2', 'Item 3'], + isLoading: true, + isEndOfList: false +}; + +export const EndOfList = Template.bind({}); +EndOfList.args = { + items: ['Item 1', 'Item 2', 'Item 3'], + isLoading: false, + isEndOfList: true +}; \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/sveltekit/src/components/LoadmorebuttoninList/LoadmorebuttoninList.svelte b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/LoadmorebuttoninList/LoadmorebuttoninList.svelte new file mode 100644 index 0000000000..4892e370d0 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/LoadmorebuttoninList/LoadmorebuttoninList.svelte @@ -0,0 +1,41 @@ + + +
    +
      + {#each items as item (item)} +
    • {item}
    • + {/each} +
    + {#if !isEndOfList} + + {:else} +

    End of List

    + {/if} +
    + + \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/sveltekit/src/components/MultiselectList/MultiselectList.css b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/MultiselectList/MultiselectList.css new file mode 100644 index 0000000000..d38430eb66 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/MultiselectList/MultiselectList.css @@ -0,0 +1,43 @@ +:root { + --item-bg: #fff; + --item-hover-bg: #f0f0f0; + --item-selected-bg: #007bff; + --item-disabled-bg: #e0e0e0; + --text-color: #333; + --selected-text-color: #fff; + --border-color: #ccc; +} + +.multiselect-list { + display: flex; + flex-direction: column; + max-width: 300px; + margin: 0 auto; +} + +.list-item { + padding: 10px; + background-color: var(--item-bg); + color: var(--text-color); + border-bottom: 1px solid var(--border-color); + cursor: pointer; + transition: background-color 0.2s; +} + +.list-item:hover { + background-color: var(--item-hover-bg); +} + +.list-item.selected { + background-color: var(--item-selected-bg); + color: var(--selected-text-color); +} + +.list-item[aria-disabled="true"] { + background-color: var(--item-disabled-bg); + cursor: not-allowed; +} + +.list-item:last-child { + border-bottom: none; +} \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/sveltekit/src/components/MultiselectList/MultiselectList.stories.ts b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/MultiselectList/MultiselectList.stories.ts new file mode 100644 index 0000000000..304d8dcad8 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/MultiselectList/MultiselectList.stories.ts @@ -0,0 +1,80 @@ +import MultiselectList from './MultiselectList.svelte'; +import type { Meta, StoryFn } from '@storybook/svelte'; + +const meta: Meta = { + title: 'component/Lists/MultiselectList', + component: MultiselectList, + tags: ['autodocs'], + argTypes: { + items: { control: 'object' }, + disabled: { control: 'boolean' } + }, + parameters: { + layout: 'centered', + viewport: { + viewports: { + smallMobile: { name: 'Small Mobile', styles: { width: '320px', height: '568px' } }, + largeMobile: { name: 'Large Mobile', styles: { width: '414px', height: '896px' } }, + tablet: { name: 'Tablet', styles: { width: '768px', height: '1024px' } }, + desktop: { name: 'Desktop', styles: { width: '1024px', height: '768px' } }, + } + } + } +}; + +export default meta; + +const Template:StoryFn = (args) => ({ + Component:MultiselectList, + props:args, +}); + +export const Default = Template.bind({}); +Default.args = { + items:[ + { id: '1', label: 'Option 1', selected: false }, + { id: '2', label: 'Option 2', selected: false }, + { id: '3', label: 'Option 3', selected: false } + ], + disabled:false, +}; + +export const ItemSelected = Template.bind({}); +ItemSelected.args = { + items:[ + { id: '1', label: 'Option 1', selected: true }, + { id: '2', label: 'Option 2', selected: false }, + { id: '3', label: 'Option 3', selected: false } + ], + disabled:false, +}; + +export const ItemDeselected = Template.bind({}); +ItemDeselected.args = { + items:[ + { id: '1', label: 'Option 1', selected: false }, + { id: '2', label: 'Option 2', selected: true }, + { id: '3', label: 'Option 3', selected: false } + ], + disabled:false, +}; + +export const Disabled = Template.bind({}); +Disabled.args = { + items:[ + { id: '1', label: 'Option 1', selected: false }, + { id: '2', label: 'Option 2', selected: false }, + { id: '3', label: 'Option 3', selected: false } + ], + disabled:true, +}; + +export const Hover = Template.bind({}); +Hover.args = { + items:[ + { id: '1', label: 'Option 1', selected: false }, + { id: '2', label: 'Option 2', selected: false }, + { id: '3', label: 'Option 3', selected: false } + ], + disabled:true, +}; \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/sveltekit/src/components/MultiselectList/MultiselectList.svelte b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/MultiselectList/MultiselectList.svelte new file mode 100644 index 0000000000..6a9a9163bf --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/MultiselectList/MultiselectList.svelte @@ -0,0 +1,32 @@ + + +
    + {#each items as { id, label, selected } (id)} +
    toggleSelect(id)} + aria-selected={selected} + aria-disabled={disabled} + tabindex={disabled ? -1 : 0} + on:keydown={(e)=>{if (e.key === 'Enter' || e.key === ' '){toggleSelect(id)}}} + role = "tab" + > + {label} +
    + {/each} +
    + + \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/sveltekit/src/components/NotificationBellIcon/NotificationBellIcon.css b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/NotificationBellIcon/NotificationBellIcon.css new file mode 100644 index 0000000000..8b91eae5e1 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/NotificationBellIcon/NotificationBellIcon.css @@ -0,0 +1,32 @@ +:root { + --bell-size: 24px; + --bell-color: #333; + --notification-dot-size: 8px; + --notification-dot-color: #e74c3c; +} + +.notification-bell { + position: relative; + display: inline-flex; + align-items: center; + justify-content: center; + width: var(--bell-size); + height: var(--bell-size); + color: var(--bell-color); + cursor: pointer; +} + +.bell-icon { + width: 100%; + height: 100%; +} + +.notification-dot { + position: absolute; + top: 0; + right: 0; + width: var(--notification-dot-size); + height: var(--notification-dot-size); + background-color: var(--notification-dot-color); + border-radius: 50%; +} \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/sveltekit/src/components/NotificationBellIcon/NotificationBellIcon.stories.ts b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/NotificationBellIcon/NotificationBellIcon.stories.ts new file mode 100644 index 0000000000..a7d3c242d6 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/NotificationBellIcon/NotificationBellIcon.stories.ts @@ -0,0 +1,54 @@ +import NotificationBellIcon from './NotificationBellIcon.svelte'; +import type { Meta, StoryFn } from '@storybook/svelte'; + +const meta: Meta = { + title: 'component/Indicators/NotificationBellIcon', + component: NotificationBellIcon, + tags: ['autodocs'], + argTypes: { + hasNotifications: { control: 'boolean' }, + dismissed: { control: 'boolean' }, + }, + parameters: { + layout: 'centered', + viewport: { + viewports: { + smallMobile: { name: 'Small Mobile', styles: { width: '320px', height: '568px' } }, + largeMobile: { name: 'Large Mobile', styles: { width: '414px', height: '896px' } }, + tablet: { name: 'Tablet', styles: { width: '768px', height: '1024px' } }, + desktop: { name: 'Desktop', styles: { width: '1024px', height: '768px' } }, + } + } + } +}; + +export default meta; + +const Template:StoryFn = (args) => ({ + Component: NotificationBellIcon, + props:args, +}); + +export const Default = Template.bind({}); +Default.args = { + hasNotifications:false, + dismissed:false, +}; + +export const NoNotifications = Template.bind({}); +NoNotifications.args = { + hasNotifications:false, + dismissed:true, +}; + +export const NewNotifications = Template.bind({}); +NewNotifications.args = { + hasNotifications:true, + dismissed:false, +}; + +export const Dismissed = Template.bind({}); +Dismissed.args = { + hasNotifications:true, + dismissed:true, +}; \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/sveltekit/src/components/NotificationBellIcon/NotificationBellIcon.svelte b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/NotificationBellIcon/NotificationBellIcon.svelte new file mode 100644 index 0000000000..bb61cd769c --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/NotificationBellIcon/NotificationBellIcon.svelte @@ -0,0 +1,18 @@ + + +
    + + {#if hasNotifications && !dismissed} + + {/if} +
    + + \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/sveltekit/src/components/NumberInputWithIncrement/NumberInputWithIncrement.css b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/NumberInputWithIncrement/NumberInputWithIncrement.css new file mode 100644 index 0000000000..befa35dfc8 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/NumberInputWithIncrement/NumberInputWithIncrement.css @@ -0,0 +1,35 @@ +:root { + --button-bg: #f0f0f0; + --button-bg-hover: #e0e0e0; + --button-bg-disabled: #d0d0d0; + --input-border: #ccc; +} + +.number-input { + display: flex; + align-items: center; +} + +button { + background-color: var(--button-bg); + border: none; + padding: 0.5rem; + cursor: pointer; + transition: background-color 0.3s; +} + +button:hover:not(:disabled) { + background-color: var(--button-bg-hover); +} + +button:disabled { + background-color: var(--button-bg-disabled); + cursor: not-allowed; +} + +input[type="number"] { + width: 3rem; + text-align: center; + border: 1px solid var(--input-border); + margin: 0 0.5rem; +} \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/sveltekit/src/components/NumberInputWithIncrement/NumberInputWithIncrement.stories.ts b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/NumberInputWithIncrement/NumberInputWithIncrement.stories.ts new file mode 100644 index 0000000000..645524fbe7 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/NumberInputWithIncrement/NumberInputWithIncrement.stories.ts @@ -0,0 +1,83 @@ +import { userEvent, within } from '@storybook/test'; +import NumberInputWithIncrement from './NumberInputWithIncrement.svelte'; +import type { Meta, StoryFn } from '@storybook/svelte'; + +const meta: Meta = { + title: 'component/Forms/NumberInputWithIncrement', + component: NumberInputWithIncrement, + tags: ['autodocs'], + argTypes: { + value: { control: 'number' }, + min: { control: 'number' }, + max: { control: 'number' }, + step: { control: 'number' }, + disabled: { control: 'boolean' }, + }, + parameters: { + layout: 'centered', + viewport: { + viewports: { + smallMobile: { name: 'Small Mobile', styles: { width: '320px', height: '568px' } }, + largeMobile: { name: 'Large Mobile', styles: { width: '414px', height: '896px' } }, + tablet: { name: 'Tablet', styles: { width: '768px', height: '1024px' } }, + desktop: { name: 'Desktop', styles: { width: '1024px', height: '768px' } }, + } + } + } +}; + +export default meta; + +const Template:StoryFn = (args) => ({ + Component:NumberInputWithIncrement, + props:args, +}); + +export const Default = Template.bind({}); +Default.args = { + value: 0, + min: 0, + max: 100, + step: 1, + disabled: false, +}; + +export const Increment = Template.bind({}); +Increment.args = { + value: 5, + min: 0, + max: 10, + step: 1, + disabled: false, +}; +Increment.play = async({canvasElement}) => { + const canvas = within(canvasElement); + + const incrementButton = await canvas.getByText('+'); + + await userEvent.click(incrementButton); +}; + +export const Decrement = Template.bind({}); +Decrement.args = { + value: 5, + min: 0, + max: 10, + step: 1, + disabled: false, +}; +Decrement.play = async({canvasElement}) => { + const canvas = within(canvasElement); + + const decrementButton = await canvas.getByText('-'); + await userEvent.click(decrementButton) +}; + +export const Disabled = Template.bind({}); +Disabled.args = { + value: 5, + min: 0, + max: 10, + step: 1, + disabled: true, +}; \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/sveltekit/src/components/NumberInputWithIncrement/NumberInputWithIncrement.svelte b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/NumberInputWithIncrement/NumberInputWithIncrement.svelte new file mode 100644 index 0000000000..8fb1772f0c --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/NumberInputWithIncrement/NumberInputWithIncrement.svelte @@ -0,0 +1,40 @@ + + +
    + + + +
    + + \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/sveltekit/src/components/NumberedList/NumberedList.css b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/NumberedList/NumberedList.css new file mode 100644 index 0000000000..3cbb411619 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/NumberedList/NumberedList.css @@ -0,0 +1,43 @@ +:root { + --item-bg: #fff; + --item-hover-bg: #f0f0f0; + --item-selected-bg: #007bff; + --item-disabled-bg: #e0e0e0; + --text-color: #333; + --selected-text-color: #fff; + --border-color: #ccc; +} + +.numbered-list { + padding: 0; + list-style: decimal inside; + max-width: 300px; + margin: 0 auto; +} + +.list-item { + padding: 10px; + background-color: var(--item-bg); + color: var(--text-color); + border-bottom: 1px solid var(--border-color); + cursor: pointer; + transition: background-color 0.2s; +} + +.list-item:hover { + background-color: var(--item-hover-bg); +} + +.list-item.selected { + background-color: var(--item-selected-bg); + color: var(--selected-text-color); +} + +.list-item[aria-disabled="true"] { + background-color: var(--item-disabled-bg); + cursor: not-allowed; +} + +.list-item:last-child { + border-bottom: none; +} \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/sveltekit/src/components/NumberedList/NumberedList.stories.ts b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/NumberedList/NumberedList.stories.ts new file mode 100644 index 0000000000..7f5dc58a1f --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/NumberedList/NumberedList.stories.ts @@ -0,0 +1,70 @@ +import NumberedList from './NumberedList.svelte'; +import type { Meta, StoryFn } from '@storybook/svelte'; + +const meta: Meta = { + title: 'component/Lists/NumberedList', + component: NumberedList, + tags: ['autodocs'], + argTypes: { + items: { control: 'object' }, + disabled: { control: 'boolean' } + }, + parameters: { + layout: 'centered', + viewport: { + viewports: { + smallMobile: { name: 'Small Mobile', styles: { width: '320px', height: '568px' } }, + largeMobile: { name: 'Large Mobile', styles: { width: '414px', height: '896px' } }, + tablet: { name: 'Tablet', styles: { width: '768px', height: '1024px' } }, + desktop: { name: 'Desktop', styles: { width: '1024px', height: '768px' } }, + } + } + } +}; + +export default meta; + +const Template:StoryFn = (args) => ({ + Component:NumberedList, + props:args, +}); + +export const Default = Template.bind({}); +Default.args = { + items: [ + { id: '1', label: 'Item 1', selected: false }, + { id: '2', label: 'Item 2', selected: false }, + { id: '3', label: 'Item 3', selected: false } + ], + disabled:false, +}; + +export const Selected = Template.bind({}); +Selected.args = { + items: [ + { id: '1', label: 'Item 1', selected: true }, + { id: '2', label: 'Item 2', selected: false }, + { id: '3', label: 'Item 3', selected: false } + ], + disabled:false, +}; + +export const Hover = Template.bind({}); +Hover.args = { + items: [ + { id: '1', label: 'Item 1', selected: false }, + { id: '2', label: 'Item 2', selected: false }, + { id: '3', label: 'Item 3', selected: false } + ], + disabled:false, +}; + +export const Disabled = Template.bind({}); +Disabled.args = { + items: [ + { id: '1', label: 'Item 1', selected: false }, + { id: '2', label: 'Item 2', selected: false }, + { id: '3', label: 'Item 3', selected: false } + ], + disabled:true, +}; \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/sveltekit/src/components/NumberedList/NumberedList.svelte b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/NumberedList/NumberedList.svelte new file mode 100644 index 0000000000..c343fd0db9 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/NumberedList/NumberedList.svelte @@ -0,0 +1,32 @@ + + +
      + {#each items as { id, label, selected } (id)} +
    1. handleSelect(id)} + aria-selected={selected} + aria-disabled={disabled} + tabindex={disabled ? -1 : 0} + on:keydown={(e)=>{if(e.key === 'Enter' || e.key === ' '){handleSelect(id)}}} + role="tab" + > + {label} +
    2. + {/each} +
    + + \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/sveltekit/src/components/Pagination/Pagination.css b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/Pagination/Pagination.css new file mode 100644 index 0000000000..48e2723a35 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/Pagination/Pagination.css @@ -0,0 +1,40 @@ +:root { + --button-bg: #fff; + --button-hover-bg: #f0f0f0; + --button-active-bg: #007bff; + --button-disabled-bg: #e0e0e0; + --text-color: #333; + --active-text-color: #fff; + --border-color: #ccc; +} + +.pagination { + display: flex; + justify-content: center; + align-items: center; + gap: 5px; +} + +.page-button { + padding: 8px 12px; + background-color: var(--button-bg); + color: var(--text-color); + border: 1px solid var(--border-color); + cursor: pointer; + transition: background-color 0.2s; +} + +.page-button:hover { + background-color: var(--button-hover-bg); +} + +.page-button.active { + background-color: var(--button-active-bg); + color: var(--active-text-color); +} + +.page-button[disabled], +.page-button[aria-disabled="true"] { + background-color: var(--button-disabled-bg); + cursor: not-allowed; +} \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/sveltekit/src/components/Pagination/Pagination.stories.ts b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/Pagination/Pagination.stories.ts new file mode 100644 index 0000000000..e4f2bdbfbb --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/Pagination/Pagination.stories.ts @@ -0,0 +1,59 @@ +import Pagination from './Pagination.svelte'; +import type { Meta, StoryFn } from '@storybook/svelte'; + +const meta: Meta = { + title: 'component/Lists/Pagination', + component: Pagination, + tags: ['autodocs'], + argTypes: { + totalItems: { control: 'number' }, + itemsPerPage: { control: 'number' }, + currentPage: { control: 'number' } + }, + parameters: { + layout: 'centered', + viewport: { + viewports: { + smallMobile: { name: 'Small Mobile', styles: { width: '320px', height: '568px' } }, + largeMobile: { name: 'Large Mobile', styles: { width: '414px', height: '896px' } }, + tablet: { name: 'Tablet', styles: { width: '768px', height: '1024px' } }, + desktop: { name: 'Desktop', styles: { width: '1024px', height: '768px' } }, + } + } + } +}; + +export default meta; + +const Template:StoryFn = (args) => ({ + Component: Pagination, + props:args, +}); + +export const Default = Template.bind({}); +Default.args = { + totalItems: 50, + itemsPerPage: 10, + currentPage: 1 +}; + +export const Active = Template.bind({}); +Active.args = { + totalItems: 50, + itemsPerPage: 10, + currentPage: 3 +}; + +export const Inactive = Template.bind({}); +Inactive.args = { + totalItems: 50, + itemsPerPage: 10, + currentPage: 1 +}; + +export const Hover = Template.bind({}); +Hover.args = { + totalItems: 50, + itemsPerPage: 10, + currentPage: 2 +}; \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/sveltekit/src/components/Pagination/Pagination.svelte b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/Pagination/Pagination.svelte new file mode 100644 index 0000000000..dcfdc008c5 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/Pagination/Pagination.svelte @@ -0,0 +1,45 @@ + + + + + \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/sveltekit/src/components/PasswordConfirmationField/PasswordConfirmationField.css b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/PasswordConfirmationField/PasswordConfirmationField.css new file mode 100644 index 0000000000..1add039e83 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/PasswordConfirmationField/PasswordConfirmationField.css @@ -0,0 +1,28 @@ +:root { + --input-border: #ccc; + --input-border-error: #ff4d4d; + --error-color: #ff4d4d; +} + +.password-confirmation-field { + display: flex; + flex-direction: column; + gap: 0.5rem; +} + +input { + padding: 0.5rem; + border: 1px solid var(--input-border); + border-radius: 4px; + transition: border-color 0.3s; +} + +input:focus { + outline: none; + border-color: var(--input-border-error); +} + +.error { + color: var(--error-color); + font-size: 0.875rem; +} \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/sveltekit/src/components/PasswordConfirmationField/PasswordConfirmationField.stories.ts b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/PasswordConfirmationField/PasswordConfirmationField.stories.ts new file mode 100644 index 0000000000..40394c728b --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/PasswordConfirmationField/PasswordConfirmationField.stories.ts @@ -0,0 +1,59 @@ +import PasswordConfirmationField from './PasswordConfirmationField.svelte'; +import type { Meta, StoryFn } from '@storybook/svelte'; + +const meta: Meta = { + title: 'component/Forms/PasswordConfirmationField', + component: PasswordConfirmationField, + tags: ['autodocs'], + argTypes: { + password: { control: 'text' }, + confirmPassword: { control: 'text' }, + disabled: { control: 'boolean' }, + }, + parameters: { + layout: 'centered', + viewport: { + viewports: { + smallMobile: { name: 'Small Mobile', styles: { width: '320px', height: '568px' } }, + largeMobile: { name: 'Large Mobile', styles: { width: '414px', height: '896px' } }, + tablet: { name: 'Tablet', styles: { width: '768px', height: '1024px' } }, + desktop: { name: 'Desktop', styles: { width: '1024px', height: '768px' } }, + } + } + } +}; + +export default meta; + +const Template:StoryFn = (args) => ({ + Component: PasswordConfirmationField, + props:args, +}); + +export const Default = Template.bind({}); +Default.args = { + password: '', + confirmPassword: '', + disabled: false, +}; + +export const Matching = Template.bind({}); +Matching.args = { + password: 'password123', + confirmPassword: 'password123', + disabled: false, +}; + +export const NotMatching = Template.bind({}); +NotMatching.args = { + password: 'password123', + confirmPassword: 'password222', + disabled: false, +}; + +export const Disabled = Template.bind({}); +Disabled.args = { + password: 'password123', + confirmPassword: 'password123', + disabled: true, +}; \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/sveltekit/src/components/PasswordConfirmationField/PasswordConfirmationField.svelte b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/PasswordConfirmationField/PasswordConfirmationField.svelte new file mode 100644 index 0000000000..00bce9da2e --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/PasswordConfirmationField/PasswordConfirmationField.svelte @@ -0,0 +1,35 @@ + + +
    + + + {#if !passwordsMatch} +

    Passwords do not match

    + {/if} +
    + + \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/sveltekit/src/components/PinnedList/PinnedList.css b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/PinnedList/PinnedList.css new file mode 100644 index 0000000000..3073ca06f9 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/PinnedList/PinnedList.css @@ -0,0 +1,46 @@ +:root { + --item-bg: #fff; + --item-hover-bg: #f0f0f0; + --item-selected-bg: #007bff; + --item-text-color: #333; + --item-selected-text-color: #fff; + --border-color: #ccc; +} + +.pinned-list { + list-style: none; + padding: 0; + margin: 0; +} + +.list-item { + display: flex; + justify-content: space-between; + align-items: center; + padding: 8px; + background-color: var(--item-bg); + color: var(--item-text-color); + border: 1px solid var(--border-color); + transition: background-color 0.2s; + cursor: pointer; +} + +.list-item:hover { + background-color: var(--item-hover-bg); +} + +.list-item.pinned { + font-weight: bold; +} + +.list-item.selected { + background-color: var(--item-selected-bg); + color: var(--item-selected-text-color); +} + +.pin-button { + background: none; + border: none; + font-size: 16px; + cursor: pointer; +} \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/sveltekit/src/components/PinnedList/PinnedList.stories.ts b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/PinnedList/PinnedList.stories.ts new file mode 100644 index 0000000000..afeff77022 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/PinnedList/PinnedList.stories.ts @@ -0,0 +1,81 @@ +import PinnedList from './PinnedList.svelte'; +import type { Meta, StoryFn } from '@storybook/svelte'; + +const meta: Meta = { + title: 'component/Lists/PinnedList', + component: PinnedList, + tags: ['autodocs'], + argTypes: { + items: { control: 'object' }, + selectedItem: { control: 'number' } + }, + parameters: { + layout: 'centered', + viewport: { + viewports: { + smallMobile: { name: 'Small Mobile', styles: { width: '320px', height: '568px' } }, + largeMobile: { name: 'Large Mobile', styles: { width: '414px', height: '896px' } }, + tablet: { name: 'Tablet', styles: { width: '768px', height: '1024px' } }, + desktop: { name: 'Desktop', styles: { width: '1024px', height: '768px' } }, + } + } + } +}; + +export default meta; + +const Template:StoryFn = (args) => ({ + Component: PinnedList, + props: args, +}); + +export const Default = Template.bind({}); +Default.args = { + items: [ + { id: 1, text: 'Item 1', pinned: false }, + { id: 2, text: 'Item 2', pinned: false }, + { id: 3, text: 'Item 3', pinned: false } + ], + selectedItem: null +}; + +export const Pinned = Template.bind({}); +Pinned.args = { + items: [ + { id: 1, text: 'Item 1', pinned: true }, + { id: 2, text: 'Item 2', pinned: false }, + { id: 3, text: 'Item 3', pinned: false } + ], + selectedItem: null +}; + +export const Unpinned = Template.bind({}); +Unpinned.args = { + items: [ + { id: 1, text: 'Item 1', pinned: false }, + { id: 2, text: 'Item 2', pinned: false }, + { id: 3, text: 'Item 3', pinned: false } + ], + selectedItem: null +}; + + +export const Hover = Template.bind({}); +Hover.args = { + items: [ + { id: 1, text: 'Item 1', pinned: false }, + { id: 2, text: 'Item 2', pinned: false }, + { id: 3, text: 'Item 3', pinned: false } + ], + selectedItem: null +}; + +export const selectedItem = Template.bind({}); +selectedItem.args = { + items: [ + { id: 1, text: 'Item 1', pinned: false }, + { id: 2, text: 'Item 2', pinned: false }, + { id: 3, text: 'Item 3', pinned: false } + ], + selectedItem: 2, +}; \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/sveltekit/src/components/PinnedList/PinnedList.svelte b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/PinnedList/PinnedList.svelte new file mode 100644 index 0000000000..803bd321a3 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/PinnedList/PinnedList.svelte @@ -0,0 +1,44 @@ + + +
      + {#each items as item (item.id)} +
    • selectItem(item.id)} + on:keydown={(e)=>{if(e.key === 'Enter' || e.key === ' '){selectItem(item.id)}}} + aria-selected={selectedItem === item.id} + tabindex ="0" + role="tab" + > + {item.text} + +
    • + {/each} +
    + + \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/sveltekit/src/components/ProgressBar/ProgressBar.css b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/ProgressBar/ProgressBar.css new file mode 100644 index 0000000000..055eae4214 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/ProgressBar/ProgressBar.css @@ -0,0 +1,29 @@ +:root { + --progress-bar-height: 16px; + --progress-bar-background: #e0e0e0; + --progress-bar-color: #76c7c0; + --progress-bar-disabled-color: #b0b0b0; + --progress-bar-hover-color: #60a9a7; +} + +.progress-bar-container { + width: 100%; + background-color: var(--progress-bar-background); + border-radius: 8px; + overflow: hidden; +} + +.progress-bar { + height: var(--progress-bar-height); + background-color: var(--progress-bar-color); + border-radius: 8px; + transition: width 0.2s ease-in-out; +} + +.progress-bar-container[aria-disabled="true"] .progress-bar { + background-color: var(--progress-bar-disabled-color); +} + +.progress-bar-container:hover .progress-bar:not([aria-disabled="true"]) { + background-color: var(--progress-bar-hover-color); +} \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/sveltekit/src/components/ProgressBar/ProgressBar.stories.ts b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/ProgressBar/ProgressBar.stories.ts new file mode 100644 index 0000000000..ae001ab523 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/ProgressBar/ProgressBar.stories.ts @@ -0,0 +1,60 @@ +import ProgressBar from './ProgressBar.svelte'; +import type { Meta, StoryFn, StoryObj } from '@storybook/svelte'; + +const meta: Meta = { + title: 'component/Indicators/ProgressBar', + component: ProgressBar, + tags: ['autodocs'], + argTypes: { + progress: { control: { type: 'range', min: 0, max: 100 } }, + disabled: { control: 'boolean' }, + }, + parameters: { + layout: 'centered', + viewport: { + viewports: { + smallMobile: { name: 'Small Mobile', styles: { width: '320px', height: '568px' } }, + largeMobile: { name: 'Large Mobile', styles: { width: '414px', height: '896px' } }, + tablet: { name: 'Tablet', styles: { width: '768px', height: '1024px' } }, + desktop: { name: 'Desktop', styles: { width: '1024px', height: '768px' } }, + } + } + } +}; + +export default meta; + +const Template:StoryFn = (args) => ({ + Component:ProgressBar, + props:args, +}); + +export const Default = Template.bind({}); +Default.args = { + progress: 50, + disabled: false, +}; + +export const Complete = Template.bind({}); +Complete.args = { + progress: 100, + disabled: false, +}; + +export const Incomplete = Template.bind({}); +Incomplete.args = { + progress: 25, + disabled: false, +}; + +export const Hover = Template.bind({}); +Hover.args = { + progress: 70, + disabled: false, +}; + +export const Disabled = Template.bind({}); +Disabled.args = { + progress: 50, + disabled: true, +}; \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/sveltekit/src/components/ProgressBar/ProgressBar.svelte b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/ProgressBar/ProgressBar.svelte new file mode 100644 index 0000000000..b07d1a7ee4 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/ProgressBar/ProgressBar.svelte @@ -0,0 +1,13 @@ + + +
    +
    +
    + + \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/sveltekit/src/components/ProgressCircle/ProgressCircle.css b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/ProgressCircle/ProgressCircle.css new file mode 100644 index 0000000000..d18c7d9818 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/ProgressCircle/ProgressCircle.css @@ -0,0 +1,42 @@ +:root { + --circle-background-color: #e0e0e0; + --circle-progress-color: #76c7c0; + --circle-complete-color: #4caf50; + --circle-incomplete-color: #f44336; + --circle-paused-color: #ff9800; + --circle-active-color: #2196f3; +} + +.progress-circle { + transform: rotate(-90deg); +} + +.background { + fill: none; + stroke: var(--circle-background-color); + stroke-width: 10; +} + +.progress { + fill: none; + stroke: var(--circle-progress-color); + stroke-width: 10; + transition: stroke-dashoffset 0.35s; + transform-origin: center; +} + +[data-state='complete'] .progress { + stroke: var(--circle-complete-color); +} + +[data-state='incomplete'] .progress { + stroke: var(--circle-incomplete-color); +} + +[data-state='paused'] .progress { + stroke: var(--circle-paused-color); +} + +[data-state='active'] .progress { + stroke: var(--circle-active-color); +} \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/sveltekit/src/components/ProgressCircle/ProgressCircle.stories.ts b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/ProgressCircle/ProgressCircle.stories.ts new file mode 100644 index 0000000000..ffa8c3f81d --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/ProgressCircle/ProgressCircle.stories.ts @@ -0,0 +1,66 @@ +import ProgressCircle from './ProgressCircle.svelte'; +import type { Meta, StoryFn } from '@storybook/svelte'; + +const meta: Meta = { + title: 'component/Indicators/ProgressCircle', + component: ProgressCircle, + tags: ['autodocs'], + argTypes: { + progress: { control: { type: 'range', min: 0, max: 100 } }, + state: { control: 'select', options: ['complete', 'incomplete', 'in-progress', 'paused', 'active'] }, + }, + parameters: { + layout: 'centered', + viewport: { + viewports: { + smallMobile: { name: 'Small Mobile', styles: { width: '320px', height: '568px' } }, + largeMobile: { name: 'Large Mobile', styles: { width: '414px', height: '896px' } }, + tablet: { name: 'Tablet', styles: { width: '768px', height: '1024px' } }, + desktop: { name: 'Desktop', styles: { width: '1024px', height: '768px' } }, + } + } + } +}; + +export default meta; + +const Template:StoryFn = (args) => ({ + Component: ProgressCircle, + props:args, +}); + +export const Default = Template.bind({}); +Default.args = { + progress: 50, + state: 'in-progress', +}; + +export const Complete = Template.bind({}); +Complete.args = { + progress: 100, + state: 'complete', +}; + +export const InComplete = Template.bind({}); +InComplete.args = { + progress: 0, + state: 'incomplete', +}; + +export const InProgress = Template.bind({}); +InProgress.args = { + progress: 50, + state: 'in-progress', +}; + +export const Paused = Template.bind({}); +Paused.args = { + progress: 50, + state: 'paused', +}; + +export const Active = Template.bind({}); +Active.args = { + progress: 75, + state: 'active', +}; \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/sveltekit/src/components/ProgressCircle/ProgressCircle.svelte b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/ProgressCircle/ProgressCircle.svelte new file mode 100644 index 0000000000..f5b718ce5f --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/ProgressCircle/ProgressCircle.svelte @@ -0,0 +1,33 @@ + + + + + + + + \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/sveltekit/src/components/README.md b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/README.md new file mode 100644 index 0000000000..4287ca8617 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/README.md @@ -0,0 +1 @@ +# \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/sveltekit/src/components/RadioButton/RadioButton.css b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/RadioButton/RadioButton.css new file mode 100644 index 0000000000..d519f23fc2 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/RadioButton/RadioButton.css @@ -0,0 +1,48 @@ +:root { + --radio-border: #ccc; + --radio-border-selected: #007bff; + --radio-border-disabled: #d3d3d3; + --label-color: #333; + --label-color-disabled: #a9a9a9; +} + +.radio-button { + display: flex; + align-items: center; + cursor: pointer; + user-select: none; +} + +input[type="radio"] { + appearance: none; + margin: 0; + width: 1rem; + height: 1rem; + border: 2px solid var(--radio-border); + border-radius: 50%; + display: inline-block; + position: relative; + vertical-align: middle; + cursor: pointer; + outline: none; + transition: border-color 0.3s; +} + +input[type="radio"]:checked { + border-color: var(--radio-border-selected); +} + +input[type="radio"]:disabled { + border-color: var(--radio-border-disabled); + cursor: not-allowed; +} + +label { + margin-left: 0.5rem; + color: var(--label-color); +} + +input[type="radio"]:disabled + label { + color: var(--label-color-disabled); + cursor: not-allowed; +} \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/sveltekit/src/components/RadioButton/RadioButton.stories.ts b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/RadioButton/RadioButton.stories.ts new file mode 100644 index 0000000000..7f099d600f --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/RadioButton/RadioButton.stories.ts @@ -0,0 +1,59 @@ +import RadioButton from './RadioButton.svelte'; +import type { Meta, StoryFn } from '@storybook/svelte'; + +const meta: Meta = { + title: 'component/Forms/RadioButton', + component: RadioButton, + tags: ['autodocs'], + argTypes: { + selected: { control: 'boolean' }, + disabled: { control: 'boolean' }, + label: { control: 'text' }, + }, + parameters: { + layout: 'centered', + viewport: { + viewports: { + smallMobile: { name: 'Small Mobile', styles: { width: '320px', height: '568px' } }, + largeMobile: { name: 'Large Mobile', styles: { width: '414px', height: '896px' } }, + tablet: { name: 'Tablet', styles: { width: '768px', height: '1024px' } }, + desktop: { name: 'Desktop', styles: { width: '1024px', height: '768px' } }, + } + } + } +}; + +export default meta; + +const Template:StoryFn = (args) => ({ + Component:RadioButton, + props:args, +}); + +export const Default = Template.bind({}); +Default.args = { + selected: false, + disabled: false, + label: 'Default', +}; + +export const Selected = Template.bind({}); +Selected.args = { + selected: true, + disabled: false, + label: 'Selected', +}; + +export const UnSelected = Template.bind({}); +UnSelected.args = { + selected: false, + disabled: false, + label: 'Unselected', +}; + +export const Disabled = Template.bind({}); +Disabled.args = { + selected: false, + disabled: true, + label: 'Disabled', +}; \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/sveltekit/src/components/RadioButton/RadioButton.svelte b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/RadioButton/RadioButton.svelte new file mode 100644 index 0000000000..28d6df2079 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/RadioButton/RadioButton.svelte @@ -0,0 +1,26 @@ + + + + + \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/sveltekit/src/components/RangeSlider/RangeSlider.css b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/RangeSlider/RangeSlider.css new file mode 100644 index 0000000000..930043454e --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/RangeSlider/RangeSlider.css @@ -0,0 +1,57 @@ +:root { + --slider-track-bg: #ddd; + --slider-thumb-bg: #007bff; + --slider-thumb-bg-hover: #0056b3; + --slider-thumb-bg-active: #003f7f; + --slider-thumb-bg-disabled: #b0b0b0; + --label-color: #333; + --label-color-disabled: #a9a9a9; +} + +.range-slider { + display: flex; + align-items: center; + width: 100%; +} + +input[type='range'] { + -webkit-appearance: none; + width: 100%; + height: 6px; + background: var(--slider-track-bg); + outline: none; + opacity: 0.8; + transition: opacity 0.2s; + margin: 0 10px; +} + +input[type='range']::-webkit-slider-thumb { + -webkit-appearance: none; + appearance: none; + width: 20px; + height: 20px; + background: var(--slider-thumb-bg); + cursor: pointer; +} + +input[type='range']:hover::-webkit-slider-thumb { + background: var(--slider-thumb-bg-hover); +} + +input[type='range']:active::-webkit-slider-thumb { + background: var(--slider-thumb-bg-active); +} + +input[type='range']:disabled::-webkit-slider-thumb { + background: var(--slider-thumb-bg-disabled); + cursor: not-allowed; +} + +label { + color: var(--label-color); +} + +input[type='range']:disabled + label { + color: var(--label-color-disabled); + cursor: not-allowed; +} \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/sveltekit/src/components/RangeSlider/RangeSlider.stories.ts b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/RangeSlider/RangeSlider.stories.ts new file mode 100644 index 0000000000..666389ffe2 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/RangeSlider/RangeSlider.stories.ts @@ -0,0 +1,94 @@ +import RangeSlider from './RangeSlider.svelte'; +import type { Meta, StoryFn } from '@storybook/svelte'; + +const meta: Meta = { + title: 'component/Forms/RangeSlider', + component: RangeSlider, + tags: ['autodocs'], + argTypes: { + min: { control: 'number' }, + max: { control: 'number' }, + value: { control: 'number' }, + disabled: { control: 'boolean' }, + label: { control: 'text' }, + labelPosition: { control: 'select', options: ['left', 'center', 'right'] }, + }, + parameters: { + layout: 'centered', + viewport: { + viewports: { + smallMobile: { name: 'Small Mobile', styles: { width: '320px', height: '568px' } }, + largeMobile: { name: 'Large Mobile', styles: { width: '414px', height: '896px' } }, + tablet: { name: 'Tablet', styles: { width: '768px', height: '1024px' } }, + desktop: { name: 'Desktop', styles: { width: '1024px', height: '768px' } }, + } + } + } +}; + +export default meta; + +const Template:StoryFn = (args) => ({ + Component:RangeSlider, + props:args, +}); + +export const Default = Template.bind({}); +Default.args = { + min: 0, + max: 100, + value: 50, + disabled: false, + label: 'Default', + labelPosition: 'right', +}; + +export const Min = Template.bind({}); +Min.args = { + min: 0, + max: 100, + value: 0, + disabled: false, + label: 'Min', + labelPosition: 'right', +}; + +export const Max = Template.bind({}); +Max.args = { + min: 0, + max: 100, + value: 100, + disabled: false, + label: 'Max', + labelPosition: 'right', +}; + +export const Hover = Template.bind({}); +Hover.args = { + min: 0, + max: 100, + value: 50, + disabled: false, + label: 'Hover', + labelPosition: 'right', +}; + +export const Active = Template.bind({}); +Active.args = { + min: 0, + max: 100, + value: 75, + disabled: false, + label: 'Active', + labelPosition: 'right', +}; + +export const Disabled = Template.bind({}); +Disabled.args = { + min: 0, + max: 100, + value: 50, + disabled: true, + label: 'Disabled', + labelPosition: 'right', +}; \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/sveltekit/src/components/RangeSlider/RangeSlider.svelte b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/RangeSlider/RangeSlider.svelte new file mode 100644 index 0000000000..c50d0abec5 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/RangeSlider/RangeSlider.svelte @@ -0,0 +1,41 @@ + + +
    + {#if labelPosition === 'left'} + + {/if} + + {#if labelPosition === 'center'} + + {/if} + {#if labelPosition === 'right'} + + {/if} +
    + + \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/sveltekit/src/components/RatingStars/RatingStars.css b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/RatingStars/RatingStars.css new file mode 100644 index 0000000000..9be02e9a5c --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/RatingStars/RatingStars.css @@ -0,0 +1,33 @@ +:root { + --star-color: #e0e0e0; + --star-filled-color: #ffc107; + --star-hover-color: #ffeb3b; + --star-selected-color: #fdd835; +} + +.rating-stars { + display: inline-flex; + gap: 0.5rem; +} + +.star { + font-size: 2rem; + color: var(--star-color); + background: none; + border: none; + cursor: pointer; + transition: color 0.3s; +} + +.star.filled { + color: var(--star-filled-color); +} + +[data-state='hover'] .star:hover, +[data-state='hover'] .star:hover ~ .star { + color: var(--star-hover-color); +} + +[data-state='selected'] .star.filled { + color: var(--star-selected-color); +} \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/sveltekit/src/components/RatingStars/RatingStars.stories.ts b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/RatingStars/RatingStars.stories.ts new file mode 100644 index 0000000000..b615abd60a --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/RatingStars/RatingStars.stories.ts @@ -0,0 +1,54 @@ +import RatingStars from './RatingStars.svelte'; +import type { Meta, StoryFn } from '@storybook/svelte'; + +const meta: Meta = { + title: 'component/Indicators/RatingStars', + component: RatingStars, + tags: ['autodocs'], + argTypes: { + rating: { control: { type: 'range', min: 0, max: 5 } }, + state: { control: 'select', options: ['hover', 'selected', 'inactive'] }, + }, + parameters: { + layout: 'centered', + viewport: { + viewports: { + smallMobile: { name: 'Small Mobile', styles: { width: '320px', height: '568px' } }, + largeMobile: { name: 'Large Mobile', styles: { width: '414px', height: '896px' } }, + tablet: { name: 'Tablet', styles: { width: '768px', height: '1024px' } }, + desktop: { name: 'Desktop', styles: { width: '1024px', height: '768px' } }, + } + } + } +}; + +export default meta; + +const Template:StoryFn = (args) => ({ + Component:RatingStars, + props:args, +}); + +export const Default = Template.bind({}); +Default.args = { + rating:3, + state:'inactive', +}; + +export const Hover = Template.bind({}); +Hover.args = { + rating:3, + state:'hover', +}; + +export const Selected = Template.bind({}); +Selected.args = { + rating:4, + state:'selected', +}; + +export const Inactive = Template.bind({}); +Inactive.args = { + rating:0, + state:'inactive', +}; \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/sveltekit/src/components/RatingStars/RatingStars.svelte b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/RatingStars/RatingStars.svelte new file mode 100644 index 0000000000..e3cdedeb61 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/RatingStars/RatingStars.svelte @@ -0,0 +1,31 @@ + + +
    + {#each stars as star} + + {/each} +
    + + \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/sveltekit/src/components/RichTextEditor/RichTextEditor.css b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/RichTextEditor/RichTextEditor.css new file mode 100644 index 0000000000..aaf068d18e --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/RichTextEditor/RichTextEditor.css @@ -0,0 +1,32 @@ +:root { + --editor-border-color: #ccc; + --editor-focus-border-color: #007bff; + --editor-bg-color: #fff; + --editor-readonly-bg-color: #f8f9fa; +} + +.rich-text-editor { + border: 1px solid var(--editor-border-color); + border-radius: 4px; + background-color: var(--editor-bg-color); + padding: 10px; + min-height: 200px; + max-height: 500px; + overflow-y: auto; +} + +.rich-text-editor:focus-within { + border-color: var(--editor-focus-border-color); +} + +.ql-container.ql-snow { + border: none; +} + +.ql-editor { + padding: 0; +} + +.ql-editor:read-only { + background-color: var(--editor-readonly-bg-color); +} \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/sveltekit/src/components/RichTextEditor/RichTextEditor.stories.ts b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/RichTextEditor/RichTextEditor.stories.ts new file mode 100644 index 0000000000..a77b9405ef --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/RichTextEditor/RichTextEditor.stories.ts @@ -0,0 +1,48 @@ +import RichTextEditor from './RichTextEditor.svelte'; +import type { Meta, StoryFn } from '@storybook/svelte'; + +const meta: Meta = { + title: 'component/Forms/RichTextEditor', + component: RichTextEditor, + tags: ['autodocs'], + argTypes: { + content: { control: 'text' }, + readOnly: { control: 'boolean' }, + }, + parameters: { + layout: 'fullscreen', + viewport: { + viewports: { + smallMobile: { name: 'Small Mobile', styles: { width: '320px', height: '568px' } }, + largeMobile: { name: 'Large Mobile', styles: { width: '414px', height: '896px' } }, + tablet: { name: 'Tablet', styles: { width: '768px', height: '1024px' } }, + desktop: { name: 'Desktop', styles: { width: '1024px', height: '768px' } }, + } + } + } +}; + +export default meta; + +const Template:StoryFn = (args) => ({ + Component: RichTextEditor, + props:args, +}); + +export const Default = Template.bind({}); +Default.args = { + content: '

    Start writing...

    ', + readOnly: false, +}; + +export const Editing = Template.bind({}); +Editing.args = { + content: '

    Edit this text.

    ', + readOnly: false, +}; + +export const ReadOnly = Template.bind({}); +ReadOnly.args = { + content: '

    This text is read-only.

    ', + readOnly: true, +}; \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/sveltekit/src/components/RichTextEditor/RichTextEditor.svelte b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/RichTextEditor/RichTextEditor.svelte new file mode 100644 index 0000000000..b832c3684d --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/RichTextEditor/RichTextEditor.svelte @@ -0,0 +1,47 @@ + + +
    + + \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/sveltekit/src/components/ScrollableList/ScrollableList.css b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/ScrollableList/ScrollableList.css new file mode 100644 index 0000000000..1d23ecd23b --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/ScrollableList/ScrollableList.css @@ -0,0 +1,32 @@ +:root { + --list-bg: #f8f8f8; + --list-border-color: #ccc; + --list-item-bg: #fff; + --list-item-hover-bg: #e0e0e0; + --text-color: #333; + --disabled-bg: #d3d3d3; +} + +.scrollable-list { + max-height: 300px; + overflow-y: auto; + background-color: var(--list-bg); + border: 1px solid var(--list-border-color); +} + +.scrollable-list.disabled { + background-color: var(--disabled-bg); + pointer-events: none; +} + +.list-item { + padding: 8px; + background-color: var(--list-item-bg); + color: var(--text-color); + border-bottom: 1px solid var(--list-border-color); + transition: background-color 0.2s; +} + +.list-item:hover { + background-color: var(--list-item-hover-bg); +} \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/sveltekit/src/components/ScrollableList/ScrollableList.stories.ts b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/ScrollableList/ScrollableList.stories.ts new file mode 100644 index 0000000000..68517bd9cf --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/ScrollableList/ScrollableList.stories.ts @@ -0,0 +1,60 @@ +import ScrollableList from './ScrollableList.svelte'; +import type { Meta, StoryFn } from '@storybook/svelte'; + +const meta: Meta = { + title: 'component/Lists/ScrollableList', + component: ScrollableList, + tags: ['autodocs'], + argTypes: { + items: { control: 'object' }, + disabled: { control: 'boolean' } + }, + parameters: { + layout: 'centered', + viewport: { + viewports: { + smallMobile: { name: 'Small Mobile', styles: { width: '320px', height: '568px' } }, + largeMobile: { name: 'Large Mobile', styles: { width: '414px', height: '896px' } }, + tablet: { name: 'Tablet', styles: { width: '768px', height: '1024px' } }, + desktop: { name: 'Desktop', styles: { width: '1024px', height: '768px' } }, + } + } + } +}; + +export default meta; + +const Template:StoryFn = (args) => ({ + Component:ScrollableList, + props:args, +}); + +export const Default = Template.bind({}); +Default.args = { + items: Array.from({ length: 20 }, (_, i) => ({ id: i + 1, text: `Item ${i + 1}` })), + disabled: false +}; + +export const Scrolling = Template.bind({}); +Scrolling.args = { + items: Array.from({ length: 20 }, (_, i) => ({ id: i + 1, text: `Item ${i + 1}` })), + disabled: false +}; + +export const EndOfList = Template.bind({}); +EndOfList.args = { + items: Array.from({ length: 5 }, (_, i) => ({ id: i + 1, text: `Item ${i + 1}` })), + disabled: false +}; + +export const Hover = Template.bind({}); +Hover.args = { + items: Array.from({ length: 20 }, (_, i) => ({ id: i + 1, text: `Item ${i + 1}` })), + disabled: false +}; + +export const Disabled = Template.bind({}); +Disabled.args = { + items: Array.from({ length: 20 }, (_, i) => ({ id: i + 1, text: `Item ${i + 1}` })), + disabled: true +}; \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/sveltekit/src/components/ScrollableList/ScrollableList.svelte b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/ScrollableList/ScrollableList.svelte new file mode 100644 index 0000000000..746e149ea4 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/ScrollableList/ScrollableList.svelte @@ -0,0 +1,34 @@ + + + + + \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/sveltekit/src/components/SearchBar/SearchBar.css b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/SearchBar/SearchBar.css new file mode 100644 index 0000000000..901d4cdcb0 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/SearchBar/SearchBar.css @@ -0,0 +1,33 @@ +:root { + --search-bar-padding: 0.5rem 1rem; + --search-bar-margin: 0.5rem; + --search-bar-border-radius: 4px; + --search-bar-font-size: 1rem; + --search-bar-bg: #fff; + --search-bar-color: #333; + --search-bar-border: 1px solid #ccc; + --search-bar-border-focused: 1px solid #007bff; + --search-bar-border-disabled: 1px solid #ddd; + --search-bar-bg-disabled: #f5f5f5; +} + +.search-bar { + padding: var(--search-bar-padding); + margin: var(--search-bar-margin); + border-radius: var(--search-bar-border-radius); + font-size: var(--search-bar-font-size); + background-color: var(--search-bar-bg); + color: var(--search-bar-color); + border: var(--search-bar-border); + transition: border 0.3s ease; +} + +.search-bar.is-focused { + border: var(--search-bar-border-focused); +} + +.search-bar:disabled { + background-color: var(--search-bar-bg-disabled); + border: var(--search-bar-border-disabled); + cursor: not-allowed; +} \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/sveltekit/src/components/SearchBar/SearchBar.stories.ts b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/SearchBar/SearchBar.stories.ts new file mode 100644 index 0000000000..71da9a5c39 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/SearchBar/SearchBar.stories.ts @@ -0,0 +1,59 @@ +import SearchBar from './SearchBar.svelte'; +import type { Meta, StoryFn } from '@storybook/svelte'; + +const meta: Meta = { + title: 'component/Input/SearchBar', + component: SearchBar, + tags: ['autodocs'], + argTypes: { + isFocused: { control: 'boolean' }, + isDisabled: { control: 'boolean' }, + placeholder: { control: 'text' } + }, + parameters: { + layout: 'centered', + viewport: { + viewports: { + smallMobile: { name: 'Small Mobile', styles: { width: '320px', height: '568px' } }, + largeMobile: { name: 'Large Mobile', styles: { width: '414px', height: '896px' } }, + tablet: { name: 'Tablet', styles: { width: '768px', height: '1024px' } }, + desktop: { name: 'Desktop', styles: { width: '1024px', height: '768px' } }, + } + } + } +}; + +export default meta; + +const Template:StoryFn = (args) => ({ + Component:SearchBar, + props:args, +}); + +export const Default = Template.bind({}); +Default.args = { + isFocused: false, + isDisabled: false, + placeholder: 'Search...' +}; + +export const Focused = Template.bind({}); +Focused.args = { + isFocused: true, + isDisabled: false, + placeholder: 'Search...' +}; + +export const UnFocused = Template.bind({}); +UnFocused.args = { + isFocused: false, + isDisabled: false, + placeholder: 'Search...' +}; + +export const Disabled = Template.bind({}); +Disabled.args = { + isFocused: false, + isDisabled: true, + placeholder: 'Search...' +}; \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/sveltekit/src/components/SearchBar/SearchBar.svelte b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/SearchBar/SearchBar.svelte new file mode 100644 index 0000000000..8657413086 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/SearchBar/SearchBar.svelte @@ -0,0 +1,19 @@ + + + + + \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/sveltekit/src/components/SearchInputWithFilterOptions/SearchInputWithFilterOptions.css b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/SearchInputWithFilterOptions/SearchInputWithFilterOptions.css new file mode 100644 index 0000000000..e403bc0f35 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/SearchInputWithFilterOptions/SearchInputWithFilterOptions.css @@ -0,0 +1,53 @@ +:root { + --input-border-color: #ccc; + --input-focus-border-color: #007bff; + --button-bg-color: #f0f0f0; + --button-active-bg-color: #007bff; + --button-text-color: #000; + --button-active-text-color: #fff; +} + +.search-input-container { + display: flex; + flex-direction: column; + gap: 10px; +} + +.search-input { + padding: 8px; + border: 1px solid var(--input-border-color); + border-radius: 4px; + font-size: 16px; + width: 100%; +} + +.search-input:focus { + border-color: var(--input-focus-border-color); + outline: none; +} + +.filter-options { + display: flex; + gap: 8px; + flex-wrap: wrap; +} + +.filter-button { + padding: 8px 12px; + background-color: var(--button-bg-color); + color: var(--button-text-color); + border: none; + border-radius: 4px; + cursor: pointer; + transition: background-color 0.3s; +} + +.filter-button.active { + background-color: var(--button-active-bg-color); + color: var(--button-active-text-color); +} + +.filter-button:disabled { + opacity: 0.5; + cursor: not-allowed; +} \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/sveltekit/src/components/SearchInputWithFilterOptions/SearchInputWithFilterOptions.stories.ts b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/SearchInputWithFilterOptions/SearchInputWithFilterOptions.stories.ts new file mode 100644 index 0000000000..280791d645 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/SearchInputWithFilterOptions/SearchInputWithFilterOptions.stories.ts @@ -0,0 +1,72 @@ +import SearchInputWithFilterOptions from './SearchInputWithFilterOptions.svelte'; +import type { Meta, StoryFn } from '@storybook/svelte'; + +const meta: Meta = { + title: 'component/Forms/SearchInputWithFilterOptions', + component: SearchInputWithFilterOptions, + tags: ['autodocs'], + argTypes: { + query: { control: 'text' }, + filters: { control: 'object' }, + activeFilters: { control: 'object' }, + disabled: { control: 'boolean' }, + }, + parameters: { + layout: 'fullscreen', + viewport: { + viewports: { + smallMobile: { name: 'Small Mobile', styles: { width: '320px', height: '568px' } }, + largeMobile: { name: 'Large Mobile', styles: { width: '414px', height: '896px' } }, + tablet: { name: 'Tablet', styles: { width: '768px', height: '1024px' } }, + desktop: { name: 'Desktop', styles: { width: '1024px', height: '768px' } }, + } + } + } +}; + +export default meta; + +const Template:StoryFn = (args) => ({ + Component:SearchInputWithFilterOptions, + props:args, +}); + +export const Default = Template.bind({}); +Default.args = { + query: '', + filters: ['Option 1', 'Option 2', 'Option 3'], + activeFilters: [], + disabled: false, +}; + +export const Searching = Template.bind({}); +Searching.args = { + query: 'Search Term', + filters: ['Option 1', 'Option 2', 'Option 3'], + activeFilters: [], + disabled: false, +}; + +export const FiltersActive = Template.bind({}); +FiltersActive.args = { + query: '', + filters: ['Option 1', 'Option 2', 'Option 3'], + activeFilters: ['Option 1', 'Option 2'], + disabled: false, +}; + +export const NoResults = Template.bind({}); +NoResults.args = { + query: 'No results term', + filters: ['Option 1', 'Option 2', 'Option 3'], + activeFilters: [], + disabled: false, +}; + +export const Disabled = Template.bind({}); +Disabled.args = { + query: '', + filters: ['Option 1', 'Option 2', 'Option 3'], + activeFilters: [], + disabled: false, +}; \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/sveltekit/src/components/SearchInputWithFilterOptions/SearchInputWithFilterOptions.svelte b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/SearchInputWithFilterOptions/SearchInputWithFilterOptions.svelte new file mode 100644 index 0000000000..6e16f13209 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/SearchInputWithFilterOptions/SearchInputWithFilterOptions.svelte @@ -0,0 +1,53 @@ + + +
    + +
    + {#each filters as filter} + + {/each} +
    +
    + + \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/sveltekit/src/components/SelectableListWithItemDetails/SelectableListWithItemDetails.css b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/SelectableListWithItemDetails/SelectableListWithItemDetails.css new file mode 100644 index 0000000000..99242c3b99 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/SelectableListWithItemDetails/SelectableListWithItemDetails.css @@ -0,0 +1,38 @@ +:root { + --list-bg: #f8f8f8; + --list-border-color: #ccc; + --list-item-bg: #fff; + --list-item-hover-bg: #e0e0e0; + --list-item-selected-bg: #d0ebff; + --text-color: #333; + --details-bg: #f0f8ff; +} + +.selectable-list { + max-height: 300px; + background-color: var(--list-bg); + border: 1px solid var(--list-border-color); +} + +.list-item { + padding: 8px; + background-color: var(--list-item-bg); + color: var(--text-color); + border-bottom: 1px solid var(--list-border-color); + cursor: pointer; + transition: background-color 0.2s; +} + +.list-item:hover { + background-color: var(--list-item-hover-bg); +} + +.list-item.selected { + background-color: var(--list-item-selected-bg); +} + +.item-details { + background-color: var(--details-bg); + padding: 4px; + margin-top: 4px; +} \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/sveltekit/src/components/SelectableListWithItemDetails/SelectableListWithItemDetails.stories.ts b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/SelectableListWithItemDetails/SelectableListWithItemDetails.stories.ts new file mode 100644 index 0000000000..aac086365e --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/SelectableListWithItemDetails/SelectableListWithItemDetails.stories.ts @@ -0,0 +1,86 @@ +import SelectableListWithItemDetails from './SelectableListWithItemDetails.svelte'; +import type { Meta, StoryFn } from '@storybook/svelte'; + +const meta: Meta = { + title: 'component/Lists/SelectableListWithItemDetails', + component: SelectableListWithItemDetails, + tags: ['autodocs'], + argTypes: { + items: { control: 'object' }, + selectedItemId: { control: 'number' }, + detailsOpen: { control: 'boolean' } + }, + parameters: { + layout: 'centered', + viewport: { + viewports: { + smallMobile: { name: 'Small Mobile', styles: { width: '320px', height: '568px' } }, + largeMobile: { name: 'Large Mobile', styles: { width: '414px', height: '896px' } }, + tablet: { name: 'Tablet', styles: { width: '768px', height: '1024px' } }, + desktop: { name: 'Desktop', styles: { width: '1024px', height: '768px' } }, + } + } + } +}; + +export default meta; + +const Template:StoryFn = (args) => ({ + Component:SelectableListWithItemDetails, + props:args, +}); + +export const Default = Template.bind({}); +Default.args = { + items: Array.from({ length: 5 }, (_, i) => ({ + id: i + 1, + text: `Item ${i + 1}`, + details: `Details for Item ${i + 1}` + })), + selectedItemId: null, + detailsOpen: false +}; + +export const ItemSelected = Template.bind({}); +ItemSelected.args = { + items: Array.from({ length: 5 }, (_, i) => ({ + id: i + 1, + text: `Item ${i + 1}`, + details: `Details for Item ${i + 1}` + })), + selectedItemId: 1, + detailsOpen: false +}; + +export const ItemDeselected = Template.bind({}); +ItemDeselected.args = { + items: Array.from({ length: 5 }, (_, i) => ({ + id: i + 1, + text: `Item ${i + 1}`, + details: `Details for Item ${i + 1}` + })), + selectedItemId: null, + detailsOpen: false +}; + +export const DetailsOpened = Template.bind({}); +DetailsOpened.args = { + items: Array.from({ length: 5 }, (_, i) => ({ + id: i + 1, + text: `Item ${i + 1}`, + details: `Details for Item ${i + 1}` + })), + selectedItemId: 1, + detailsOpen: true +}; + +export const DetailsClosed = Template.bind({}); +DetailsClosed.args = { + items: Array.from({ length: 5 }, (_, i) => ({ + id: i + 1, + text: `Item ${i + 1}`, + details: `Details for Item ${i + 1}` + })), + selectedItemId: 1, + detailsOpen: false +}; \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/sveltekit/src/components/SelectableListWithItemDetails/SelectableListWithItemDetails.svelte b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/SelectableListWithItemDetails/SelectableListWithItemDetails.svelte new file mode 100644 index 0000000000..56b873d321 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/SelectableListWithItemDetails/SelectableListWithItemDetails.svelte @@ -0,0 +1,42 @@ + + +
    + {#each items as item (item.id)} +
    toggleSelection(item.id)} + on:keydown={(e)=>{if(e.key === 'Enter' || e.key === ' '){toggleSelection(item.id)}}} + role="menuitem" + aria-current={selectedItemId === item.id} + tabindex="0" + > + {item.text} + {#if selectedItemId === item.id && detailsOpen} +
    {item.details}
    + {/if} +
    + {/each} +
    + + \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/sveltekit/src/components/SignalStrengthIndicator/SignalStrengthIndicator.css b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/SignalStrengthIndicator/SignalStrengthIndicator.css new file mode 100644 index 0000000000..d0dd2cd7e7 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/SignalStrengthIndicator/SignalStrengthIndicator.css @@ -0,0 +1,47 @@ +:root { + --bar-color: #e0e0e0; + --bar-active-color: #4caf50; + --bar-weak-color: #ff9800; +} + +.signal-strength-indicator { + display: flex; + align-items: flex-end; + gap: 0.2rem; +} + +.bar { + width: 0.4rem; + background-color: var(--bar-color); + transition: background-color 0.3s; +} + +.bar1 { + height: 0.6rem; +} + +.bar2 { + height: 1.2rem; +} + +.bar3 { + height: 1.8rem; +} + +.bar4 { + height: 2.4rem; +} + +.bar5 { + height: 3rem; +} + +[data-strength='full'] .bar { + background-color: var(--bar-active-color); +} + +[data-strength='weak'] .bar1, +[data-strength='weak'] .bar2, +[data-strength='weak'] .bar3 { + background-color: var(--bar-weak-color); +} \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/sveltekit/src/components/SignalStrengthIndicator/SignalStrengthIndicator.stories.ts b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/SignalStrengthIndicator/SignalStrengthIndicator.stories.ts new file mode 100644 index 0000000000..ed07e07c88 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/SignalStrengthIndicator/SignalStrengthIndicator.stories.ts @@ -0,0 +1,49 @@ +import SignalStrengthIndicator from './SignalStrengthIndicator.svelte'; +import type { Meta, StoryFn } from '@storybook/svelte'; + +const meta: Meta = { + title: 'component/Indicators/SignalStrengthIndicator', + component: SignalStrengthIndicator, + tags: ['autodocs'], + argTypes: { + strength: { control: 'select', options: ['full', 'weak', 'none'] }, + }, + parameters: { + layout: 'centered', + viewport: { + viewports: { + smallMobile: { name: 'Small Mobile', styles: { width: '320px', height: '568px' } }, + largeMobile: { name: 'Large Mobile', styles: { width: '414px', height: '896px' } }, + tablet: { name: 'Tablet', styles: { width: '768px', height: '1024px' } }, + desktop: { name: 'Desktop', styles: { width: '1024px', height: '768px' } }, + } + } + } +}; + +export default meta; + +const Template:StoryFn = (args) => ({ + Component:SignalStrengthIndicator, + props:args, +}); + +export const Default = Template.bind({}); +Default.args = { + strength: 'none', +}; + +export const FullSignal = Template.bind({}); +FullSignal.args = { + strength: 'full', +}; + +export const WeakSignal = Template.bind({}); +WeakSignal.args = { + strength: 'weak', +}; + +export const NoSignal = Template.bind({}); +NoSignal.args = { + strength: 'none', +}; \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/sveltekit/src/components/SignalStrengthIndicator/SignalStrengthIndicator.svelte b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/SignalStrengthIndicator/SignalStrengthIndicator.svelte new file mode 100644 index 0000000000..707440efc7 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/SignalStrengthIndicator/SignalStrengthIndicator.svelte @@ -0,0 +1,21 @@ + + +
    +
    +
    +
    +
    +
    +
    + + \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/sveltekit/src/components/Slider/Slider.css b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/Slider/Slider.css new file mode 100644 index 0000000000..1585124305 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/Slider/Slider.css @@ -0,0 +1,34 @@ +:root { + --slider-thumb-size: 16px; + --slider-track-height: 4px; + --slider-color: #007bff; + --slider-bg: #ddd; + --slider-bg-disabled: #f5f5f5; +} + +.slider { + appearance: none; + width: 100%; + height: var(--slider-track-height); + background: var(--slider-bg); + outline: none; + opacity: 0.7; + transition: opacity 0.2s; +} + +.slider:disabled { + background: var(--slider-bg-disabled); + cursor: not-allowed; +} + +.slider::-webkit-slider-thumb { + appearance: none; + width: var(--slider-thumb-size); + height: var(--slider-thumb-size); + background: var(--slider-color); + cursor: pointer; +} + +.slider:disabled::-webkit-slider-thumb { + background: #ccc; +} \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/sveltekit/src/components/Slider/Slider.stories.ts b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/Slider/Slider.stories.ts new file mode 100644 index 0000000000..180dc07b89 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/Slider/Slider.stories.ts @@ -0,0 +1,69 @@ +import Slider from './Slider.svelte'; +import type { Meta, StoryFn } from '@storybook/svelte'; + +const meta: Meta = { + title: 'component/Input/Slider', + component: Slider, + tags: ['autodocs'], + argTypes: { + value: { control: 'number' }, + min: { control: 'number' }, + max: { control: 'number' }, + isDisabled: { control: 'boolean' }, + step: { control: 'number' } + }, + parameters: { + layout: 'centered', + viewport: { + viewports: { + smallMobile: { name: 'Small Mobile', styles: { width: '320px', height: '568px' } }, + largeMobile: { name: 'Large Mobile', styles: { width: '414px', height: '896px' } }, + tablet: { name: 'Tablet', styles: { width: '768px', height: '1024px' } }, + desktop: { name: 'Desktop', styles: { width: '1024px', height: '768px' } }, + } + } + } +}; + +export default meta; + +const Template:StoryFn = (args) => ({ + Component: Slider, + props:args, +}); + +export const Default = Template.bind({}); +Default.args = { + value: 50, + min: 0, + max: 100, + isDisabled: false, + step: 1 +}; + +export const Min = Template.bind({}); +Min.args = { + value: 0, + min: 0, + max: 100, + isDisabled: false, + step: 1 +}; + +export const Max = Template.bind({}); +Max.args = { + value: 100, + min: 0, + max: 100, + isDisabled: false, + step: 1 +}; + +export const Disabled = Template.bind({}); +Disabled.args = { + value: 50, + min: 0, + max: 100, + isDisabled: true, + step: 1 +}; \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/sveltekit/src/components/Slider/Slider.svelte b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/Slider/Slider.svelte new file mode 100644 index 0000000000..67960cf945 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/Slider/Slider.svelte @@ -0,0 +1,26 @@ + + + + + \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/sveltekit/src/components/SortableList/SortableList.css b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/SortableList/SortableList.css new file mode 100644 index 0000000000..beaca42c4d --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/SortableList/SortableList.css @@ -0,0 +1,22 @@ +.sortable-list { + list-style-type: none; + padding: 0; + margin: 0; +} + +.sortable-item { + padding: 10px; + margin: 5px 0; + border: 1px solid #ddd; + background-color: #f9f9f9; + cursor: move; +} + +.sortable-item[aria-grabbed="true"] { + background-color: #e0e0e0; +} + +.sortable-item[aria-disabled="true"] { + cursor: not-allowed; + background-color: #f0f0f0; +} \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/sveltekit/src/components/SortableList/SortableList.stories.ts b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/SortableList/SortableList.stories.ts new file mode 100644 index 0000000000..0cd18618e0 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/SortableList/SortableList.stories.ts @@ -0,0 +1,49 @@ +import SortableList from './SortableList.svelte'; +import type { Meta, StoryFn} from '@storybook/svelte'; + +const meta: Meta = { + title: 'Components/Lists/SortableList', + component: SortableList, + tags: ['autodocs'], + argTypes: { + items: { + control: { type: 'object' }, + }, + disabled: { + control: { type: 'boolean' }, + }, + }, +}; + +export default meta; + +const Template: StoryFn = (args) => ({ + Component: SortableList, + props: args, +}); + +const sampleItems = ['Item 1', 'Item 2', 'Item 3', 'Item 4']; + +export const Default = Template.bind({}); +Default.args = { + items: sampleItems, + disabled: false, +}; + +export const Dragging = Template.bind({}); +Dragging.args = { + items: sampleItems, + disabled: false, +}; + +export const Sorted = Template.bind({}); +Sorted.args = { + items: ['Item 2', 'Item 1', 'Item 3', 'Item 4'], + disabled: false, +}; + +export const Disabled = Template.bind({}); +Disabled.args = { + items: sampleItems, + disabled: true, +}; \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/sveltekit/src/components/SortableList/SortableList.svelte b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/SortableList/SortableList.svelte new file mode 100644 index 0000000000..0fba09a0e8 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/SortableList/SortableList.svelte @@ -0,0 +1,60 @@ + + +
      + {#each items as item, index} +
    • handleDragStart(event, index)} + on:dragover={(event) => handleDragOver(event, index)} + on:drop={handleDrop} + tabindex="0" + role="menuitem" + aria-grabbed={draggingIndex === index ? 'true' : 'false'} + aria-disabled={disabled} + > + {item} +
    • + {/each} +
    + + \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/sveltekit/src/components/SortableTable/SortableTable.css b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/SortableTable/SortableTable.css new file mode 100644 index 0000000000..4adef132bc --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/SortableTable/SortableTable.css @@ -0,0 +1,22 @@ +.sortable-table { + width: 100%; + border-collapse: collapse; +} + +.sortable-table th, .sortable-table td { + padding: 8px; + text-align: left; + border-bottom: 1px solid #ddd; +} + +.sortable-table th.sortable { + cursor: pointer; + user-select: none; +} + +.filter input[type="text"] { + margin-bottom: 10px; + padding: 5px; + width: 100%; + box-sizing: border-box; +} \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/sveltekit/src/components/SortableTable/SortableTable.stories.ts b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/SortableTable/SortableTable.stories.ts new file mode 100644 index 0000000000..9affd677e9 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/SortableTable/SortableTable.stories.ts @@ -0,0 +1,60 @@ +import SortableTable from './SortableTable.svelte'; +import type { Meta, StoryFn } from '@storybook/svelte'; +import type { TableColumn, TableRow } from './SortableTable.svelte'; + +const meta: Meta = { + title: 'Components/Lists/SortableTable', + component: SortableTable, + tags: ['autodocs'], + argTypes: { + columns: { + control: { type: 'object' }, + }, + rows: { + control: { type: 'object' }, + }, + }, +}; + +export default meta; + +const Template: StoryFn = (args) => ({ + Component: SortableTable, + props: args, +}); + +const sampleColumns: TableColumn[] = [ + { key: 'id', label: 'ID', sortable: true }, + { key: 'name', label: 'Name', sortable: true }, + { key: 'age', label: 'Age', sortable: true }, +]; + +const sampleRows: TableRow[] = [ + { id: 1, name: 'Alice', age: 30 }, + { id: 2, name: 'Bob', age: 25 }, + { id: 3, name: 'Charlie', age: 35 }, +]; + +export const Default = Template.bind({}); +Default.args = { + columns: sampleColumns, + rows: sampleRows, +}; + +export const Sorting = Template.bind({}); +Sorting.args = { + columns: sampleColumns, + rows: sampleRows, +}; + +export const Filtering = Template.bind({}); +Filtering.args = { + columns: sampleColumns, + rows: sampleRows, +}; + +export const RowSelection = Template.bind({}); +RowSelection.args = { + columns: sampleColumns, + rows: sampleRows, +}; \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/sveltekit/src/components/SortableTable/SortableTable.svelte b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/SortableTable/SortableTable.svelte new file mode 100644 index 0000000000..72aaf5b468 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/SortableTable/SortableTable.svelte @@ -0,0 +1,95 @@ + + + + + +
    + filterRows()} + aria-label="Filter rows" + /> +
    + + + + {#each columns as column} + + {/each} + + + + {#each filterRows() as row (row.id)} + selectRow(row)} + on:keydown={(e) => e.key === "Enter" && selectRow(row)} + tabindex="0" + > + {#each columns as column} + + {/each} + + {/each} + +
    column.sortable && sortRows(column.key)} + on:keydown={(e) => e.key === "Enter" && column.sortable && sortRows(column.key)} + tabindex="0" + aria-sort={sortKey === column.key ? (sortOrder === 'asc' ? 'ascending' : 'descending') : 'none'} + role="columnheader" + class:sortable={column.sortable} + > + {column.label} {#if column.sortable && sortKey === column.key}{sortOrder === 'asc' ? '↑' : '↓'}{/if} +
    {row[column.key]}
    + + \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/sveltekit/src/components/StatusDots/StatusDots.css b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/StatusDots/StatusDots.css new file mode 100644 index 0000000000..ba36e821bc --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/StatusDots/StatusDots.css @@ -0,0 +1,33 @@ +:root { + --dot-size: 1rem; + --dot-online-color: #4caf50; + --dot-offline-color: #f44336; + --dot-busy-color: #ff9800; + --dot-idle-color: #9e9e9e; +} + +.status-dots { + display: inline-flex; + align-items: center; + justify-content: center; +} + +.dot { + width: var(--dot-size); + height: var(--dot-size); + border-radius: 50%; + background-color: var(--dot-offline-color); + transition: background-color 0.3s; +} + +[data-status='online'] .dot { + background-color: var(--dot-online-color); +} + +[data-status='busy'] .dot { + background-color: var(--dot-busy-color); +} + +[data-status='idle'] .dot { + background-color: var(--dot-idle-color); +} \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/sveltekit/src/components/StatusDots/StatusDots.stories.ts b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/StatusDots/StatusDots.stories.ts new file mode 100644 index 0000000000..742c612c32 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/StatusDots/StatusDots.stories.ts @@ -0,0 +1,54 @@ +import StatusDots from './StatusDots.svelte'; +import type { Meta, StoryFn } from '@storybook/svelte'; + +const meta: Meta = { + title: 'component/Indicators/StatusDots', + component: StatusDots, + tags: ['autodocs'], + argTypes: { + status: { control: 'select', options: ['online', 'offline', 'busy', 'idle'] }, + }, + parameters: { + layout: 'centered', + viewport: { + viewports: { + smallMobile: { name: 'Small Mobile', styles: { width: '320px', height: '568px' } }, + largeMobile: { name: 'Large Mobile', styles: { width: '414px', height: '896px' } }, + tablet: { name: 'Tablet', styles: { width: '768px', height: '1024px' } }, + desktop: { name: 'Desktop', styles: { width: '1024px', height: '768px' } }, + } + } + } +}; + +export default meta; + +const Template:StoryFn = (args) => ({ + Component:StatusDots, + props:args, +}); + +export const Default = Template.bind({}); +Default.args = { + status: 'offline', +}; + +export const Online = Template.bind({}); +Online.args = { + status: 'online', +}; + +export const Offline = Template.bind({}); +Offline.args = { + status: 'offline', +}; + +export const Busy = Template.bind({}); +Busy.args = { + status: 'busy', +}; + +export const Idle = Template.bind({}); +Idle.args = { + status: 'idle', +}; \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/sveltekit/src/components/StatusDots/StatusDots.svelte b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/StatusDots/StatusDots.svelte new file mode 100644 index 0000000000..d82dc7b486 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/StatusDots/StatusDots.svelte @@ -0,0 +1,17 @@ + + +
    +
    +
    + + \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/sveltekit/src/components/Stepper/Stepper.css b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/Stepper/Stepper.css new file mode 100644 index 0000000000..b84010fb3b --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/Stepper/Stepper.css @@ -0,0 +1,48 @@ +:root { + --step-size: 2rem; + --step-completed-color: #4caf50; + --step-active-color: #2196f3; + --step-disabled-color: #9e9e9e; + --step-separator-color: #cccccc; + --step-label-color: #000000; +} + +.stepper { + display: flex; + align-items: center; +} + +.step { + display: flex; + align-items: center; + flex-direction: column; + text-align: center; +} + +.step-circle { + width: var(--step-size); + height: var(--step-size); + border-radius: 50%; + background-color: var(--step-disabled-color); + transition: background-color 0.3s; +} + +.step-label { + margin-top: 0.5rem; + color: var(--step-label-color); +} + +[data-status='completed'] .step-circle { + background-color: var(--step-completed-color); +} + +[data-status='active'] .step-circle { + background-color: var(--step-active-color); +} + +.step-separator { + flex-grow: 1; + height: 0.2rem; + background-color: var(--step-separator-color); + margin: 0 0.5rem; +} \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/sveltekit/src/components/Stepper/Stepper.stories.ts b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/Stepper/Stepper.stories.ts new file mode 100644 index 0000000000..6e07e7ede6 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/Stepper/Stepper.stories.ts @@ -0,0 +1,65 @@ +import Stepper from './Stepper.svelte'; +import type { Meta, StoryFn } from '@storybook/svelte'; + +const meta: Meta = { + title: 'component/Indicators/Stepper', + component: Stepper, + tags: ['autodocs'], + argTypes: { + steps: { control: 'object' }, + }, + parameters: { + layout: 'centered', + viewport: { + viewports: { + smallMobile: { name: 'Small Mobile', styles: { width: '320px', height: '568px' } }, + largeMobile: { name: 'Large Mobile', styles: { width: '414px', height: '896px' } }, + tablet: { name: 'Tablet', styles: { width: '768px', height: '1024px' } }, + desktop: { name: 'Desktop', styles: { width: '1024px', height: '768px' } }, + } + } + } +}; + +export default meta; + +const Template:StoryFn = (args) => ({ + Component:Stepper, + props:args, +}); + +export const Default = Template.bind({}); +Default.args = { + steps: [ + { label: 'Step 1', status: 'completed' }, + { label: 'Step 2', status: 'active' }, + { label: 'Step 3', status: 'disabled' }, + ], +}; + +export const Completed = Template.bind({}); +Completed.args = { + steps: [ + { label: 'Step 1', status: 'completed' }, + { label: 'Step 2', status: 'completed' }, + { label: 'Step 3', status: 'completed' }, + ], +}; + +export const Active = Template.bind({}); +Active.args = { + steps: [ + { label: 'Step 1', status: 'completed' }, + { label: 'Step 2', status: 'active' }, + { label: 'Step 3', status: 'disabled' }, + ], +}; + +export const Disabled = Template.bind({}); +Disabled.args = { + steps: [ + { label: 'Step 1', status: 'disabled' }, + { label: 'Step 2', status: 'disabled' }, + { label: 'Step 3', status: 'disabled' }, + ], +}; \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/sveltekit/src/components/Stepper/Stepper.svelte b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/Stepper/Stepper.svelte new file mode 100644 index 0000000000..bde8b804c2 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/Stepper/Stepper.svelte @@ -0,0 +1,20 @@ + + + + + \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/sveltekit/src/components/SystemAlertGlobalNotificationBar/SystemAlertGlobalNotificationBar.css b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/SystemAlertGlobalNotificationBar/SystemAlertGlobalNotificationBar.css new file mode 100644 index 0000000000..1c351686cb --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/SystemAlertGlobalNotificationBar/SystemAlertGlobalNotificationBar.css @@ -0,0 +1,46 @@ +:root { + --notification-padding: 1rem; + --notification-border-radius: 4px; + --success-bg-color: #d4edda; + --success-text-color: #155724; + --error-bg-color: #f8d7da; + --error-text-color: #721c24; + --warning-bg-color: #fff3cd; + --warning-text-color: #856404; + --info-bg-color: #d1ecf1; + --info-text-color: #0c5460; +} + +.notification-bar { + display: flex; + align-items: center; + justify-content: center; + padding: var(--notification-padding); + border-radius: var(--notification-border-radius); + width: 100%; + box-sizing: border-box; +} + +.notification-message { + font-size: 1rem; +} + +[data-type='success'] { + background-color: var(--success-bg-color); + color: var(--success-text-color); +} + +[data-type='error'] { + background-color: var(--error-bg-color); + color: var(--error-text-color); +} + +[data-type='warning'] { + background-color: var(--warning-bg-color); + color: var(--warning-text-color); +} + +[data-type='info'] { + background-color: var(--info-bg-color); + color: var(--info-text-color); +} \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/sveltekit/src/components/SystemAlertGlobalNotificationBar/SystemAlertGlobalNotificationBar.stories.ts b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/SystemAlertGlobalNotificationBar/SystemAlertGlobalNotificationBar.stories.ts new file mode 100644 index 0000000000..baa60acc9d --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/SystemAlertGlobalNotificationBar/SystemAlertGlobalNotificationBar.stories.ts @@ -0,0 +1,63 @@ +import SystemAlertGlobalNotificationBar from './SystemAlertGlobalNotificationBar.svelte'; +import type { Meta, StoryFn } from '@storybook/svelte'; + +const meta: Meta = { + title: 'component/Indicators/SystemAlertGlobalNotificationBar', + component: SystemAlertGlobalNotificationBar, + tags: ['autodocs'], + argTypes: { + message: { control: 'text' }, + type: { + control: { type: 'select' }, + options: ['success', 'error', 'warning', 'info'] + }, + }, + parameters: { + layout: 'centered', + viewport: { + viewports: { + smallMobile: { name: 'Small Mobile', styles: { width: '320px', height: '568px' } }, + largeMobile: { name: 'Large Mobile', styles: { width: '414px', height: '896px' } }, + tablet: { name: 'Tablet', styles: { width: '768px', height: '1024px' } }, + desktop: { name: 'Desktop', styles: { width: '1024px', height: '768px' } }, + } + } + } +}; + +export default meta; + +const Template:StoryFn = (args) => ({ + Component: SystemAlertGlobalNotificationBar, + props:args, +}); + +export const Default = Template.bind({}); +Default.args = { + message: 'This is an information alert!', + type: 'info' +}; + +export const Success = Template.bind({}); +Success.args = { + message: 'Operation completed successfully!', + type: 'success' +}; + +export const Error = Template.bind({}); +Error.args = { + message: 'An error has occured!', + type: 'error' +}; + +export const Warning = Template.bind({}); +Warning.args = { + message: 'Please be aware of the following warning!', + type: 'warning' +}; + +export const Info = Template.bind({}); +Info.args = { +message: 'This is an information alert!', + type: 'info' +}; \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/sveltekit/src/components/SystemAlertGlobalNotificationBar/SystemAlertGlobalNotificationBar.svelte b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/SystemAlertGlobalNotificationBar/SystemAlertGlobalNotificationBar.svelte new file mode 100644 index 0000000000..36d6a9d93a --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/SystemAlertGlobalNotificationBar/SystemAlertGlobalNotificationBar.svelte @@ -0,0 +1,13 @@ + + + + + \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/sveltekit/src/components/Tabs/Tabs.css b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/Tabs/Tabs.css new file mode 100644 index 0000000000..09071afb33 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/Tabs/Tabs.css @@ -0,0 +1,35 @@ +.tabs { + display: flex; + flex-direction: column; +} + +.tab-list { + display: flex; +} + +.tab-button { + padding: 10px; + margin-right: 2px; + background-color: #f1f1f1; + border: none; + cursor: pointer; +} + +.tab-button[aria-selected="true"] { + background-color: #ddd; + font-weight: bold; +} + +.tab-button[aria-disabled="true"] { + cursor: not-allowed; + opacity: 0.5; +} + +.tab-button:hover:not([aria-disabled="true"]) { + background-color: #ccc; +} + +.tab-content { + padding: 10px; + border-top: 1px solid #ddd; +} \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/sveltekit/src/components/Tabs/Tabs.stories.ts b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/Tabs/Tabs.stories.ts new file mode 100644 index 0000000000..c7bb6ddde0 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/Tabs/Tabs.stories.ts @@ -0,0 +1,59 @@ +import Tabs from './Tabs.svelte'; +import type { Meta, StoryFn } from '@storybook/svelte'; + +const meta: Meta = { + title: 'Components/Lists/Tabs', + component: Tabs, + tags: ['autodocs'], + argTypes: { + tabs: { + control: { type: 'object' }, + }, + activeIndex: { + control: { type: 'number' }, + }, + }, +}; + +export default meta; + +const Template: StoryFn = (args) => ({ + Component: Tabs, + props: args, +}); + +const sampleTabs = [ + { label: 'Tab 1', content: 'Content for Tab 1' }, + { label: 'Tab 2', content: 'Content for Tab 2' }, + { label: 'Tab 3', content: 'Content for Tab 3', disabled: true }, +]; + +export const Default = Template.bind({}); +Default.args = { + tabs: sampleTabs, + activeIndex: 0, +}; + +export const Active = Template.bind({}); +Active.args = { + tabs: sampleTabs, + activeIndex: 1, +}; + +export const Inactive = Template.bind({}); +Inactive.args = { + tabs: sampleTabs, + activeIndex: 2, +}; + +export const Disabled = Template.bind({}); +Disabled.args = { + tabs: sampleTabs, + activeIndex: 0, +}; + +export const Hover = Template.bind({}); +Hover.args = { + tabs: sampleTabs, + activeIndex: 0, +}; \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/sveltekit/src/components/Tabs/Tabs.svelte b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/Tabs/Tabs.svelte new file mode 100644 index 0000000000..e825594140 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/Tabs/Tabs.svelte @@ -0,0 +1,37 @@ + + +
    +
    + {#each tabs as { label, disabled }, index} + + {/each} +
    +
    + {#if tabs.length > 0} + {tabs[activeIndex]?.content} + {/if} +
    +
    + + \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/sveltekit/src/components/TaskCompletionCheckList/TaskCompletionCheckList.css b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/TaskCompletionCheckList/TaskCompletionCheckList.css new file mode 100644 index 0000000000..48df96e446 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/TaskCompletionCheckList/TaskCompletionCheckList.css @@ -0,0 +1,23 @@ +:root { + --checklist-padding: 1rem; + --checklist-item-margin: 0.5rem 0; + --checklist-label-color: #333; + --checklist-label-font-size: 1rem; +} + +.checklist { + list-style: none; + padding: var(--checklist-padding); +} + +.checklist li { + margin: var(--checklist-item-margin); + display: flex; + align-items: center; +} + +.checklist label { + margin-left: 0.5rem; + color: var(--checklist-label-color); + font-size: var(--checklist-label-font-size); +} \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/sveltekit/src/components/TaskCompletionCheckList/TaskCompletionCheckList.stories.ts b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/TaskCompletionCheckList/TaskCompletionCheckList.stories.ts new file mode 100644 index 0000000000..a3e58a3b80 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/TaskCompletionCheckList/TaskCompletionCheckList.stories.ts @@ -0,0 +1,65 @@ +import TaskCompletionCheckList from './TaskCompletionCheckList.svelte'; +import type { Meta, StoryFn } from '@storybook/svelte'; + +const meta: Meta = { + title: 'component/Indicators/TaskCompletionCheckList', + component: TaskCompletionCheckList, + tags: ['autodocs'], + argTypes: { + tasks: { control: 'object' } + }, + parameters: { + layout: 'centered', + viewport: { + viewports: { + smallMobile: { name: 'Small Mobile', styles: { width: '320px', height: '568px' } }, + largeMobile: { name: 'Large Mobile', styles: { width: '414px', height: '896px' } }, + tablet: { name: 'Tablet', styles: { width: '768px', height: '1024px' } }, + desktop: { name: 'Desktop', styles: { width: '1024px', height: '768px' } }, + } + } + } +}; + +export default meta; + +const Template:StoryFn = (args) => ({ + Component:TaskCompletionCheckList, + props:args, +}); + +export const Default = Template.bind({}); +Default.args = { + tasks: [ + { id: 1, label: 'Task 1', completed: false }, + { id: 2, label: 'Task 2', completed: false }, + { id: 3, label: 'Task 3', completed: false } + ] +}; + +export const Checked = Template.bind({}); +Checked.args = { + tasks: [ + { id: 1, label: 'Task 1', completed: true }, + { id: 2, label: 'Task 2', completed: true }, + { id: 3, label: 'Task 3', completed: true } + ] +}; + +export const Unchecked = Template.bind({}); +Unchecked.args = { + tasks: [ + { id: 1, label: 'Task 1', completed: false }, + { id: 2, label: 'Task 2', completed: false }, + { id: 3, label: 'Task 3', completed: false } + ] +}; + +export const PartiallyComplete = Template.bind({}); +PartiallyComplete.args = { + tasks: [ + { id: 1, label: 'Task 1', completed: true }, + { id: 2, label: 'Task 2', completed: false }, + { id: 3, label: 'Task 3', completed: true } + ] +}; \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/sveltekit/src/components/TaskCompletionCheckList/TaskCompletionCheckList.svelte b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/TaskCompletionCheckList/TaskCompletionCheckList.svelte new file mode 100644 index 0000000000..3767b301d2 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/TaskCompletionCheckList/TaskCompletionCheckList.svelte @@ -0,0 +1,34 @@ + + +
      + {#each tasks as { id, label, completed }} +
    • + toggleTaskCompletion(id)} + /> + +
    • + {/each} +
    + + \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/sveltekit/src/components/Textarea/Textarea.css b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/Textarea/Textarea.css new file mode 100644 index 0000000000..95922f4699 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/Textarea/Textarea.css @@ -0,0 +1,27 @@ +:root { + --textarea-border-color: #ccc; + --textarea-focus-border-color: #007bff; + --textarea-bg-color: #fff; + --textarea-disabled-bg-color: #f7f7f7; +} + +.textarea { + width: 100%; + padding: 10px; + border: 1px solid var(--textarea-border-color); + border-radius: 4px; + background-color: var(--textarea-bg-color); + font-size: 16px; + resize: vertical; +} + +.textarea:focus { + border-color: var(--textarea-focus-border-color); + outline: none; +} + +.textarea:disabled { + background-color: var(--textarea-disabled-bg-color); + opacity: 0.7; + cursor: not-allowed; +} \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/sveltekit/src/components/Textarea/Textarea.stories.ts b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/Textarea/Textarea.stories.ts new file mode 100644 index 0000000000..edfeb2a904 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/Textarea/Textarea.stories.ts @@ -0,0 +1,42 @@ +import Textarea from './Textarea.svelte'; +import type { Meta, StoryFn } from '@storybook/svelte'; + +const meta: Meta + + \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/sveltekit/src/components/TimelineList/TimelineList.css b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/TimelineList/TimelineList.css new file mode 100644 index 0000000000..6bfc4557da --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/TimelineList/TimelineList.css @@ -0,0 +1,31 @@ +.timeline { + list-style: none; + padding: 0; + margin: 0; +} + +.timeline-event { + margin-bottom: 20px; + position: relative; +} + +.event-content { + background-color: #f9f9f9; + padding: 15px; + border-radius: 5px; + transition: background-color 0.3s; + cursor: pointer; +} + +.event-content.active { + background-color: #e0f7fa; + font-weight: bold; +} + +.event-content.completed { + background-color: #c8e6c9; +} + +.event-content:hover:not(.completed) { + background-color: #eee; +} \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/sveltekit/src/components/TimelineList/TimelineList.stories.ts b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/TimelineList/TimelineList.stories.ts new file mode 100644 index 0000000000..f507cd0bbe --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/TimelineList/TimelineList.stories.ts @@ -0,0 +1,51 @@ +import TimelineList from './TimelineList.svelte'; +import type { Meta, StoryFn } from '@storybook/svelte'; + +const meta: Meta = { + title: 'Components/Lists/TimelineList', + component: TimelineList, + tags: ['autodocs'], + argTypes: { + events: { + control: { type: 'object' }, + }, + }, +}; + +export default meta; + +const Template: StoryFn = (args) => ({ + Component: TimelineList, + props: args, +}); + +const sampleEvents = [ + { title: 'Event 1', description: 'Description for Event 1', completed: true, active: false }, + { title: 'Event 2', description: 'Description for Event 2', completed: false, active: true }, + { title: 'Event 3', description: 'Description for Event 3', completed: false, active: false }, +]; + +export const Default = Template.bind({}); +Default.args = { + events: sampleEvents, +}; + +export const Active = Template.bind({}); +Active.args = { + events: sampleEvents.map((event, i) => ({ ...event, active: i === 1 })), +}; + +export const Completed = Template.bind({}); +Completed.args = { + events: sampleEvents.map((event) => ({ ...event, completed: true })), +}; + +export const Hover = Template.bind({}); +Hover.args = { + events: sampleEvents, +}; + +export const Inactive = Template.bind({}); +Inactive.args = { + events: sampleEvents.map((event) => ({ ...event, active: false })), +}; \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/sveltekit/src/components/TimelineList/TimelineList.svelte b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/TimelineList/TimelineList.svelte new file mode 100644 index 0000000000..7368d4d4b9 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/TimelineList/TimelineList.svelte @@ -0,0 +1,33 @@ + + +
      + {#each events as { title, description, completed, active }, index} +
    • +
      handleClick(index)} + on:keydown={(event) => event.key === 'Enter' && handleClick(index)} + role='button' + > +

      {title}

      +

      {description}

      +
      +
    • + {/each} +
    + + \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/sveltekit/src/components/Toast/Toast.css b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/Toast/Toast.css new file mode 100644 index 0000000000..abfe86cef7 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/Toast/Toast.css @@ -0,0 +1,58 @@ +:root { + --toast-padding: 1rem; + --toast-margin: 0.5rem; + --toast-border-radius: 4px; + --toast-font-size: 1rem; + --toast-success-bg: #d4edda; + --toast-error-bg: #f8d7da; + --toast-warning-bg: #fff3cd; + --toast-info-bg: #d1ecf1; + --toast-color: #333; + --toast-button-color: #999; +} + +.toast { + position: fixed; + bottom: var(--toast-margin); + left: 50%; + transform: translateX(-50%); + padding: var(--toast-padding); + border-radius: var(--toast-border-radius); + font-size: var(--toast-font-size); + color: var(--toast-color); + display: flex; + justify-content: space-between; + align-items: center; + width: calc(100% - 2 * var(--toast-margin)); + max-width: 500px; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); +} + +.toast p { + margin: 0; + flex-grow: 1; +} + +.toast button { + background: none; + border: none; + color: var(--toast-button-color); + cursor: pointer; + font-size: 1.2rem; +} + +.toast--success { + background-color: var(--toast-success-bg); +} + +.toast--error { + background-color: var(--toast-error-bg); +} + +.toast--warning { + background-color: var(--toast-warning-bg); +} + +.toast--info { + background-color: var(--toast-info-bg); +} \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/sveltekit/src/components/Toast/Toast.stories.ts b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/Toast/Toast.stories.ts new file mode 100644 index 0000000000..aa1953ac7a --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/Toast/Toast.stories.ts @@ -0,0 +1,74 @@ +import Toast from './Toast.svelte'; +import type { Meta, StoryFn } from '@storybook/svelte'; + +const meta: Meta = { + title: 'component/Indicators/Toast', + component: Toast, + tags: ['autodocs'], + argTypes: { + message: { control: 'text' }, + type: { control: 'select', options: ['success', 'error', 'warning', 'info'] }, + visible: { control: 'boolean' } + }, + parameters: { + layout: 'centered', + viewport: { + viewports: { + smallMobile: { name: 'Small Mobile', styles: { width: '320px', height: '568px' } }, + largeMobile: { name: 'Large Mobile', styles: { width: '414px', height: '896px' } }, + tablet: { name: 'Tablet', styles: { width: '768px', height: '1024px' } }, + desktop: { name: 'Desktop', styles: { width: '1024px', height: '768px' } }, + } + } + } +}; + +export default meta; + +const Template:StoryFn = (args) => ({ + Component: Toast, + props:args, +}) + +export const Default = Template.bind ({}); +Default.args = { + message: 'This is a default toast message.', + type: 'info', + visible: true +} + +export const Success = Template.bind ({}); +Success.args = { + message: 'Operation completed successfully!', + type: 'success', + visible: true +}; + + +export const Error = Template.bind ({}); +Error.args = { + message: 'There was an error processing your request.', + type: 'error', + visible: true +}; + +export const Warning = Template.bind ({}); +Warning.args = { + message: 'This is a warning message.', + type: 'warning', + visible: true +}; + +export const Info = Template.bind ({}); +Info.args = { + message: 'This is some informative text.', + type: 'info', + visible: true +}; + +export const Dismissed = Template.bind ({}); +Dismissed.args = { + message: 'This message has been dismissed.', + type: 'info', + visible: false +}; \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/sveltekit/src/components/Toast/Toast.svelte b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/Toast/Toast.svelte new file mode 100644 index 0000000000..4bdd67f1ea --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/Toast/Toast.svelte @@ -0,0 +1,27 @@ + + +{#if visible} + +{/if} + + \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/sveltekit/src/components/ToggleSwitch/ToggleSwitch.css b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/ToggleSwitch/ToggleSwitch.css new file mode 100644 index 0000000000..891ae4be6b --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/ToggleSwitch/ToggleSwitch.css @@ -0,0 +1,58 @@ +:root { + --toggle-width: 50px; + --toggle-height: 25px; + --toggle-bg-color: #ccc; + --toggle-checked-bg-color: #4caf50; + --toggle-slider-color: #fff; + --toggle-disabled-opacity: 0.5; +} + +.toggle-switch { + position: relative; + display: inline-block; + width: var(--toggle-width); + height: var(--toggle-height); +} + +.toggle-switch input { + opacity: 0; + width: 0; + height: 0; +} + +.slider { + position: absolute; + cursor: pointer; + top: 0; + left: 0; + right: 0; + bottom: 0; + background-color: var(--toggle-bg-color); + transition: 0.4s; + border-radius: var(--toggle-height); +} + +.slider:before { + position: absolute; + content: ""; + height: calc(var(--toggle-height) - 4px); + width: calc(var(--toggle-height) - 4px); + left: 2px; + bottom: 2px; + background-color: var(--toggle-slider-color); + transition: 0.4s; + border-radius: 50%; +} + +input:checked + .slider { + background-color: var(--toggle-checked-bg-color); +} + +input:checked + .slider:before { + transform: translateX(calc(var(--toggle-width) - var(--toggle-height))); +} + +input:disabled + .slider { + opacity: var(--toggle-disabled-opacity); + cursor: not-allowed; +} \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/sveltekit/src/components/ToggleSwitch/ToggleSwitch.stories.ts b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/ToggleSwitch/ToggleSwitch.stories.ts new file mode 100644 index 0000000000..412d5e4505 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/ToggleSwitch/ToggleSwitch.stories.ts @@ -0,0 +1,55 @@ +import ToggleSwitch from './ToggleSwitch.svelte'; +import type { Meta, StoryFn } from '@storybook/svelte'; + +const meta: Meta = { + title: 'component/Forms/ToggleSwitch', + component: ToggleSwitch, + tags: ['autodocs'], + argTypes: { + checked: { control: 'boolean' }, + disabled: { control: 'boolean' }, + }, + parameters: { + layout: 'fullscreen', + viewport: { + viewports: { + smallMobile: { name: 'Small Mobile', styles: { width: '320px', height: '568px' } }, + largeMobile: { name: 'Large Mobile', styles: { width: '414px', height: '896px' } }, + tablet: { name: 'Tablet', styles: { width: '768px', height: '1024px' } }, + desktop: { name: 'Desktop', styles: { width: '1024px', height: '768px' } }, + } + } + } +}; + +export default meta; + +const Template:StoryFn = (args) => ({ + Component: ToggleSwitch, + props:args +}); + +export const Default = Template.bind({}); +Default.args = { + checked: false, + disabled: false, +}; + + +export const On = Template.bind({}); +On.args = { + checked: true, + disabled: false, +}; + +export const Off = Template.bind({}); +Off.args = { + checked: false, + disabled: false, +}; + +export const Disabled = Template.bind({}); +Disabled.args = { + checked: false, + disabled: true, +}; \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/sveltekit/src/components/ToggleSwitch/ToggleSwitch.svelte b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/ToggleSwitch/ToggleSwitch.svelte new file mode 100644 index 0000000000..30055b7032 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/ToggleSwitch/ToggleSwitch.svelte @@ -0,0 +1,27 @@ + + + + + \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/sveltekit/src/components/TreeviewList/TreeviewList.css b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/TreeviewList/TreeviewList.css new file mode 100644 index 0000000000..93afce63ab --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/TreeviewList/TreeviewList.css @@ -0,0 +1,30 @@ +.treeview { + list-style: none; + padding: 0; + margin: 0; +} + +.tree-node { + margin-bottom: 10px; + position: relative; +} + +.node-content { + background-color: #f9f9f9; + padding: 10px; + border-radius: 5px; + transition: background-color 0.3s; + cursor: pointer; +} + +.node-content.expanded { + background-color: #e0f7fa; +} + +.node-content.selected { + background-color: #c8e6c9; +} + +.node-content:hover { + background-color: #eee; +} \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/sveltekit/src/components/TreeviewList/TreeviewList.stories.ts b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/TreeviewList/TreeviewList.stories.ts new file mode 100644 index 0000000000..5ca4f30c2c --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/TreeviewList/TreeviewList.stories.ts @@ -0,0 +1,56 @@ +import TreeviewList from './TreeviewList.svelte'; +import type { Meta, StoryFn } from '@storybook/svelte'; + +const meta: Meta = { + title: 'Components/Lists/TreeviewList', + component: TreeviewList, + tags: ['autodocs'], + argTypes: { + nodes: { + control: { type: 'object' }, + }, + }, +}; + +export default meta; + +const Template: StoryFn = (args) => ({ + Component: TreeviewList, + props: args, +}); + +const sampleNodes = [ + { label: 'Node 1', expanded: true, selected: false, children: [ + { label: 'Child 1.1', expanded: false, selected: false }, + { label: 'Child 1.2', expanded: false, selected: false } + ]}, + { label: 'Node 2', expanded: false, selected: false, children: [ + { label: 'Child 2.1', expanded: false, selected: false } + ]}, + { label: 'Node 3', expanded: false, selected: false } +]; + +export const Default = Template.bind({}); +Default.args = { + nodes: sampleNodes, +}; + +export const NodeExpanded = Template.bind({}); +NodeExpanded.args = { + nodes: sampleNodes.map((node, i) => ({ ...node, expanded: i === 0 })), +}; + +export const NodeCollapsed = Template.bind({}); +NodeCollapsed.args = { + nodes: sampleNodes.map((node) => ({ ...node, expanded: false })), +}; + +export const Hover = Template.bind({}); +Hover.args = { + nodes: sampleNodes, +}; + +export const Selected = Template.bind({}); +Selected.args = { + nodes: sampleNodes.map((node, i) => ({ ...node, selected: i === 1 })), +}; \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/sveltekit/src/components/TreeviewList/TreeviewList.svelte b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/TreeviewList/TreeviewList.svelte new file mode 100644 index 0000000000..05167b091d --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/TreeviewList/TreeviewList.svelte @@ -0,0 +1,50 @@ + + +
      + {#each nodes as { label, children, expanded, selected }, index} +
    • +
      toggleNode(index)} + on:keydown={(event) => event.key === 'Enter' && toggleNode(index)} + > + selectNode(index)} on:keydown={(event) => event.key === ' ' && selectNode(index)}>{label} +
      + {#if expanded && children} +
        + {#each children as child} + + {/each} +
      + {/if} +
    • + {/each} +
    + + \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/sveltekit/src/components/Upload/Upload.css b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/Upload/Upload.css new file mode 100644 index 0000000000..6889e182d2 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/Upload/Upload.css @@ -0,0 +1,42 @@ +:root { + --upload-padding: 1rem; + --upload-margin: 0.5rem; + --upload-border-radius: 4px; + --upload-font-size: 1rem; + --upload-bg: #f7f7f7; + --upload-color: #333; + --upload-progress-bg: #e0e0e0; + --upload-progress-color: #76c7c0; +} + +.upload { + padding: var(--upload-padding); + margin: var(--upload-margin); + border-radius: var(--upload-border-radius); + font-size: var(--upload-font-size); + background-color: var(--upload-bg); + color: var(--upload-color); + width: 100%; + max-width: 500px; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); +} + +.upload p { + margin: 0 0 0.5rem 0; +} + +.upload progress { + width: 100%; + height: 20px; + appearance: none; +} + +.upload progress::-webkit-progress-bar { + background-color: var(--upload-progress-bg); + border-radius: var(--upload-border-radius); +} + +.upload progress::-webkit-progress-value { + background-color: var(--upload-progress-color); + border-radius: var(--upload-border-radius); +} \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/sveltekit/src/components/Upload/Upload.stories.ts b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/Upload/Upload.stories.ts new file mode 100644 index 0000000000..1039002873 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/Upload/Upload.stories.ts @@ -0,0 +1,73 @@ +import Upload from './Upload.svelte'; +import type { Meta, StoryFn } from '@storybook/svelte'; + +const meta: Meta = { + title: 'component/Indicators/Upload', + component: Upload, + tags: ['autodocs'], + argTypes: { + status: { control: 'select', options: ['uploading', 'downloading', 'completed', 'paused', 'failed'] }, + fileName: { control: 'text' }, + progress: { control: 'number' } + }, + parameters: { + layout: 'centered', + viewport: { + viewports: { + smallMobile: { name: 'Small Mobile', styles: { width: '320px', height: '568px' } }, + largeMobile: { name: 'Large Mobile', styles: { width: '414px', height: '896px' } }, + tablet: { name: 'Tablet', styles: { width: '768px', height: '1024px' } }, + desktop: { name: 'Desktop', styles: { width: '1024px', height: '768px' } }, + } + } + } +}; + +export default meta; + +const Template:StoryFn = (args) => ({ + Component: Upload, + props:args, +}); + +export const Default = Template.bind({}); +Default.args = { + fileName: 'example.txt', + status: 'uploading', + progress: 50 +}; + +export const Uploading = Template.bind({}); +Uploading.args = { + fileName: 'example.txt', + status: 'uploading', + progress: 50 +}; + +export const Downloading = Template.bind({}); +Downloading.args = { + fileName: 'downloading_file.txt', + status: 'downloading', + progress: 60 +}; + +export const Completed = Template.bind({}); +Completed.args = { + fileName: 'completed_file.txt', + status: 'completed', + progress: 100 +}; + +export const Paused = Template.bind({}); +Paused.args = { + fileName: 'paused_file.txt', + status: 'paused', + progress: 70 +}; + +export const Failed = Template.bind({}); +Failed.args = { + fileName: 'paused_file.txt', + status: 'paused', + progress: 70 +}; \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/sveltekit/src/components/Upload/Upload.svelte b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/Upload/Upload.svelte new file mode 100644 index 0000000000..78382cf27a --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/Upload/Upload.svelte @@ -0,0 +1,25 @@ + + +
    +

    {fileName}

    +

    {statuses[status]}

    + {#if status === 'uploading' || status === 'downloading'} + + {/if} +
    + + \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/sveltekit/src/components/ValidationMessages/ValidationMessages.css b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/ValidationMessages/ValidationMessages.css new file mode 100644 index 0000000000..ad6ca35131 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/ValidationMessages/ValidationMessages.css @@ -0,0 +1,31 @@ +:root { + --success-bg-color: #d4edda; + --success-text-color: #155724; + --error-bg-color: #f8d7da; + --error-text-color: #721c24; + --warning-bg-color: #fff3cd; + --warning-text-color: #856404; + --padding: 1rem; + --border-radius: 4px; +} + +.validation-message { + padding: var(--padding); + border-radius: var(--border-radius); + margin: 0.5rem 0; +} + +.validation-message.success { + background-color: var(--success-bg-color); + color: var(--success-text-color); +} + +.validation-message.error { + background-color: var(--error-bg-color); + color: var(--error-text-color); +} + +.validation-message.warning { + background-color: var(--warning-bg-color); + color: var(--warning-text-color); +} \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/sveltekit/src/components/ValidationMessages/ValidationMessages.stories.ts b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/ValidationMessages/ValidationMessages.stories.ts new file mode 100644 index 0000000000..ecd73d0b23 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/ValidationMessages/ValidationMessages.stories.ts @@ -0,0 +1,57 @@ +import ValidationMessages from './ValidationMessages.svelte'; +import type { Meta, StoryFn } from '@storybook/svelte'; + +const meta: Meta = { + title: 'component/Forms/ValidationMessages', + component: ValidationMessages, + tags: ['autodocs'], + argTypes: { + message: { control: 'text' }, + type: { + control: { type: 'select' }, + options: ['success', 'error', 'warning'] + }, + }, + parameters: { + layout: 'fullscreen', + viewport: { + viewports: { + smallMobile: { name: 'Small Mobile', styles: { width: '320px', height: '568px' } }, + largeMobile: { name: 'Large Mobile', styles: { width: '414px', height: '896px' } }, + tablet: { name: 'Tablet', styles: { width: '768px', height: '1024px' } }, + desktop: { name: 'Desktop', styles: { width: '1024px', height: '768px' } }, + } + } + } +}; + +export default meta; + +const Template:StoryFn = (args) => ({ + Component: ValidationMessages, + props:args, +}); + +export const Default = Template.bind({}); +Default.args = { + message: 'This is a validation message.', + type: 'success', +}; + +export const Success = Template.bind({}); +Success.args = { + message: 'Operation was successful!', + type: 'success', +}; + +export const Error = Template.bind({}); +Error.args = { + message: 'There was an error processing your request.', + type: 'error', +}; + +export const Warning = Template.bind({}); +Warning.args = { + message: 'This is a warning. Please check your input.', + type: 'warning', +}; \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/sveltekit/src/components/ValidationMessages/ValidationMessages.svelte b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/ValidationMessages/ValidationMessages.svelte new file mode 100644 index 0000000000..3fcb0da7f2 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/ValidationMessages/ValidationMessages.svelte @@ -0,0 +1,14 @@ + + + + + \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/sveltekit/src/components/VirtualizedList/VirtualizedList.css b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/VirtualizedList/VirtualizedList.css new file mode 100644 index 0000000000..e13c43a1e8 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/VirtualizedList/VirtualizedList.css @@ -0,0 +1,20 @@ +.virtualized-list { + list-style: none; + padding: 0; + margin: 0; + max-height: 400px; + overflow-y: auto; +} + +.list-item { + padding: 10px; + border-bottom: 1px solid #ddd; +} + +.list-item:last-child { + border-bottom: none; +} + +.list-end { + height: 1px; +} \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/sveltekit/src/components/VirtualizedList/VirtualizedList.stories.ts b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/VirtualizedList/VirtualizedList.stories.ts new file mode 100644 index 0000000000..e6b81796ae --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/VirtualizedList/VirtualizedList.stories.ts @@ -0,0 +1,53 @@ +import VirtualizedList from './VirtualizedList.svelte'; +import type { Meta, StoryFn } from '@storybook/svelte'; + +const meta: Meta = { + title: 'Components/Lists/VirtualizedList', + component: VirtualizedList, + tags: ['autodocs'], + argTypes: { + items: { + control: { type: 'object' }, + }, + isLoading: { + control: { type: 'boolean' }, + }, + hasMore: { + control: { type: 'boolean' }, + }, + loadMore: { action: 'loadMore' }, + }, +}; + +export default meta; + +const Template: StoryFn = (args) => ({ + Component: VirtualizedList, + props: args, +}); + +const sampleItems = Array.from({ length: 20 }, (_, i) => `Item ${i + 1}`); + +export const Default = Template.bind({}); +Default.args = { + items: sampleItems, + isLoading: false, + hasMore: true, + loadMore: () => console.log('Loading more items...'), +}; + +export const LoadingMore = Template.bind({}); +LoadingMore.args = { + items: sampleItems, + isLoading: true, + hasMore: true, + loadMore: () => console.log('Loading more items...'), +}; + +export const EndOfList = Template.bind({}); +EndOfList.args = { + items: sampleItems, + isLoading: false, + hasMore: false, + loadMore: () => console.log('Loading more items...'), +}; \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/sveltekit/src/components/VirtualizedList/VirtualizedList.svelte b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/VirtualizedList/VirtualizedList.svelte new file mode 100644 index 0000000000..8aa50a7d5c --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/VirtualizedList/VirtualizedList.svelte @@ -0,0 +1,47 @@ + + +
      + {#each items as item} +
    • {item}
    • + {/each} + + {#if isLoading} +
    • Loading...
    • + {/if} + {#if !hasMore && !isLoading} +
    • End of List
    • + {/if} +
    + + \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/sveltekit/src/components/VisualCueForAccessibilityFocusIndicator/VisualCueForAccessibilityFocusIndicator.css b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/VisualCueForAccessibilityFocusIndicator/VisualCueForAccessibilityFocusIndicator.css new file mode 100644 index 0000000000..7eac18111f --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/VisualCueForAccessibilityFocusIndicator/VisualCueForAccessibilityFocusIndicator.css @@ -0,0 +1,25 @@ +:root { + --focus-indicator-padding: 1rem; + --focus-indicator-margin: 0.5rem; + --focus-indicator-border-radius: 4px; + --focus-indicator-font-size: 1rem; + --focus-indicator-bg: #f2f2f2; + --focus-indicator-color: #333; + --focus-indicator-outline-focused: 2px solid #007bff; + --focus-indicator-outline-unfocused: 2px solid transparent; +} + +.focus-indicator { + padding: var(--focus-indicator-padding); + margin: var(--focus-indicator-margin); + border-radius: var(--focus-indicator-border-radius); + font-size: var(--focus-indicator-font-size); + background-color: var(--focus-indicator-bg); + color: var(--focus-indicator-color); + outline: var(--focus-indicator-outline-unfocused); + transition: outline 0.3s ease; +} + +.focus-indicator.is-focused { + outline: var(--focus-indicator-outline-focused); +} \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/sveltekit/src/components/VisualCueForAccessibilityFocusIndicator/VisualCueForAccessibilityFocusIndicator.stories.ts b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/VisualCueForAccessibilityFocusIndicator/VisualCueForAccessibilityFocusIndicator.stories.ts new file mode 100644 index 0000000000..0312905c12 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/VisualCueForAccessibilityFocusIndicator/VisualCueForAccessibilityFocusIndicator.stories.ts @@ -0,0 +1,65 @@ +import VisualCueForAccessibilityFocusIndicator from './VisualCueForAccessibilityFocusIndicator.svelte'; +import type { Meta, StoryFn, StoryObj } from '@storybook/svelte'; + +const meta: Meta = { + title: 'component/Indicators/VisualCueForAccessibilityFocusIndicator', + component: VisualCueForAccessibilityFocusIndicator, + tags: ['autodocs'], + argTypes: { + isFocused: { control: 'boolean' } + }, + parameters: { + layout: 'centered', + viewport: { + viewports: { + smallMobile: { name: 'Small Mobile', styles: { width: '320px', height: '568px' } }, + largeMobile: { name: 'Large Mobile', styles: { width: '414px', height: '896px' } }, + tablet: { name: 'Tablet', styles: { width: '768px', height: '1024px' } }, + desktop: { name: 'Desktop', styles: { width: '1024px', height: '768px' } }, + } + } + } +}; + +export default meta; + +const Template:StoryFn = (args) => ({ + Component: VisualCueForAccessibilityFocusIndicator, + props:args, +}); + + +export const Default = Template.bind({}); +Default.args = { + isFocused: false +} + +export const Focused = Template.bind({}); +Focused.args = { + isFocused: true +} + +export const Unfocused = Template.bind({}); +Unfocused.args = { + isFocused: false +} + +// type Story = StoryObj; + +// export const Default: Story = { +// args: { + // isFocused: false +// } +// }; + +// export const Focused: Story = { +// args: { +// isFocused: true +// } +// }; + +// export const Unfocused: Story = { +// args: { +// isFocused: false +// } +// }; \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/sveltekit/src/components/VisualCueForAccessibilityFocusIndicator/VisualCueForAccessibilityFocusIndicator.svelte b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/VisualCueForAccessibilityFocusIndicator/VisualCueForAccessibilityFocusIndicator.svelte new file mode 100644 index 0000000000..e86af73db7 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/sveltekit/src/components/VisualCueForAccessibilityFocusIndicator/VisualCueForAccessibilityFocusIndicator.svelte @@ -0,0 +1,11 @@ + + +
    +

    Focus Indicator

    +
    + + \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/sveltekit/static/favicon.png b/pkgs/experimental/swarmakit/libs/sveltekit/static/favicon.png new file mode 100644 index 0000000000..825b9e65af Binary files /dev/null and b/pkgs/experimental/swarmakit/libs/sveltekit/static/favicon.png differ diff --git a/pkgs/experimental/swarmakit/libs/sveltekit/svelte.config.js b/pkgs/experimental/swarmakit/libs/sveltekit/svelte.config.js new file mode 100644 index 0000000000..4a82086e24 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/sveltekit/svelte.config.js @@ -0,0 +1,18 @@ +import adapter from '@sveltejs/adapter-auto'; +import { vitePreprocess } from '@sveltejs/vite-plugin-svelte'; + +/** @type {import('@sveltejs/kit').Config} */ +const config = { + // Consult https://kit.svelte.dev/docs/integrations#preprocessors + // for more information about preprocessors + preprocess: vitePreprocess(), + + kit: { + // adapter-auto only supports some environments, see https://kit.svelte.dev/docs/adapter-auto for a list. + // If your environment is not supported, or you settled on a specific environment, switch out the adapter. + // See https://kit.svelte.dev/docs/adapters for more information about adapters. + adapter: adapter() + } +}; + +export default config; diff --git a/pkgs/experimental/swarmakit/libs/sveltekit/tsconfig.json b/pkgs/experimental/swarmakit/libs/sveltekit/tsconfig.json new file mode 100644 index 0000000000..fc93cbd940 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/sveltekit/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "./.svelte-kit/tsconfig.json", + "compilerOptions": { + "allowJs": true, + "checkJs": true, + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true, + "resolveJsonModule": true, + "skipLibCheck": true, + "sourceMap": true, + "strict": true, + "moduleResolution": "bundler" + } + // Path aliases are handled by https://kit.svelte.dev/docs/configuration#alias + // except $lib which is handled by https://kit.svelte.dev/docs/configuration#files + // + // If you want to overwrite includes/excludes, make sure to copy over the relevant includes/excludes + // from the referenced tsconfig.json - TypeScript does not merge them in +} diff --git a/pkgs/experimental/swarmakit/libs/sveltekit/vite.config.ts b/pkgs/experimental/swarmakit/libs/sveltekit/vite.config.ts new file mode 100644 index 0000000000..bbf8c7da43 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/sveltekit/vite.config.ts @@ -0,0 +1,6 @@ +import { sveltekit } from '@sveltejs/kit/vite'; +import { defineConfig } from 'vite'; + +export default defineConfig({ + plugins: [sveltekit()] +}); diff --git a/pkgs/experimental/swarmakit/libs/vue/.gitignore b/pkgs/experimental/swarmakit/libs/vue/.gitignore new file mode 100644 index 0000000000..bc6d3062c4 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/vue/.gitignore @@ -0,0 +1,26 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? + +*storybook.log \ No newline at end of file diff --git a/pkgs/experimental/swarmakit/libs/vue/.storybook/main.ts b/pkgs/experimental/swarmakit/libs/vue/.storybook/main.ts new file mode 100644 index 0000000000..b779d6e6a7 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/vue/.storybook/main.ts @@ -0,0 +1,19 @@ +import type { StorybookConfig } from "@storybook/vue3-vite"; + +const config: StorybookConfig = { + stories: ["../src/**/*.mdx", + "../src/**/*.stories.@(js|jsx|mjs|ts|tsx)" + ], + addons: [ + "@storybook/addon-onboarding", + "@storybook/addon-links", + "@storybook/addon-essentials", + "@chromatic-com/storybook", + "@storybook/addon-interactions", + ], + framework: { + name: "@storybook/vue3-vite", + options: {}, + }, +}; +export default config; diff --git a/pkgs/experimental/swarmakit/libs/vue/.storybook/preview.ts b/pkgs/experimental/swarmakit/libs/vue/.storybook/preview.ts new file mode 100644 index 0000000000..e7772c2ced --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/vue/.storybook/preview.ts @@ -0,0 +1,60 @@ +import type { Preview, addParameters } from "@storybook/vue3"; +import '../src/themes/light.css'; // Load default light theme by default + +export const globalTypes = { + theme: { + name: 'Theme', + description: 'Global theme for components', + defaultValue: 'light', + toolbar: { + icon: 'circlehollow', + items: [ + { value: 'light', title: 'Light Theme' }, + { value: 'dark', title: 'Dark Theme' }, + { value: 'custom', title: 'Custom Theme' }, + ], + }, + }, +}; + +export const decorators = [ + (Story, context) => { + const theme = context.globals.theme; + + // Dynamically load the CSS for the selected theme + const link = document.getElementById('theme-style'); + if (link) { + document.head.removeChild(link); + } + const style = document.createElement('link'); + style.id = 'theme-style'; + style.rel = 'stylesheet'; + style.href = `/src/themes/${theme}.css`; + document.head.appendChild(style); + + // Set data-theme attribute for potential theme-based logic in CSS + document.body.setAttribute('data-theme', theme); + + return { + ...Story(), + }; + }, +]; + + + + + +const preview: Preview = { + parameters: { + controls: { + matchers: { + color: /(background|color)$/i, + date: /Date$/i, + }, + }, + }, +}; + +export default preview; + diff --git a/pkgs/experimental/swarmakit/libs/vue/.vscode/extensions.json b/pkgs/experimental/swarmakit/libs/vue/.vscode/extensions.json new file mode 100644 index 0000000000..a7cea0b067 --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/vue/.vscode/extensions.json @@ -0,0 +1,3 @@ +{ + "recommendations": ["Vue.volar"] +} diff --git a/pkgs/experimental/swarmakit/libs/vue/README.md b/pkgs/experimental/swarmakit/libs/vue/README.md new file mode 100644 index 0000000000..79bf6af25a --- /dev/null +++ b/pkgs/experimental/swarmakit/libs/vue/README.md @@ -0,0 +1,73 @@ +# Vue 3 + TypeScript + Vite + +
    + +[![Hits](https://hits.seeyoufarm.com/api/count/incr/badge.svg?url=https%3A%2F%2Fgithub.com%2Fswarmauri%2Fswarmakit%2Ftree%2Fmaster%2Flibs%2Fvue&count_bg=%2379C83D&title_bg=%23555555&icon=&icon_color=%23E7E7E7&title=hits&edge_flat=false)](https://hits.seeyoufarm.com) +![NPM Version](https://img.shields.io/npm/v/%40swarmakit%2Fvue) +![npm downloads](https://img.shields.io/npm/dt/@swarmakit/vue.svg) +[![License](https://img.shields.io/badge/License-Apache_2.0-blue.svg)](https://opensource.org/licenses/Apache-2.0) +[![Build and Publish Monorepo](https://github.com/swarmauri/swarmakit/actions/workflows/publish.yml/badge.svg)](https://github.com/swarmauri/swarmakit/actions/workflows/publish.yml) +
    + +
    + +![Static Badge](https://img.shields.io/badge/Vue-%234FC08D?style=for-the-badge&logo=vuedotjs&labelColor=black) +![Static Badge](https://img.shields.io/badge/TypeScript-1D4ED8?style=for-the-badge&logo=typescript&labelColor=black) +
    + +This template should help get you started developing with Vue 3 and TypeScript in Vite. The template uses Vue 3 ` + ``` + +2. **Example Usage in your Vue Template:** Use the imported component within your Vue template: + + ```html +